3

I'm using the deprecated Camera class. I'm doing the processing in the onPreviewFrame(byte[] data, Camera camera) method. Zbar scanner doesn't have an option to trigger "try inverted" scanning. I figured out I can set negative color effect on my android camera and it works great for scanning inverted QR code, but it stops detecting normal ones.

I'm trying to find a way to trick, like having 2 instances of the same camera, one with negative effect applied and one without and only display no-effect one, but it won't let me.

The code below is called each 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 set the negative effect I'm talking about.

Camera.Parameters params = mCamera.getParameters();
params.setColorEffect(Camera.Parameters.EFFECT_NEGATIVE);
mCamera.setParameters(params);

Another way would be to process the YUV byte array I get from preview callback myself and apply negative effect but I'm not sure how to do it without heavy conversions.

Any ideas ?

Someone
  • 216
  • 3
  • 8

2 Answers2

2

I managed to get it working converting the YUV data back and forth, after weeks of not finding any answer/getting misled. In ZBarScannerView.java this is how my onPreviewFrame(..) looks like:

@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 (gotten 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 takes care of converting the int array back into a byte array after the inversion.

Also I am using these code snippets I got somewhere on stackExchange (it's been too long for me to remember where) with a few small changes, inside an 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 into the class, within the ZBarScannerView class scope. The variable "switcher" is there to switch between checking for inverted or non-inverted codes.

Please ask if you have any trouble, I struggled with this for so long, I feel ya, and answer might need some editing for readability.

Community
  • 1
  • 1
Versa
  • 605
  • 7
  • 11
1

I have been trying to do the same for a long time. And have still not been completely successful. Can't tell if this answers the question or not. But this code put in mBarcodeScannerView, and calling it with autoInvert() in mZBarScannerView, while being annoying to look at, will allow you to scan both normal and inverted QR-codes. Basically it just switches between camera effects every 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 keep it from hurting my eyes in dark environments, I also put in a black, somewhat transparent view on top of the preview when it goes invert. For me, this is the solution while I work on finding a better one.

Versa
  • 605
  • 7
  • 11
  • Look at my other answer, which answers the question and is confirmed working, leaving this up as it might solve someones related question. – Versa May 21 '15 at 10:53