I want to scan both classic and inverted (light dark) QR codes. What to do?
I am using the legacy camera class. I am doing the processing in the onPreviewFrame (byte [], Camera Camera) method. The Zbar does not have the ability to run an "inverted" scan. I realized I could set a negative color effect on my android camera and it works fine for scanning an inverted QR code, but it stops detecting normal ones.
I'm trying to find a way to cheat, like having 2 instances of the same camera, with negative effect, and one without and only showing ineffective, but it won't let me.
The following code is called every time a frame is displayed.
private Camera.PreviewCallback previewCb = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
Image barcode = new Image(size.width, size.height, "Y800");
barcode.setData(data);
int result = scanner.scanImage(barcode);
And this is how I establish the negative effect I'm talking about.
Camera.Parameters params = mCamera.getParameters();
params.setColorEffect(Camera.Parameters.EFFECT_NEGATIVE);
mCamera.setParameters(params);
Another way is to process the YUV byte array that I get from the preview preview callback and apply the negative effect, but I'm not sure how to do this without heavy conversions.
Any ideas?
source to share
I managed to get it to work converting YUV data back and forth after weeks of no response or misleading. This ZBarScannerView.java
is how mine looks like onPreviewFrame(..)
:
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
int width = size.width;
int height = size.height;
switcher = !switcher;
if(DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
int tmp = width;
width = height;
height = tmp;
data = rotatedData;
}
Image barcode = new Image(width, height, "Y800");
if (switcher) {
int[] pixels = applyGrayScale(data,width,height);
Bitmap bm = Bitmap.createBitmap(pixels,width,height, Bitmap.Config.ARGB_8888);
bm = MyUtils.createInvertedBitmap(bm, width, height);
pixels = new int[width*height];
bm.getPixels(pixels, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight());
encodeYUV420SP(data, pixels, bm.getWidth(), bm.getHeight());
}
barcode.setData(data);
int result = mScanner.scanImage(barcode);
if (result != 0) {
stopCamera();
if(mResultHandler != null) {
SymbolSet syms = mScanner.getResults();
Result rawResult = new Result();
for (Symbol sym : syms) {
String symData = sym.getData();
if (!TextUtils.isEmpty(symData)) {
rawResult.setContents(symData);
rawResult.setBarcodeFormat(BarcodeFormat.getFormatById(sym.getType()));
break;
}
}
mResultHandler.handleResult(rawResult);
}
} else {
camera.setOneShotPreviewCallback(this);
}
}
also add this to the class (derived from here ):
void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
final int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize;
int a, R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
R = (argb[index] & 0xff0000) >> 16;
G = (argb[index] & 0xff00) >> 8;
B = (argb[index] & 0xff) >> 0;
// well known RGB to YUV algorithm
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
// NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
// meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other
// pixel AND every other scanline.
yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0) {
yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
}
index++;
}
}
}
this will take care of converting the int array back to byte array after inversion.
Also I am using these code snippets I got somewhere on stackExchange (it took me too long to remember where) with small changes inside the utility class named MyUtils.java
:
public class MyUtils {
public static Integer sizeWidth;
public static Integer sizeHeight;
public static Bitmap createInvertedBitmap(Bitmap src, Integer width, Integer height) {
sizeWidth = width;
sizeHeight = height;
ColorMatrix colorMatrix_Inverted =
new ColorMatrix(new float[] {
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0});
ColorFilter ColorFilter_Sepia = new ColorMatrixColorFilter(
colorMatrix_Inverted);
Bitmap bitmap = Bitmap.createBitmap(sizeWidth, sizeHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColorFilter(ColorFilter_Sepia);
canvas.drawBitmap(src, 0, 0, paint);
return bitmap;
}
public static int[] applyGrayScale(byte [] data, int width, int height) {
int p;
int size = width*height;
int[] pixels = new int[size];
for(int i = 0; i < size; i++) {
p = data[i] & 0xFF;
pixels[i] = 0xff000000 | p<<16 | p<<8 | p;
}
return pixels;
}
}
Finally, add Boolean switcher = true
to the class within the ZBarScannerView class. The variable "switch" must switch between checking for inverted or non-inverted codes.
Please ask if you have any problem, I have struggled with this for so long, I feel like ya and the answer may need to be edited for readability.
source to share
I have been trying to do the same for a long time. And they still haven't been completely successful. I can't say if this answers the question or not. But this code is put in mBarcodeScannerView
and calls it with autoInvert()
in mZBarScannerView
, while annoying browsing will allow you to scan both normal and inverted QR codes. Basically it just switches between camera effects so often.
public void autoInvert() {
// Don't think this line is needed.
// autoInvertOn = !autoInvertOn;
Runnable runAutoInvert = new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Camera.Parameters parameters = mCamera.getParameters();
if (parameters.getSupportedColorEffects().contains(Camera.Parameters.EFFECT_NEGATIVE)) {
while (mCamera != null && autoInvertOn) {
try {
parameters = mCamera.getParameters();
parameters.setColorEffect(Camera.Parameters.EFFECT_NEGATIVE);
mCamera.setParameters(parameters);
Thread.sleep(800);
parameters = mCamera.getParameters();
parameters.setColorEffect(Camera.Parameters.EFFECT_NONE);
mCamera.setParameters(parameters);
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e1) {
mCamera = null;
}
}
}
}
};
Thread autoInvertThread = new Thread(runAutoInvert);
autoInvertThread.start();
}
To avoid damaging my eyes in dark conditions, I also added a black, somewhat transparent look over the preview when it flipped. For me this is the solution while I am working on finding the best one.
source to share