Skip to content

Commit da67405

Browse files
committed
Add support for YUV_420_888 Image format.
Casting the bytes to a type directly is not possible, thus allocating a new texture is necessary (and costly). But on the bright side, we avoid the conversion inside the camera plugin [0]. [0] https://github.com/flutter/packages/blob/d1fd6232ec33cd5a25aa762e605c494afced812f/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/ImageStreamReaderUtils.java#L35
1 parent da48532 commit da67405

File tree

4 files changed

+111
-63
lines changed

4 files changed

+111
-63
lines changed

packages/example/lib/vision_detector_views/camera_view.dart

+38-13
Original file line numberDiff line numberDiff line change
@@ -358,27 +358,52 @@ class _CameraViewState extends State<CameraView> {
358358

359359
// get image format
360360
final format = InputImageFormatValue.fromRawValue(image.format.raw);
361-
// validate format depending on platform
362-
// only supported formats:
363-
// * nv21 for Android
364-
// * bgra8888 for iOS
365-
if (format == null ||
366-
(Platform.isAndroid && format != InputImageFormat.nv21) ||
367-
(Platform.isIOS && format != InputImageFormat.bgra8888)) return null;
361+
if (format == null) {
362+
print('could not find format from raw value: $image.format.raw');
363+
return null;
364+
}
365+
// Validate format depending on platform
366+
final androidSupportedFormats = [
367+
InputImageFormat.nv21,
368+
InputImageFormat.yv12,
369+
InputImageFormat.yuv_420_888
370+
];
371+
if ((Platform.isAndroid && androidSupportedFormats.contains(format)) ||
372+
(Platform.isIOS && format != InputImageFormat.bgra8888)) {
373+
print('image format is not supported: $format');
374+
return null;
375+
}
368376

369-
// since format is constraint to nv21 or bgra8888, both only have one plane
370-
if (image.planes.length != 1) return null;
371-
final plane = image.planes.first;
377+
// Compile a flat list of all image data. For image formats with multiple planes,
378+
// takes some copying.
379+
final Uint8List bytes = image.planes.length == 1
380+
? image.planes.first.bytes
381+
: _concatenatePlanes(image);
372382

373383
// compose InputImage using bytes
374384
return InputImage.fromBytes(
375-
bytes: plane.bytes,
385+
bytes: bytes,
376386
metadata: InputImageMetadata(
377387
size: Size(image.width.toDouble(), image.height.toDouble()),
378388
rotation: rotation, // used only in Android
379-
format: format, // used only in iOS
380-
bytesPerRow: plane.bytesPerRow, // used only in iOS
389+
format: format,
390+
bytesPerRow: image.planes.first.bytesPerRow, // used only in iOS
381391
),
382392
);
383393
}
394+
395+
Uint8List _concatenatePlanes(CameraImage image) {
396+
int length = 0;
397+
for (final Plane p in image.planes) {
398+
length += p.bytes.length;
399+
}
400+
401+
final Uint8List bytes = Uint8List(length);
402+
int offset = 0;
403+
for (final Plane p in image.planes) {
404+
bytes.setRange(offset, offset + p.bytes.length, p.bytes);
405+
offset += p.bytes.length;
406+
}
407+
return bytes;
408+
}
384409
}

packages/example/pubspec.lock

