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: "idpath" 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 contents(Result r) throws Exception { Base64.Encoder b64 = Base64.getEncoder(); Set 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 md = r.getResultMetadata(); if (md != null && md.get(ResultMetadataType.BYTE_SEGMENTS) instanceof List) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (byte[] seg : (List) 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 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); } } }