Android platform generates two dimensional code and realizes scanning and identification function

  • 2021-07-24 11:43:33
  • OfStack

Past lives of 1.2 dimensional code

"2D barcode/2D code (2-dimensional bar code) records data symbol information with black and white patterns distributed in the plane (2D direction) according to a certain geometric pattern; The concepts of "0" and "1" bit stream, which constitute the internal logic basis of computer, are skillfully used in coding, Several geometric shapes corresponding to binary system are used to represent the numerical information of characters, which are automatically read by image input equipment or photoelectric scanning equipment to realize automatic information processing. It has one common feature of bar code technology: each code system has its own specific character set; Each character occupies a certain width; It has 1 fixed calibration function, etc. At the same time, it also has the function of automatically identifying the information of different lines and processing the rotation change points of graphics. [1] "

The above is the explanation of Baidu Encyclopedia. Since there are two-dimensional codes, there must be one-dimensional codes.

1-D code. The most common is food & Bar code at the back of the book.

Barcode originated in the 1940s, and then was invented in UPC code in 1970, and began to be widely used in food packaging.

Specific introduction can see Baidu Encyclopedia 1D code.

In fact, 2D codes are essentially similar to 1D codes, just like 1D arrays and 2D arrays 1.

java Support Library for 2.2 Dimensional Codes

In order to make it easy for java or android to inherit the function of barcode, google has developed a library of zxing:

https://github.com/zxing/zxing

3. Generate 2-D code


public class EncodeThread {
public static void encode(final String url, final int width, final int height, final EncodeResult result) {
if (result == null) {
return;
}
if (TextUtils.isEmpty(url)) {
result.onEncodeResult(null);
return;
}
new Thread() {
@Override
public void run() {
try {
MultiFormatWriter writer = new MultiFormatWriter();
Hashtable<EncodeHintType, String> hints = new Hashtable<>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
BitMatrix bitMatrix = writer.encode(url, BarcodeFormat.QR_CODE, width, height, hints);
Bitmap bitmap = parseBitMatrix(bitMatrix);
result.onEncodeResult(bitmap);
return;
} catch (WriterException e) {
e.printStackTrace();
}
result.onEncodeResult(null);
}
}.start();
}
/**
*  Generate 2 Dimension code content <br>
*
* @param matrix
* @return
*/
public static Bitmap parseBitMatrix(BitMatrix matrix) {
final int QR_WIDTH = matrix.getWidth();
final int QR_HEIGHT = matrix.getHeight();
int[] pixels = new int[QR_WIDTH * QR_HEIGHT];
//this we using qrcode algorithm
for (int y = 0; y < QR_HEIGHT; y++) {
for (int x = 0; x < QR_WIDTH; x++) {
if (matrix.get(x, y)) {
pixels[y * QR_WIDTH + x] = 0xff000000;
} else {
pixels[y * QR_WIDTH + x] = 0xffffffff;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(QR_WIDTH, QR_HEIGHT, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, QR_WIDTH, 0, 0, QR_WIDTH, QR_HEIGHT);
return bitmap;
}
public interface EncodeResult {
void onEncodeResult(Bitmap bitmap);
}
} 

zxing supports many barcode formats: we use the QR_CODE code here. That is, the 2D code in our common WeChat.

Let's analyze this code first:

MultiFormatWriter writer = new MultiFormatWriter();

This is a tool class, and all the supported write are written in it.


public BitMatrix encode(String contents,
BarcodeFormat format,
int width, int height,
Map<EncodeHintType,?> hints) throws WriterException {
Writer writer;
switch (format) {
case EAN_8:
writer = new EAN8Writer();
break;
case UPC_E:
writer = new UPCEWriter();
break;
case EAN_13:
writer = new EAN13Writer();
break;
case UPC_A:
writer = new UPCAWriter();
break;
case QR_CODE:
writer = new QRCodeWriter();
break;
case CODE_39:
writer = new Code39Writer();
break;
case CODE_93:
writer = new Code93Writer();
break;
case CODE_128:
writer = new Code128Writer();
break;
case ITF:
writer = new ITFWriter();
break;
case PDF_417:
writer = new PDF417Writer();
break;
case CODABAR:
writer = new CodaBarWriter();
break;
case DATA_MATRIX:
writer = new DataMatrixWriter();
break;
case AZTEC:
writer = new AztecWriter();
break;
default:
throw new IllegalArgumentException("No encoder available for format " + format);
}
return writer.encode(contents, format, width, height, hints);
} 

This is the latest official support format, specifically see the introduction of jar support in the format.

For the results with bitmatrix, set each dot white or black by touching an algorithm.

Finally, create a picture with 2-dimensional code.

4. Identify 2-D codes

How to identify 2D code from one picture:


public class ReDecodeThread {
public static void encode(final Bitmap bitmap, final ReDecodeThreadResult listener) {
if (listener == null) {
return;
}
if (bitmap == null) {
listener.onReDecodeResult(null);
return;
}
new Thread() {
@Override
public void run() {
try {
MultiFormatReader multiFormatReader = new MultiFormatReader();
BitmapLuminanceSource source = new BitmapLuminanceSource(bitmap);
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
Result result1 = multiFormatReader.decode(bitmap1);
listener.onReDecodeResult(result1.getText());
return;
} catch (NotFoundException e) {
e.printStackTrace();
}
listener.onReDecodeResult(null);
}
}.start();
}
public interface ReDecodeThreadResult {
void onReDecodeResult(String url);
}
} 

The process is also very simple, using MultiFormatReader to analyze pictures, there is no need to lack the barcode format of pictures.

If you analyze the source code, you will use reader in each format in turn until you find the right one.

Of course, we can convert Bitmap into Bitmatrix, and then analyze it.


public final class BitmapLuminanceSource extends LuminanceSource{
private final byte[] luminances;
public BitmapLuminanceSource(String path) throws FileNotFoundException {
this(loadBitmap(path));
}
public BitmapLuminanceSource(Bitmap bitmap) {
super(bitmap.getWidth(), bitmap.getHeight());
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
// In order to measure pure decoding speed, we convert the entire image
// to a greyscale array
// up front, which is the same as the Y channel of the
// YUVLuminanceSource in the real app.
luminances = new byte[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
int pixel = pixels[offset + x];
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = pixel & 0xff;
if (r == g && g == b) {
// Image is already greyscale, so pick any channel.
luminances[offset + x] = (byte) r;
} else {
// Calculate luminance cheaply, favoring green.
luminances[offset + x] = (byte) ((r + g + g + b) >> 2);
}
}
}
}
@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];
}
System.arraycopy(luminances, y * width, row, 0, width);
return row;
}
// Since this class does not support cropping, the underlying byte array
// already contains
// exactly what the caller is asking for, so give it to them without a copy.
@Override
public byte[] getMatrix() {
return luminances;
}
private static Bitmap loadBitmap(String path) throws FileNotFoundException {
Bitmap bitmap = BitmapFactory.decodeFile(path);
if (bitmap == null) {
throw new FileNotFoundException("Couldn't open " + path);
}
return bitmap;
}
} 

