44import android .graphics .Bitmap ;
55import android .graphics .ImageFormat ;
66import android .graphics .Rect ;
7- import android .graphics .YuvImage ;
8- import android .graphics .BitmapFactory ;
97import android .util .Log ;
108
119import com .zebra .ai .vision .detector .AIVisionSDKException ;
@@ -132,15 +130,15 @@ public void analyze(@NonNull ImageProxy image) {
132130 // Determine which image data to process
133131 ImageData imageData ;
134132 if (currentCropRegion != null ) {
135- // Crop the image before processing
133+ // Crop the image before processing - returns grayscale bitmap directly
136134 Bitmap croppedBitmap = cropImageProxy (image , currentCropRegion );
137135 if (croppedBitmap != null ) {
138136 // When cropping, we pass rotation=0 because:
139137 // 1. We crop in raw image space (before rotation)
140138 // 2. We want bounding boxes in raw image space (so we can add raw crop offset)
141139 // 3. The activity uses lastImageRotationDegrees to transform to effective space
142140 imageData = ImageData .fromBitmap (croppedBitmap , 0 );
143- Log .d (TAG , "Processing cropped image: " + croppedBitmap .getWidth () + "x" + croppedBitmap .getHeight () + " (rotation=" + rotationDegrees + " stored for activity)" );
141+ Log .d (TAG , "Processing grayscale cropped image: " + croppedBitmap .getWidth () + "x" + croppedBitmap .getHeight () + " (rotation=" + rotationDegrees + " stored for activity)" );
144142 } else {
145143 // Fallback to full image if cropping fails
146144 Log .w (TAG , "Cropping failed, falling back to full image" );
@@ -222,12 +220,13 @@ private Bitmap cropImageProxy(@NonNull ImageProxy image, @NonNull Rect cropRect)
222220 return null ;
223221 }
224222
225- // Try native implementation first for best performance
223+ // Use grayscale conversion - much faster than full YUV to RGB
224+ // The Y plane is already grayscale, so we just copy it directly
226225 if (NativeYuvProcessor .isAvailable ()) {
227- return cropYuvToRgbNative (image , left , top , cropWidth , cropHeight );
226+ return cropYuvToGrayscaleNative (image , left , top , cropWidth , cropHeight );
228227 } else {
229228 // Fall back to Java implementation
230- return cropYuvToRgbJava (image , left , top , cropWidth , cropHeight );
229+ return cropYuvToGrayscaleJava (image , left , top , cropWidth , cropHeight );
231230 }
232231
233232 } catch (Exception e ) {
@@ -237,29 +236,25 @@ private Bitmap cropImageProxy(@NonNull ImageProxy image, @NonNull Rect cropRect)
237236 }
238237
239238 /**
240- * Native NDK implementation for cropped YUV to RGB conversion.
241- * Writes directly to a pre-allocated Bitmap for maximum efficiency.
239+ * Ultra-fast native grayscale cropping.
240+ * Only reads the Y plane (which is already grayscale) and writes directly to bitmap.
241+ * This is significantly faster than full YUV to RGB conversion.
242242 */
243243 @ Nullable
244- private Bitmap cropYuvToRgbNative (@ NonNull ImageProxy image , int cropLeft , int cropTop , int cropWidth , int cropHeight ) {
244+ private Bitmap cropYuvToGrayscaleNative (@ NonNull ImageProxy image , int cropLeft , int cropTop , int cropWidth , int cropHeight ) {
245245 try {
246246 ImageProxy .PlaneProxy [] planes = image .getPlanes ();
247247
248248 ByteBuffer yBuffer = planes [0 ].getBuffer ();
249- ByteBuffer uBuffer = planes [1 ].getBuffer ();
250- ByteBuffer vBuffer = planes [2 ].getBuffer ();
251-
252249 int yRowStride = planes [0 ].getRowStride ();
253- int uvRowStride = planes [1 ].getRowStride ();
254- int uvPixelStride = planes [1 ].getPixelStride ();
255250
256251 // Create output bitmap
257252 Bitmap bitmap = Bitmap .createBitmap (cropWidth , cropHeight , Bitmap .Config .ARGB_8888 );
258253
259- // Call native method to write directly to bitmap
260- boolean success = NativeYuvProcessor .cropYuvToBitmapNative (
261- yBuffer , uBuffer , vBuffer ,
262- yRowStride , uvRowStride , uvPixelStride ,
254+ // Call native method - only needs Y plane!
255+ boolean success = NativeYuvProcessor .cropYToGrayscaleBitmapNative (
256+ yBuffer ,
257+ yRowStride ,
263258 cropLeft , cropTop , cropWidth , cropHeight ,
264259 bitmap
265260 );
@@ -268,77 +263,50 @@ private Bitmap cropYuvToRgbNative(@NonNull ImageProxy image, int cropLeft, int c
268263 return bitmap ;
269264 } else {
270265 bitmap .recycle ();
271- Log .w (TAG , "Native YUV conversion failed, falling back to Java" );
272- return cropYuvToRgbJava (image , cropLeft , cropTop , cropWidth , cropHeight );
266+ Log .w (TAG , "Native grayscale conversion failed, falling back to Java" );
267+ return cropYuvToGrayscaleJava (image , cropLeft , cropTop , cropWidth , cropHeight );
273268 }
274269
275270 } catch (Exception e ) {
276- Log .e (TAG , "Error in cropYuvToRgbNative : " + e .getMessage ());
277- return cropYuvToRgbJava (image , cropLeft , cropTop , cropWidth , cropHeight );
271+ Log .e (TAG , "Error in cropYuvToGrayscaleNative : " + e .getMessage ());
272+ return cropYuvToGrayscaleJava (image , cropLeft , cropTop , cropWidth , cropHeight );
278273 }
279274 }
280275
281276 /**
282- * Java fallback implementation for cropped YUV to RGB conversion.
283- * Uses integer math for reasonable performance .
277+ * Java fallback implementation for cropped Y plane to grayscale conversion.
278+ * Much simpler and faster than full YUV to RGB - just copies Y values .
284279 */
285280 @ Nullable
286- private Bitmap cropYuvToRgbJava (@ NonNull ImageProxy image , int cropLeft , int cropTop , int cropWidth , int cropHeight ) {
281+ private Bitmap cropYuvToGrayscaleJava (@ NonNull ImageProxy image , int cropLeft , int cropTop , int cropWidth , int cropHeight ) {
287282 try {
288283 ImageProxy .PlaneProxy [] planes = image .getPlanes ();
289-
290284 ByteBuffer yBuffer = planes [0 ].getBuffer ();
291- ByteBuffer uBuffer = planes [1 ].getBuffer ();
292- ByteBuffer vBuffer = planes [2 ].getBuffer ();
293-
294285 int yRowStride = planes [0 ].getRowStride ();
295- int uvRowStride = planes [1 ].getRowStride ();
296- int uvPixelStride = planes [1 ].getPixelStride ();
297286
298287 // Create output pixel array
299- int [] rgbPixels = new int [cropWidth * cropHeight ];
300-
301- // Precompute UV row base for crop region
302- int uvCropLeft = cropLeft / 2 ;
288+ int [] grayPixels = new int [cropWidth * cropHeight ];
303289 int pixelIndex = 0 ;
304290
305- // Convert only the cropped region using integer math (fixed-point, 10-bit precision )
291+ // Just copy Y values as grayscale (R=G=B=Y )
306292 for (int row = 0 ; row < cropHeight ; row ++) {
307293 int srcY = cropTop + row ;
308294 int yRowOffset = srcY * yRowStride + cropLeft ;
309- int uvRowOffset = (srcY >> 1 ) * uvRowStride ;
310295
311296 for (int col = 0 ; col < cropWidth ; col ++) {
312- // Get Y value
313- int y = (yBuffer .get (yRowOffset + col ) & 0xFF ) - 16 ;
314-
315- // Get U and V values (subsampled 2x2)
316- int uvIndex = uvRowOffset + ((uvCropLeft + (col >> 1 )) * uvPixelStride );
317- int u = (uBuffer .get (uvIndex ) & 0xFF ) - 128 ;
318- int v = (vBuffer .get (uvIndex ) & 0xFF ) - 128 ;
319-
320- // YUV to RGB conversion using integer math (fixed-point)
321- int y1192 = 1192 * y ;
322- int r = (y1192 + 1634 * v ) >> 10 ;
323- int g = (y1192 - 401 * u - 833 * v ) >> 10 ;
324- int b = (y1192 + 2066 * u ) >> 10 ;
325-
326- // Clamp to [0, 255]
327- r = r < 0 ? 0 : (r > 255 ? 255 : r );
328- g = g < 0 ? 0 : (g > 255 ? 255 : g );
329- b = b < 0 ? 0 : (b > 255 ? 255 : b );
330-
331- rgbPixels [pixelIndex ++] = 0xFF000000 | (r << 16 ) | (g << 8 ) | b ;
297+ int y = yBuffer .get (yRowOffset + col ) & 0xFF ;
298+ // Pack as ARGB with R=G=B=Y (grayscale)
299+ grayPixels [pixelIndex ++] = 0xFF000000 | (y << 16 ) | (y << 8 ) | y ;
332300 }
333301 }
334302
335303 // Create bitmap from pixel array
336304 Bitmap bitmap = Bitmap .createBitmap (cropWidth , cropHeight , Bitmap .Config .ARGB_8888 );
337- bitmap .setPixels (rgbPixels , 0 , cropWidth , 0 , 0 , cropWidth , cropHeight );
305+ bitmap .setPixels (grayPixels , 0 , cropWidth , 0 , 0 , cropWidth , cropHeight );
338306 return bitmap ;
339307
340308 } catch (Exception e ) {
341- Log .e (TAG , "Error in cropYuvToRgbJava : " + e .getMessage ());
309+ Log .e (TAG , "Error in cropYuvToGrayscaleJava : " + e .getMessage ());
342310 return null ;
343311 }
344312 }
0 commit comments