qr-scan-difficulty-check / server /zxing /ZxingServer.java
devmandan's picture
QR Scan Difficulty Check
b43c3a8 verified
Raw History Blame Contribute Delete
5.36 kB
import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.common.HybridBinarizer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* ZXing core 3.5.2 as Android's Settings app calls it (SettingsLib QrCamera): QR only, HybridBinarizer,
* decodeWithState on the frame, then on the inverted frame; no TRY_HARDER.
* stdin: "id<TAB>path" lines, each an 8-bit gray PNG. stdout: one JSON line per input, flushed, with every byte
* form of the content ZXing returns (its text as UTF-8 and, where it fits, ISO-8859-1, and its raw byte
* segments), base64-encoded: a read counts when one of them is the picture's content.
*/
public class ZxingServer {
/** A plain gray plane, as SettingsLib's QrYuvLuminanceSource hands ZXing the camera's Y plane. */
static final class GraySource extends LuminanceSource {
private final byte[] data;
GraySource(byte[] data, int width, int height) {
super(width, height);
this.data = data;
}
@Override
public byte[] getRow(int y, byte[] row) {
int w = getWidth();
if (row == null || row.length < w) row = new byte[w];
System.arraycopy(data, y * w, row, 0, w);
return row;
}
@Override
public byte[] getMatrix() {
return data;
}
}
static Result decode(MultiFormatReader reader, LuminanceSource source) {
try {
return reader.decodeWithState(new BinaryBitmap(new HybridBinarizer(source)));
} catch (ReaderException e) {
return null;
} finally {
reader.reset();
}
}
@SuppressWarnings("unchecked")
static Set<String> contents(Result r) throws Exception {
Base64.Encoder b64 = Base64.getEncoder();
Set<String> out = new LinkedHashSet<>();
String text = r.getText() == null ? "" : r.getText();
out.add(b64.encodeToString(text.getBytes(StandardCharsets.UTF_8)));
if (StandardCharsets.ISO_8859_1.newEncoder().canEncode(text)) {
out.add(b64.encodeToString(text.getBytes(StandardCharsets.ISO_8859_1)));
}
Map<ResultMetadataType, Object> md = r.getResultMetadata();
if (md != null && md.get(ResultMetadataType.BYTE_SEGMENTS) instanceof List) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (byte[] seg : (List<byte[]>) md.get(ResultMetadataType.BYTE_SEGMENTS)) bos.write(seg);
if (bos.size() > 0) out.add(b64.encodeToString(bos.toByteArray()));
}
return out;
}
public static void main(String[] args) throws Exception {
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE));
MultiFormatReader reader = new MultiFormatReader();
reader.setHints(hints);
PrintStream out = new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8);
BufferedReader in = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
String line;
while ((line = in.readLine()) != null) {
String[] parts = line.split("\t", 2);
if (parts.length < 2 || !parts[0].matches("[0-9]+")) continue;
String id = parts[0];
BufferedImage img;
try {
img = ImageIO.read(new File(parts[1]));
} catch (Exception e) {
img = null;
}
if (img == null || img.getType() != BufferedImage.TYPE_BYTE_GRAY) {
out.println("{\"id\":" + id + ",\"error\":\"not an 8-bit gray PNG\"}");
continue;
}
int w = img.getWidth();
int h = img.getHeight();
byte[] y = (byte[]) img.getRaster().getDataElements(0, 0, w, h, null);
LuminanceSource src = new GraySource(y, w, h);
Result r = decode(reader, src);
boolean inverted = false;
if (r == null) { // QrCamera: "Check color inversion QR code"
r = decode(reader, src.invert());
inverted = r != null;
}
StringBuilder sb = new StringBuilder();
sb.append("{\"id\":").append(id).append(",\"inverted\":").append(inverted).append(",\"contents\":[");
if (r != null) {
int i = 0;
for (String c : contents(r)) sb.append(i++ > 0 ? "," : "").append('"').append(c).append('"');
}
sb.append("]}");
out.println(sb);
}
}
}