5. Scan 2-D code

Scanning 2D code is actually only one step more than the above, that is, directly converting what camera obtains, and then identifying it.


public void requestPreviewFrame(Handler handler, int message) {
if (camera != null && previewing) {
previewCallback.setHandler(handler, message);
if (useOneShotPreviewCallback) {
camera.setOneShotPreviewCallback(previewCallback);
} else {
camera.setPreviewCallback(previewCallback);
}
}
} 

First, put the data previewed by camera into previewCallback.


final class PreviewCallback implements Camera.PreviewCallback 

public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
if (previewHandler != null) {
Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
message.sendToTarget();
previewHandler = null;
} else {
Log.d(TAG, "Got preview callback, but no handler for it");
}
} 

As you can see, the preview data data is passed back, and then handler is passed out.

Where data is received:


@Override
public void handleMessage(Message message) {
switch (message.what) {
case R.id.decode:
//Log.d(TAG, "Got decode message");
decode((byte[]) message.obj, message.arg1, message.arg2);
break;
case R.id.quit:
Looper.myLooper().quit();
break;
}
} 

Then decode data


private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
Result rawResult = null;
//modify here
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; // Here we are swapping, that's the difference to #11
width = height;
height = tmp;
PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
if (rawResult != null) {
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
message.setData(bundle);
//Log.d(TAG, "Sending decode succeeded message...");
message.sendToTarget();
} else {
Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);
message.sendToTarget();
}
} 

When the picture on camera is converted into BinaryBitmap, the rest of the things are more directly identified from the picture.

PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));


Related articles: