CameraX ZXing YUV rowStride pixelStride crop QR scanner
Learn how row stride, pixel stride, crop bounds, and rotation interact when feeding CameraX YUV frames into ZXing, and ensure proper proxy lifecycle management.
Table of Contents5 sections

When you integrate CameraX with the ZXing decoding library for real-time barcode scanning, a naive byte-array copy often fails because it assumes that image planes are tightly packed. Hardware camera buffers frequently include padding bytes at the end of each row. Ignoring these alignment offsets corrupts the luminance matrix and causes decoding failures or misaligned bounding boxes.
Quick solution: Read each row explicitly according to its row stride instead of performing a single bulk copy, and always wrap the resulting byte array in a custom luminance source before calling the decoder. For broader implementation context on real-time computer vision pipelines, you can also review how to configure Camerax Ml Kit Real Time Image safely.
public class PlanarYUVLuminanceSource extends LuminanceSource {
private final byte[] yuvData;
private final int dataWidth;
private final int dataHeight;
private final int left;
private final int top;
public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, int width, int height) {
super(width, height);
this.yuvData = yuvData;
this.dataWidth = dataWidth;
this.dataHeight = dataHeight;
this.left = left;
this.top = top;
}
@Override
public byte[] getRow(int y, byte[] row) {
if (y < 0 || y >= getHeight()) {
throw new IllegalArgumentException("Requested row is outside the image: " + y);
}
int width = getWidth();
if (row == null || row.length < width) {
row = new byte[width];
}
int offset = (y + top) * dataWidth + left;
System.arraycopy(yuvData, offset, row, 0, width);
return row;
}
@Override
public byte[] getMatrix() {
int width = getWidth();
int height = getHeight();
byte[] resultat = new byte[width * height];
int offset = top * dataWidth + left;
if (width == dataWidth) {
System.arraycopy(yuvData, offset, resultat, 0, width * height);
return resultat;
}
for (int y = 0; y < height; y++) {
int sourceOffset = (y + top) * dataWidth + left;
System.arraycopy(yuvData, sourceOffset, resultat, y * width, width);
}
return resultat;
}
}
How row stride and crop bounds affect luminance extraction
According to the official Android documentation on CameraX image analysis, frames produced by the camera subsystem are delivered as an ImageProxy containing multiple planes. Each plane abstracts a specific color channel. For YUV_420_888 format, the Y plane holds the luminance data. Developers must inspect the row stride because hardware constraints often require rows to align with specific byte boundaries, such as 32 or 64 bytes.
When you query the Android Image.Plane stride contract, you will find that getRowStride() frequently exceeds the visible image width. If your application copies the entire plane buffer into a flat array without skipping these padding bytes, every subsequent row will shift horizontally. This misalignment shears the image data and renders standard barcode patterns unrecognizable to decoding algorithms.
Furthermore, crop bounds interact directly with these stride offsets. When you apply a bounding rectangle to isolate a region of interest in the viewfinder, your code must calculate offsets using the stride width rather than the visible width. Neglecting the stride when calculating starting indices means your crop window points to arbitrary memory locations outside your intended target.
When is rotation applied to decoding coordinates?
Image rotation introduces another layer of complexity during coordinate mapping. Camera sensors capture frames in a fixed hardware orientation, whereas the UI display rotates dynamically based on device rotation sensors. When the analysis executor receives an ImageProxy, the rotation degrees indicate how much the frame must be rotated to match the current UI orientation.
Decoding libraries such as ZXing expect upright matrices. If you rotate the image buffer directly in software before extraction, you incur heavy CPU penalties. The more efficient approach is to let the luminance source handle coordinate transformations on demand or to rotate the bounding box coordinates post-decode. Developers must ensure that rotation degrees are factored into any coordinate scaling calculations when mapping detected barcode points back to the preview view.
Who closes the proxy on every failure path?
Resource leaks are a primary source of camera freezes and application crashes during continuous image analysis. An ImageProxy holds a reference to an underlying native buffer that must be closed explicitly to allow the camera pipeline to produce the next frame. If your analysis code throws an exception during luminance extraction or barcode scanning, the proxy can remain unreleased.
To prevent camera stalls, wrap your processing logic in a try-finally block. The analysis callback must always invoke imageProxy.close() in the finally block regardless of whether decoding succeeded, threw an exception, or found no barcodes. Never close the underlying Media.Image directly outside of the proxy wrapper lifecycle contract provided by CameraX.
@Override
public void analyze(@NonNull ImageProxy imageProxy) {
try {
ByteBuffer buffer = imageProxy.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
data,
imageProxy.getPlanes()[0].getRowStride(),
imageProxy.getHeight(),
0,
0,
imageProxy.getWidth(),
imageProxy.getHeight()
);
// Pass source to ZXing decoder implementation
// Reference: https://github.com/zxing/zxing/blob/master/core/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java
processDecoding(source);
} catch (Exception e) {
handleDecodingFailure(e);
} finally {
imageProxy.close();
}
}
Verification and execution checklist
- Inspect the plane row stride using
imageProxy.getPlanes()[0].getRowStride()and verify that it matches or exceeds the visible frame width. - Confirm that synthetic padded test frames successfully decode when row stride offsets are explicitly skipped during luminance extraction.
- Test rotation handling by rotating the device into landscape and portrait modes to verify that coordinate mapping remains accurate.
- Verify that exceptions thrown during a failed decode operation still trigger the
finallyblock to release theImageProxyinstance without stalling the camera stream.
Continue Exploring
You Might Also Like

Android elapsedRealtime and Monotonic Deadlines
Learn how to use Android elapsedRealtime and Kotlin cancellation to reject late coroutine responses during startup health checks, preventing stale data from overwriting UI state.

Fix Firebase Variant CI Configuration
Learn how the Google Services Gradle plugin matches build variants to configuration files, where variant-specific JSON files live, and how to verify that your CI pipeline exercises the intended Firebase project.

Fix Android WebView onShowFileChooser Overlapping Callback
Learn how to correctly manage Android WebView onShowFileChooser callbacks, handle overlapping chooser requests, complete cancellations, and process URI results safely.