33import android .graphics .ImageFormat ;
44import android .media .Image ;
55
6+ import androidx .annotation .IntDef ;
7+
8+ import java .lang .annotation .Retention ;
9+ import java .lang .annotation .RetentionPolicy ;
610import java .nio .ByteBuffer ;
711
812
9- /*
10- Taken from https://github.com/gordinmitya/yuv2buf
11- unit-test, demo application and performance benchmarks are available there
12- */
1313abstract public class Yuv {
1414/*
15- Intro to YUV image formats:
15+ This file is part of https://github.com/gordinmitya/yuv2buf.
16+ Follow the link to find demo app, performance benchmarks and unit tests.
17+
18+ Intro to YUV image formats:
1619 YUV_420_888 - is a generic format that can be represented as I420, YV12, NV21, and NV12.
1720 420 means that for each 4 luminosity pixels we have 2 chroma pixels: U and V.
1821
@@ -31,7 +34,8 @@ abstract public class Yuv {
3134
3235 * YV12 and NV12 are the same as previous formats but with swapped order of V and U. (U then V)
3336
34- Visualization of these 4 formats: https://user-images.githubusercontent.com/9286092/89119601-4f6f8100-d4b8-11ea-9a51-2765f7e513c2.jpg
37+ Visualization of these 4 formats:
38+ https://user-images.githubusercontent.com/9286092/89119601-4f6f8100-d4b8-11ea-9a51-2765f7e513c2.jpg
3539
3640 It's guaranteed that image.getPlanes() always returns planes in order Y U V for YUV_420_888.
3741 https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888
@@ -42,23 +46,17 @@ Because I420 and NV21 are more widely supported (RenderScript, OpenCV, MNN)
4246 More about each format: https://www.fourcc.org/yuv.php
4347*/
4448
45-
46- public enum Type {
47- YUV_NV21 (ImageFormat .NV21 ),
48- YUV_I420 (ImageFormat .YUV_420_888 );
49-
50- public final int format ;
51-
52- Type (int format ) {
53- this .format = format ;
54- }
49+ @ Retention (RetentionPolicy .SOURCE )
50+ @ IntDef ({ImageFormat .NV21 , ImageFormat .YUV_420_888 })
51+ public @interface YuvType {
5552 }
5653
5754 public static class Converted {
58- public final Type type ;
55+ @ YuvType
56+ public final int type ;
5957 public final ByteBuffer buffer ;
6058
61- private Converted (Type type , ByteBuffer buffer ) {
59+ private Converted (@ YuvType int type , ByteBuffer buffer ) {
6260 this .type = type ;
6361 this .buffer = buffer ;
6462 }
@@ -67,7 +65,8 @@ private Converted(Type type, ByteBuffer buffer) {
6765 /*
6866 Api.
6967 */
70- public static Type detectType (Image image ) {
68+ @ YuvType
69+ public static int detectType (Image image ) {
7170 return detectType (wrap (image ));
7271 }
7372
@@ -91,20 +90,20 @@ private static ImageWrapper wrap(Image image) {
9190
9291 private static PlaneWrapper wrap (int width , int height , Image .Plane plane ) {
9392 return new PlaneWrapper (
94- width ,
95- height ,
96- plane .getBuffer (),
97- plane .getRowStride (),
98- plane .getPixelStride ()
93+ width ,
94+ height ,
95+ plane .getBuffer (),
96+ plane .getRowStride (),
97+ plane .getPixelStride ()
9998 );
10099 }
101100
102- // CameraX api. If you DO need it – just uncomment lines below.
103- // not included by default see https://github.com/android/camera-samples/pull/330
104- /*
105- import androidx.camera.core.ImageProxy;
106-
107- public static Type detectType(ImageProxy image) {
101+ /*
102+ CameraX api. If you don't need it – just comment lines below.
103+ */
104+ /*
105+ @YuvType
106+ public static int detectType(ImageProxy image) {
108107 return detectType(wrap(image));
109108 }
110109
@@ -128,11 +127,11 @@ private static ImageWrapper wrap(ImageProxy image) {
128127
129128 private static PlaneWrapper wrap(int width, int height, ImageProxy.PlaneProxy plane) {
130129 return new PlaneWrapper(
131- width,
132- height,
133- plane.getBuffer(),
134- plane.getRowStride(),
135- plane.getPixelStride()
130+ width,
131+ height,
132+ plane.getBuffer(),
133+ plane.getRowStride(),
134+ plane.getPixelStride()
136135 );
137136 }
138137 */
@@ -146,16 +145,17 @@ private static PlaneWrapper wrap(int width, int height, ImageProxy.PlaneProxy pl
146145 other pixelStride are not possible
147146 @see #ImageWrapper.checkFormat()
148147 */
149- static Type detectType (ImageWrapper image ) {
150-
151- if (image .u .pixelStride == 1 )
152- return Type .YUV_I420 ;
153- else
154- return Type .YUV_NV21 ;
148+ @ YuvType
149+ static int detectType (ImageWrapper image ) {
150+ if (image .u .pixelStride == 1 ) {
151+ return ImageFormat .YUV_420_888 ;
152+ } else {
153+ return ImageFormat .NV21 ;
154+ }
155155 }
156156
157157 static Converted toBuffer (ImageWrapper image , ByteBuffer reuse ) {
158- Type type = detectType (image );
158+ final int type = detectType (image );
159159 ByteBuffer output = prepareOutput (image , reuse );
160160 removePadding (image , type , output );
161161 return new Converted (type , output );
@@ -165,19 +165,24 @@ private static ByteBuffer prepareOutput(ImageWrapper image, ByteBuffer reuse) {
165165 int sizeOutput = image .width * image .height * 3 / 2 ;
166166 ByteBuffer output ;
167167 if (reuse == null
168- || reuse .capacity () < sizeOutput
169- || reuse .isReadOnly ()
170- || !reuse .isDirect ()) {
168+ || reuse .capacity () < sizeOutput
169+ || reuse .isReadOnly ()
170+ || !reuse .isDirect ()) {
171171 output = ByteBuffer .allocateDirect (sizeOutput );
172- } else
172+ } else {
173173 output = reuse ;
174+ }
174175 output .rewind ();
175176 return output ;
176177 }
177178
178179 // Input buffers are always direct as described in
179180 // https://developer.android.com/reference/android/media/Image.Plane#getBuffer()
180- private static void removePadding (ImageWrapper image , Type type , ByteBuffer output ) {
181+ private static void removePadding (
182+ ImageWrapper image ,
183+ @ YuvType final int type ,
184+ ByteBuffer output
185+ ) {
181186 int sizeLuma = image .y .width * image .y .height ;
182187 int sizeChroma = image .u .width * image .u .height ;
183188
@@ -188,7 +193,7 @@ private static void removePadding(ImageWrapper image, Type type, ByteBuffer outp
188193 output .put (image .y .buffer );
189194 }
190195
191- if (type . equals ( Type . YUV_I420 ) ) {
196+ if (type == ImageFormat . YUV_420_888 ) {
192197 if (image .u .rowStride > image .u .width ) {
193198 removePaddingCompact (image .u , output , sizeLuma );
194199 removePaddingCompact (image .v , output , sizeLuma + sizeChroma );
@@ -203,17 +208,23 @@ private static void removePadding(ImageWrapper image, Type type, ByteBuffer outp
203208 removePaddingNotCompact (image , output , sizeLuma );
204209 } else {
205210 output .position (sizeLuma );
206- output .put (image .v .buffer );
207- byte lastOne = image .u .buffer .get (image .u .buffer .capacity () - 1 );
208- output .put (lastOne );
211+ ByteBuffer uv = image .v .buffer ;
212+ final int properUVSize = image .v .height * image .v .rowStride - 1 ;
213+ if (uv .capacity () > properUVSize ) {
214+ uv = clipBuffer (image .v .buffer , 0 , properUVSize );
215+ }
216+ output .put (uv );
217+ final byte lastOne = image .u .buffer .get (image .u .buffer .capacity () - 1 );
218+ output .put (output .capacity () - 1 , lastOne );
209219 }
210220 }
211221 output .rewind ();
212222 }
213223
214224 private static void removePaddingCompact (PlaneWrapper plane , ByteBuffer dst , int offset ) {
215- if (plane .pixelStride != 1 )
225+ if (plane .pixelStride != 1 ) {
216226 throw new IllegalArgumentException ("use removePaddingCompact with pixelStride == 1" );
227+ }
217228
218229 ByteBuffer src = plane .buffer ;
219230 int rowStride = plane .rowStride ;
@@ -226,8 +237,9 @@ private static void removePaddingCompact(PlaneWrapper plane, ByteBuffer dst, int
226237 }
227238
228239 private static void removePaddingNotCompact (ImageWrapper image , ByteBuffer dst , int offset ) {
229- if (image .u .pixelStride != 2 )
240+ if (image .u .pixelStride != 2 ) {
230241 throw new IllegalArgumentException ("use removePaddingNotCompact pixelStride == 2" );
242+ }
231243
232244 int width = image .u .width ;
233245 int height = image .u .height ;
@@ -268,22 +280,22 @@ static class ImageWrapper {
268280 private void checkFormat () {
269281 if (y .pixelStride != 1 ) {
270282 throw new IllegalArgumentException (String .format (
271- "Pixel stride for Y plane must be 1 but got %d instead" ,
272- y .pixelStride
283+ "Pixel stride for Y plane must be 1 but got %d instead" ,
284+ y .pixelStride
273285 ));
274286 }
275287 if (u .pixelStride != v .pixelStride || u .rowStride != v .rowStride ) {
276288 throw new IllegalArgumentException (String .format (
277- "U and V planes must have the same pixel and row strides " +
278- "but got pixel=%d row=%d for U " +
279- "and pixel=%d and row=%d for V" ,
280- u .pixelStride , u .rowStride ,
281- v .pixelStride , v .rowStride
289+ "U and V planes must have the same pixel and row strides " +
290+ "but got pixel=%d row=%d for U " +
291+ "and pixel=%d and row=%d for V" ,
292+ u .pixelStride , u .rowStride ,
293+ v .pixelStride , v .rowStride
282294 ));
283295 }
284296 if (u .pixelStride != 1 && u .pixelStride != 2 ) {
285297 throw new IllegalArgumentException (
286- "Supported pixel strides for U and V planes are 1 and 2"
298+ "Supported pixel strides for U and V planes are 1 and 2"
287299 );
288300 }
289301 }
0 commit comments