1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import java.io.OutputStream;
20 
21 /**
22  * YuvImage contains YUV data and provides a method that compresses a region of
23  * the YUV data to a Jpeg. The YUV data should be provided as a single byte
24  * array irrespective of the number of image planes in it.
25  * Currently only ImageFormat.NV21 and ImageFormat.YUY2 are supported.
26  *
27  * To compress a rectangle region in the YUV data, users have to specify the
28  * region by left, top, width and height.
29  */
30 public class YuvImage {
31 
32     /**
33      * Number of bytes of temp storage we use for communicating between the
34      * native compressor and the java OutputStream.
35      */
36     private final static int WORKING_COMPRESS_STORAGE = 4096;
37 
38    /**
39      * The YUV format as defined in {@link ImageFormat}.
40      */
41     private int mFormat;
42 
43     /**
44      * The raw YUV data.
45      * In the case of more than one image plane, the image planes must be
46      * concatenated into a single byte array.
47      */
48     private byte[] mData;
49 
50     /**
51      * The number of row bytes in each image plane.
52      */
53     private int[] mStrides;
54 
55     /**
56      * The width of the image.
57      */
58     private int mWidth;
59 
60     /**
61      * The height of the the image.
62      */
63     private int mHeight;
64 
65     /**
66      * Construct an YuvImage.
67      *
68      * @param yuv     The YUV data. In the case of more than one image plane, all the planes must be
69      *                concatenated into a single byte array.
70      * @param format  The YUV data format as defined in {@link ImageFormat}.
71      * @param width   The width of the YuvImage.
72      * @param height  The height of the YuvImage.
73      * @param strides (Optional) Row bytes of each image plane. If yuv contains padding, the stride
74      *                of each image must be provided. If strides is null, the method assumes no
75      *                padding and derives the row bytes by format and width itself.
76      * @throws IllegalArgumentException if format is not support; width or height <= 0; or yuv is
77      *                null.
78      */
YuvImage(byte[] yuv, int format, int width, int height, int[] strides)79     public YuvImage(byte[] yuv, int format, int width, int height, int[] strides) {
80         if (format != ImageFormat.NV21 &&
81                 format != ImageFormat.YUY2) {
82             throw new IllegalArgumentException(
83                     "only support ImageFormat.NV21 " +
84                     "and ImageFormat.YUY2 for now");
85         }
86 
87         if (width <= 0  || height <= 0) {
88             throw new IllegalArgumentException(
89                     "width and height must large than 0");
90         }
91 
92         if (yuv == null) {
93             throw new IllegalArgumentException("yuv cannot be null");
94         }
95 
96         if (strides == null) {
97             mStrides = calculateStrides(width, format);
98         } else {
99             mStrides = strides;
100         }
101 
102         mData = yuv;
103         mFormat = format;
104         mWidth = width;
105         mHeight = height;
106     }
107 
108     /**
109      * Compress a rectangle region in the YuvImage to a jpeg.
110      * Only ImageFormat.NV21 and ImageFormat.YUY2
111      * are supported for now.
112      *
113      * @param rectangle The rectangle region to be compressed. The medthod checks if rectangle is
114      *                  inside the image. Also, the method modifies rectangle if the chroma pixels
115      *                  in it are not matched with the luma pixels in it.
116      * @param quality   Hint to the compressor, 0-100. 0 meaning compress for
117      *                  small size, 100 meaning compress for max quality.
118      * @param stream    OutputStream to write the compressed data.
119      * @return          True if the compression is successful.
120      * @throws IllegalArgumentException if rectangle is invalid; quality is not within [0,
121      *                  100]; or stream is null.
122      */
compressToJpeg(Rect rectangle, int quality, OutputStream stream)123     public boolean compressToJpeg(Rect rectangle, int quality, OutputStream stream) {
124         Rect wholeImage = new Rect(0, 0, mWidth, mHeight);
125         if (!wholeImage.contains(rectangle)) {
126             throw new IllegalArgumentException(
127                     "rectangle is not inside the image");
128         }
129 
130         if (quality < 0 || quality > 100) {
131             throw new IllegalArgumentException("quality must be 0..100");
132         }
133 
134         if (stream == null) {
135             throw new IllegalArgumentException("stream cannot be null");
136         }
137 
138         adjustRectangle(rectangle);
139         int[] offsets = calculateOffsets(rectangle.left, rectangle.top);
140 
141         return nativeCompressToJpeg(mData, mFormat, rectangle.width(),
142                 rectangle.height(), offsets, mStrides, quality, stream,
143                 new byte[WORKING_COMPRESS_STORAGE]);
144     }
145 
146 
147    /**
148      * @return the YUV data.
149      */
getYuvData()150     public byte[] getYuvData() {
151         return mData;
152     }
153 
154     /**
155      * @return the YUV format as defined in {@link ImageFormat}.
156      */
getYuvFormat()157     public int getYuvFormat() {
158         return mFormat;
159     }
160 
161     /**
162      * @return the number of row bytes in each image plane.
163      */
getStrides()164     public int[] getStrides() {
165         return mStrides;
166     }
167 
168     /**
169      * @return the width of the image.
170      */
getWidth()171     public int getWidth() {
172         return mWidth;
173     }
174 
175     /**
176      * @return the height of the image.
177      */
getHeight()178     public int getHeight() {
179         return mHeight;
180     }
181 
calculateOffsets(int left, int top)182     int[] calculateOffsets(int left, int top) {
183         int[] offsets = null;
184         if (mFormat == ImageFormat.NV21) {
185             offsets = new int[] {top * mStrides[0] + left,
186                   mHeight * mStrides[0] + top / 2 * mStrides[1]
187                   + left / 2 * 2 };
188             return offsets;
189         }
190 
191         if (mFormat == ImageFormat.YUY2) {
192             offsets = new int[] {top * mStrides[0] + left / 2 * 4};
193             return offsets;
194         }
195 
196         return offsets;
197     }
198 
calculateStrides(int width, int format)199     private int[] calculateStrides(int width, int format) {
200         int[] strides = null;
201         if (format == ImageFormat.NV21) {
202             strides = new int[] {width, width};
203             return strides;
204         }
205 
206         if (format == ImageFormat.YUY2) {
207             strides = new int[] {width * 2};
208             return strides;
209         }
210 
211         return strides;
212     }
213 
adjustRectangle(Rect rect)214    private void adjustRectangle(Rect rect) {
215        int width = rect.width();
216        int height = rect.height();
217        if (mFormat == ImageFormat.NV21) {
218            // Make sure left, top, width and height are all even.
219            width &= ~1;
220            height &= ~1;
221            rect.left &= ~1;
222            rect.top &= ~1;
223            rect.right = rect.left + width;
224            rect.bottom = rect.top + height;
225         }
226 
227         if (mFormat == ImageFormat.YUY2) {
228             // Make sure left and width are both even.
229             width &= ~1;
230             rect.left &= ~1;
231             rect.right = rect.left + width;
232         }
233     }
234 
235     //////////// native methods
236 
nativeCompressToJpeg(byte[] oriYuv, int format, int width, int height, int[] offsets, int[] strides, int quality, OutputStream stream, byte[] tempStorage)237     private static native boolean nativeCompressToJpeg(byte[] oriYuv,
238             int format, int width, int height, int[] offsets, int[] strides,
239             int quality, OutputStream stream, byte[] tempStorage);
240 }
241