+38-46
Original file line numberDiff line numberDiff line change
@@ -25,38 +25,30 @@ packages:
2525
url: "https://pub.dev"
2626
source: hosted
2727
version: "0.11.0+2"
28-
camera_android:
29-
dependency: "direct main"
30-
description:
31-
name: camera_android
32-
sha256: "19b7226387218864cb2388e1ad5db7db50d065222f5511254b03fc397dd21a5e"
33-
url: "https://pub.dev"
34-
source: hosted
35-
version: "0.10.9+17"
3628
camera_android_camerax:
3729
dependency: transitive
3830
description:
3931
name: camera_android_camerax
40-
sha256: "2bb0724371bae3c0889d7e0b1665357e4aa6ba6c8d32ffa3e178098ba81ed3df"
32+
sha256: ecadc214daed34d8503540525d26577731c066f1993c254aa5272da7629e8f10
4133
url: "https://pub.dev"
4234
source: hosted
43-
version: "0.6.11"
35+
version: "0.6.12"
4436
camera_avfoundation:
4537
dependency: transitive
4638
description:
4739
name: camera_avfoundation
48-
sha256: "7c28969a975a7eb2349bc2cb2dfe3ad218a33dba9968ecfb181ce08c87486655"
40+
sha256: c3038e6e72e284b14ad246a419f26908c08f8886d114cb8a2e351988439bfa68
4941
url: "https://pub.dev"
5042
source: hosted
51-
version: "0.9.17+3"
43+
version: "0.9.17+6"
5244
camera_platform_interface:
5345
dependency: transitive
5446
description:
5547
name: camera_platform_interface
56-
sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061
48+
sha256: "953e7baed3a7c8fae92f7200afeb2be503ff1a17c3b4e4ed7b76f008c2810a31"
5749
url: "https://pub.dev"
5850
source: hosted
59-
version: "2.8.0"
51+
version: "2.9.0"
6052
camera_web:
6153
dependency: transitive
6254
description:
@@ -117,10 +109,10 @@ packages:
117109
dependency: transitive
118110
description:
119111
name: file_selector_linux
120-
sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2"
112+
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
121113
url: "https://pub.dev"
122114
source: hosted
123-
version: "0.9.3"
115+
version: "0.9.3+2"
124116
file_selector_macos:
125117
dependency: transitive
126118
description:
@@ -162,18 +154,18 @@ packages:
162154
dependency: "direct main"
163155
description:
164156
name: flutter_pdfview
165-
sha256: "6b625b32a9102780236554dff42f2d798b4627704ab4a3153c07f2134a52b697"
157+
sha256: "2e3fa359524e9865ec25a64593b65092b4a9974c5871228c1a771300a003d150"
166158
url: "https://pub.dev"
167159
source: hosted
168-
version: "1.3.3"
160+
version: "1.4.0"
169161
flutter_plugin_android_lifecycle:
170162
dependency: transitive
171163
description:
172164
name: flutter_plugin_android_lifecycle
173-
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
165+
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e"
174166
url: "https://pub.dev"
175167
source: hosted
176-
version: "2.0.23"
168+
version: "2.0.24"
177169
flutter_test:
178170
dependency: "direct dev"
179171
description: flutter
@@ -308,10 +300,10 @@ packages:
308300
dependency: transitive
309301
description:
310302
name: http_parser
311-
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
303+
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
312304
url: "https://pub.dev"
313305
source: hosted
314-
version: "4.0.2"
306+
version: "4.1.2"
315307
image_picker:
316308
dependency: "direct main"
317309
description:
@@ -324,26 +316,26 @@ packages:
324316
dependency: transitive
325317
description:
326318
name: image_picker_android
327-
sha256: d3e5e00fdfeca8fd4ffb3227001264d449cc8950414c2ff70b0e06b9c628e643
319+
sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c
328320
url: "https://pub.dev"
329321
source: hosted
330-
version: "0.8.12+15"
322+
version: "0.8.12+20"
331323
image_picker_for_web:
332324
dependency: transitive
333325
description:
334326
name: image_picker_for_web
335-
sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50"
327+
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
336328
url: "https://pub.dev"
337329
source: hosted
338-
version: "3.0.5"
330+
version: "3.0.6"
339331
image_picker_ios:
340332
dependency: transitive
341333
description:
342334
name: image_picker_ios
343-
sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447"
335+
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
344336
url: "https://pub.dev"
345337
source: hosted
346-
version: "0.8.12"
338+
version: "0.8.12+2"
347339
image_picker_linux:
348340
dependency: transitive
349341
description:
@@ -364,10 +356,10 @@ packages:
364356
dependency: transitive
365357
description:
366358
name: image_picker_platform_interface
367-
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
359+
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
368360
url: "https://pub.dev"
369361
source: hosted
370-
version: "2.10.0"
362+
version: "2.10.1"
371363
image_picker_windows:
372364
dependency: transitive
373365
description:
@@ -404,10 +396,10 @@ packages:
404396
dependency: transitive
405397
description:
406398
name: lints
407-
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
399+
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
408400
url: "https://pub.dev"
409401
source: hosted
410-
version: "5.0.0"
402+
version: "5.1.1"
411403
matcher:
412404
dependency: transitive
413405
description:
@@ -436,10 +428,10 @@ packages:
436428
dependency: transitive
437429
description:
438430
name: mime
439-
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
431+
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
440432
url: "https://pub.dev"
441433
source: hosted
442-
version: "1.0.6"
434+
version: "2.0.0"
443435
path:
444436
dependency: "direct main"
445437
description:
@@ -452,26 +444,26 @@ packages:
452444
dependency: "direct main"
453445
description:
454446
name: path_provider
455-
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
447+
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
456448
url: "https://pub.dev"
457449
source: hosted
458-
version: "2.1.4"
450+
version: "2.1.5"
459451
path_provider_android:
460452
dependency: transitive
461453
description:
462454
name: path_provider_android
463-
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
455+
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
464456
url: "https://pub.dev"
465457
source: hosted
466-
version: "2.2.12"
458+
version: "2.2.15"
467459
path_provider_foundation:
468460
dependency: transitive
469461
description:
470462
name: path_provider_foundation
471-
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
463+
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
472464
url: "https://pub.dev"
473465
source: hosted
474-
version: "2.4.0"
466+
version: "2.4.1"
475467
path_provider_linux:
476468
dependency: transitive
477469
description:
@@ -500,10 +492,10 @@ packages:
500492
dependency: transitive
501493
description:
502494
name: platform
503-
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
495+
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
504496
url: "https://pub.dev"
505497
source: hosted
506-
version: "3.1.5"
498+
version: "3.1.6"
507499
plugin_platform_interface:
508500
dependency: transitive
509501
description:
@@ -545,10 +537,10 @@ packages:
545537
dependency: transitive
546538
description:
547539
name: stream_transform
548-
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
540+
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
549541
url: "https://pub.dev"
550542
source: hosted
551-
version: "2.1.0"
543+
version: "2.1.1"
552544
string_scanner:
553545
dependency: transitive
554546
description:
@@ -577,10 +569,10 @@ packages:
577569
dependency: transitive
578570
description:
579571
name: typed_data
580-
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
572+
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
581573
url: "https://pub.dev"
582574
source: hosted
583-
version: "1.3.2"
575+
version: "1.4.0"
584576
vector_math:
585577
dependency: transitive
586578
description:

