Skip to content

Commit 018b66c

Browse files
committed
Now the cropping will return a grayscale image to the processor to speed up cropping and processing
1 parent 45bdcd6 commit 018b66c

File tree

3 files changed

+126
-60
lines changed

3 files changed

+126
-60
lines changed

AI_MultiBarcodes_Capture/src/main/cpp/yuv_processor.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,76 @@ Java_com_zebra_ai_1multibarcodes_1capture_barcodedecoder_NativeYuvProcessor_crop
188188
return JNI_TRUE;
189189
}
190190

191+
/**
192+
* Ultra-fast grayscale-only cropping.
193+
* Only reads the Y plane (luminance) and writes it as grayscale to the bitmap.
194+
* This is significantly faster than full YUV to RGB conversion since:
195+
* 1. No U/V plane processing
196+
* 2. No color conversion math
197+
* 3. Simple memory copy with grayscale expansion
198+
*
199+
* The Y plane IS the grayscale image - we just need to replicate it to R, G, B channels.
200+
*/
201+
JNIEXPORT jboolean JNICALL
202+
Java_com_zebra_ai_1multibarcodes_1capture_barcodedecoder_NativeYuvProcessor_cropYToGrayscaleBitmapNative(
203+
JNIEnv *env,
204+
jclass clazz,
205+
jobject yBuffer,
206+
jint yRowStride,
207+
jint cropLeft,
208+
jint cropTop,
209+
jint cropWidth,
210+
jint cropHeight,
211+
jobject bitmap) {
212+
213+
// Get direct buffer pointer for Y plane only
214+
auto *yData = static_cast<uint8_t *>(env->GetDirectBufferAddress(yBuffer));
215+
216+
if (yData == nullptr) {
217+
LOGE("Failed to get Y buffer address");
218+
return JNI_FALSE;
219+
}
220+
221+
// Lock the bitmap for writing
222+
AndroidBitmapInfo bitmapInfo;
223+
if (AndroidBitmap_getInfo(env, bitmap, &bitmapInfo) != ANDROID_BITMAP_RESULT_SUCCESS) {
224+
LOGE("Failed to get bitmap info");
225+
return JNI_FALSE;
226+
}
227+
228+
if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
229+
LOGE("Bitmap format is not RGBA_8888");
230+
return JNI_FALSE;
231+
}
232+
233+
void *bitmapPixels;
234+
if (AndroidBitmap_lockPixels(env, bitmap, &bitmapPixels) != ANDROID_BITMAP_RESULT_SUCCESS) {
235+
LOGE("Failed to lock bitmap pixels");
236+
return JNI_FALSE;
237+
}
238+
239+
auto *outPixels = static_cast<uint32_t *>(bitmapPixels);
240+
const int bitmapStride = bitmapInfo.stride / 4; // stride in pixels (4 bytes per pixel)
241+
242+
// Process each row - just copy Y values as grayscale
243+
for (int row = 0; row < cropHeight; row++) {
244+
const int srcY = cropTop + row;
245+
const uint8_t *yRowPtr = yData + srcY * yRowStride + cropLeft;
246+
uint32_t *outRowPtr = outPixels + row * bitmapStride;
247+
248+
// Process each column - Y value becomes R=G=B
249+
for (int col = 0; col < cropWidth; col++) {
250+
const uint8_t y = yRowPtr[col];
251+
// Pack as ARGB with R=G=B=Y (grayscale)
252+
// Android RGBA_8888 is stored as ABGR in memory
253+
outRowPtr[col] = 0xFF000000 | (y << 16) | (y << 8) | y;
254+
}
255+
}
256+
257+
// Unlock the bitmap
258+
AndroidBitmap_unlockPixels(env, bitmap);
259+
260+
return JNI_TRUE;
261+
}
262+
191263
} // extern "C"

AI_MultiBarcodes_Capture/src/main/java/com/zebra/ai_multibarcodes_capture/barcodedecoder/BarcodeAnalyzer.java

Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import android.graphics.Bitmap;
55
import android.graphics.ImageFormat;
66
import android.graphics.Rect;
7-
import android.graphics.YuvImage;
8-
import android.graphics.BitmapFactory;
97
import android.util.Log;
108

119
import 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
}

AI_MultiBarcodes_Capture/src/main/java/com/zebra/ai_multibarcodes_capture/barcodedecoder/NativeYuvProcessor.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,30 @@ public static native boolean cropYuvToBitmapNative(
9494
int cropHeight,
9595
Bitmap bitmap
9696
);
97+
98+
/**
99+
* Ultra-fast native method to crop Y plane only and create a grayscale Bitmap.
100+
* This is significantly faster than full YUV to RGB conversion because:
101+
* - Only reads the Y plane (no U/V processing)
102+
* - No color conversion math required
103+
* - Simple memory copy with grayscale expansion
104+
*
105+
* @param yBuffer Y plane direct ByteBuffer
106+
* @param yRowStride Row stride for Y plane
107+
* @param cropLeft Left coordinate of crop region
108+
* @param cropTop Top coordinate of crop region
109+
* @param cropWidth Width of crop region
110+
* @param cropHeight Height of crop region
111+
* @param bitmap Pre-allocated ARGB_8888 bitmap of size cropWidth x cropHeight
112+
* @return true if successful, false otherwise
113+
*/
114+
public static native boolean cropYToGrayscaleBitmapNative(
115+
ByteBuffer yBuffer,
116+
int yRowStride,
117+
int cropLeft,
118+
int cropTop,
119+
int cropWidth,
120+
int cropHeight,
121+
Bitmap bitmap
122+
);
97123
}

0 commit comments

Comments
 (0)