packages/example/pubspec.yaml

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ dependencies:
1616
flutter_pdfview: ^1.3.3
1717
image_picker: ^1.1.2
1818
camera: ^0.11.0+2
19-
# The default Android implementation from camera_android_camerax doesn't support the required image format.
20-
camera_android: ^0.10.9+17
2119
path: ^1.9.0
2220
path_provider: ^2.1.4
2321

packages/google_mlkit_commons/android/src/main/java/com/google_mlkit_commons/InputImageConverter.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
package com.google_mlkit_commons;
22

33
import android.content.Context;
4+
import android.graphics.Canvas;
45
import android.graphics.ImageFormat;
6+
import android.graphics.SurfaceTexture;
7+
import android.media.Image;
8+
import android.media.ImageWriter;
59
import android.net.Uri;
610
import android.util.Log;
11+
import android.view.Surface;
712

813
import com.google.mlkit.vision.common.InputImage;
914

1015
import java.io.File;
1116
import java.io.IOException;
17+
import java.nio.ByteBuffer;
1218
import java.util.Map;
1319
import java.util.Objects;
1420

@@ -53,9 +59,36 @@ public static InputImage getInputImageFromData(Map<String, Object> imageData,
5359
rotationDegrees,
5460
imageFormat);
5561
}
62+
if (imageFormat == ImageFormat.YUV_420_888) {
63+
// This image format is only supported in InputImage.fromMediaImage, which requires to transform the data to the right java type.
64+
ImageWriter writer = new ImageWriter.Builder(new Surface(new SurfaceTexture(true))).setWidthAndHeight(width, height).setImageFormat(imageFormat).build();
65+
// TODO(panmari): Does this need any cleanup by calling close() somewhere?
66+
// Currently, this causes some logging like
67+
// A resource failed to call Surface.release
68+
// Calling writer.close() likely will do the trick (after) processing).
69+
Image image = writer.dequeueInputImage();
70+
if (image == null) {
71+
result.error("InputImageConverterError", "failed to allocate space for input image", null);
72+
return null;
73+
}
74+
// Deconstruct individual planes again from flattened array.
75+
Image.Plane[] planes = image.getPlanes();
76+
// Y plane
77+
ByteBuffer yBuffer = planes[0].getBuffer();
78+
yBuffer.put(data, 0, width * height);
79+
80+
// U plane
81+
ByteBuffer uBuffer = planes[1].getBuffer();
82+
int uOffset = width * height;
83+
uBuffer.put(data, uOffset, (width * height) / 4);
84+
85+
// V plane
86+
ByteBuffer vBuffer = planes[2].getBuffer();
87+
int vOffset = uOffset + (width * height) / 4;
88+
vBuffer.put(data, vOffset, (width * height) / 4);
89+
return InputImage.fromMediaImage(image, rotationDegrees);
90+
}
5691
result.error("InputImageConverterError", "ImageFormat is not supported.", null);
57-
// TODO: Use InputImage.fromMediaImage, which supports more types, e.g. IMAGE_FORMAT_YUV_420_888.
58-
// See https://developers.google.com/android/reference/com/google/mlkit/vision/common/InputImage#fromMediaImage(android.media.Image,%20int)
5992
return null;
6093
} catch (Exception e) {
6194
Log.e("ImageError", "Getting Image failed");

0 commit comments

Comments
 (0)