import java.awt.*; import java.awt.image.*; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.*; import javax.imageio.ImageIO; public class SvenQR { // Anzahl der Rechtecke horizontal public static final int AMNT_RECT_X = 4; // Anzahl der Rechtecke vertikal public static final int AMNT_RECT_Y = 3; // Rahmendicke in Pixeln public static final int BORDER_WIDTH = 8; // Rechteckgröße in Pixeln public static final int RECT_WIDTH = 40; // Liest das angegebene Bild ein und wertet den QR-Code aus. public static int ReadQR(String fileName) throws IOException { // Quellbild BufferedImage img = ImageIO.read(new File(fileName)); // Informationen über den Rand und die Ausmaße BorderInformation info; // Gemessene Größe des QR-Codes Pair measuredSize; // Wert des QR-Codes int value; img = _IN_PrepareImage(img); info = _IN_GetBorderWidth(img); Pair imageSize = new Pair((int) img.getWidth(), (int) img.getHeight()); measuredSize = _IN_GetMeasuredSize(img, info, imageSize); // Durchschnittsgröße der Rechtecke int avgSquareWidth = (int) ((measuredSize.x - 4f * info.BorderWidth) / AMNT_RECT_X); int avgSquareHeight = (int) ((measuredSize.y - 4f * info.BorderWidth) / AMNT_RECT_Y); Pair avgSquareSize = new Pair(avgSquareWidth, avgSquareHeight); value = _IN_HeuristicDetermination(img, info, avgSquareSize); return value; } public static BufferedImage GenerateQR(int value) { boolean[][] bits; BufferedImage img; // Konvertiere die Zahl in eine Bit-Tabelle, die der QR-Code-Darstellung // entspricht bits = _OUT_GetBits(value); // Generiere Bild img = _OUT_DrawImage(bits); return img; } public static BufferedImage _IN_PrepareImage(BufferedImage img) { // Bildgröße auslesen int width = img.getWidth(); int height = img.getHeight(); // // = BILD ENTSÄTTIGEN UND KONTRAST SPREIZEN = // // Jedes Pixel durchgehen und in Graustufen umwandeln, // danach den Kontrast spreizen, sodass nur noch schwarze // und weiße Pixel im Bild sind, anders kann der Algorithmus // nicht arbeiten. // for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Aktuelles Pixel auslesen int value = img.getRGB(x, y); Color c1 = new Color(value); // Graustufe berechnen int grau = (c1.getBlue() + c1.getRed() + c1.getGreen()) / 3; // Kontrast spreizen if (grau >= 127) { grau = 255; } else { grau = 0; } // Pixel ersetzen Color c = new Color(grau, grau, grau); img.setRGB(x, y, c.getRGB()); } } /*try { File fx = new File("X:/ents.png"); // Speicherort ImageIO.write(img, "png", fx); // Bildformat } catch (IOException e) { System.out.println("Error: " + e); }*/ // // = ALLEINSTEHENDE PIXEL IM BILD ENTFERNEN = // // Wenn das Bild fleckig ist, entstehen einzelne Pixel, die // den Algorithmus zum auslesen stören können, also werden // diese entfernt. // for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { boolean weissAussendrum = true; // Rechts if (!(x < width - 1 && img.getRGB(x + 1, y) == -1)) weissAussendrum = false; // Links if (!(x > 0 && img.getRGB(x - 1, y) == -1)) weissAussendrum = false; // Drunter if (!(y < height - 1 && img.getRGB(x, y + 1) == -1)) weissAussendrum = false; // Drüber if (!(y > 0 && img.getRGB(x, y - 1) == -1)) weissAussendrum = false; // Links oben if (!(x > 0 && y > 0 && img.getRGB(x - 1, y - 1) == - 1)) weissAussendrum = false; // Links unten if (!(x > 0 && y < height - 1 && img.getRGB(x - 1, y + 1) == -1)) weissAussendrum = false; // Rechts oben if (!(x < width - 1 && y > 0 && img.getRGB(x + 1, y - 1) == -1)) weissAussendrum = false; // Rechts unten if (!(x < width - 1 && y < height - 1 && img.getRGB(x + 1, y + 1) == -1)) weissAussendrum = false; if (weissAussendrum) img.setRGB(x, y, -1 ); } } /*try { File fx = new File("X:/prep2.png"); // Speicherort ImageIO.write(img, "png", fx); // Bildformat } catch (IOException e) { System.out.println("Error: " + e); }*/ return img; } private static BorderInformation _IN_GetBorderWidth(BufferedImage img) { Color pixel; // Informationen über den Rahmen des QR-Codes BorderInformation info = null; // Breite des schwarzen Rahmens und Orientierungspunkte float borderWidth = -1f; Pair firstBlackPixel = new Pair(-1, -1); Pair firstWhitePixel; // // = SUCHE DAS ERSTE SCHWARZE PIXEL IN DER LINKEN OBEREN ECKE = // // Gehe diagonal durch das Bild auf der Suche nach schwarzen Pixeln while (!_IN_CompareColors(img.getRGB(firstBlackPixel.x + 1, firstBlackPixel.y + 1), Color.BLACK)) { firstBlackPixel.x++; firstBlackPixel.y++; } firstBlackPixel.x++; firstBlackPixel.y++; // Aufgrund von Ungenauigkeiten beim Drucken, gehen wir drei Pixel in den Rand rein firstBlackPixel.x += 2; System.out.println("Suche schwarzes Pixel."); System.out.println("X: " + firstBlackPixel.x + " Y: " + firstBlackPixel.y + "\n"); // Gehe so weit wie möglich nach oben while (firstBlackPixel.y > 0 && _IN_CompareColors(img.getRGB(firstBlackPixel.x, firstBlackPixel.y - 1), Color.BLACK)) firstBlackPixel.y--; System.out.println("Gehe so weit wie möglich nach oben"); System.out.println("X: " + firstBlackPixel.x + " Y: " + firstBlackPixel.y + "\n"); // Gehe so weit wie möglich nach links while (firstBlackPixel.x > 0 && _IN_CompareColors(img.getRGB(firstBlackPixel.x - 1, firstBlackPixel.y), Color.BLACK)) firstBlackPixel.x--; System.out.println("Gehe so weit wie möglich nach links"); System.out.println("X: " + firstBlackPixel.x + " Y: " + firstBlackPixel.y + "\n"); // // = SUCHE DAS ERSTE WEISSE PIXEL VOM INNEREN RAHMEN = // firstWhitePixel = new Pair(firstBlackPixel); // Gehe diagonal vom ersten schwarzen Pixel durch das Bild while (!_IN_CompareColors(img.getRGB(firstWhitePixel.x + 1, firstWhitePixel.y + 1), Color.WHITE)) { firstWhitePixel.x++; firstWhitePixel.y++; } firstWhitePixel.x++; firstWhitePixel.y++; System.out.println("Suche weißes Pixel"); System.out.println("X: " + firstWhitePixel.x + " Y: " + firstWhitePixel.y + "\n"); // Gehe so weit wie möglich nach oben while (_IN_CompareColors(img.getRGB(firstWhitePixel.x, firstWhitePixel.y - 1), Color.WHITE)) firstWhitePixel.y--; System.out.println("Gehe soweit wie möglich nach oben"); System.out.println("X: " + firstWhitePixel.x + " Y: " + firstWhitePixel.y + "\n"); // Gehe so weit wie möglich nach links while (_IN_CompareColors(img.getRGB(firstWhitePixel.x - 1, firstWhitePixel.y), Color.WHITE)) firstWhitePixel.x--; System.out.println("Gehe soweit wie möglich nach links"); System.out.println("X: " + firstWhitePixel.x + " Y: " + firstWhitePixel.y + "\n"); // Differenzen, also Rahmenstärke ausrechnen int deltaX = firstWhitePixel.x - firstBlackPixel.x; int deltaY = firstWhitePixel.y - firstBlackPixel.y; // Durchschnitt ermitteln borderWidth = (deltaX + deltaY) / 2f; System.out.println("Border width: " + borderWidth); info = new BorderInformation(borderWidth, firstBlackPixel); return info; } private static Pair _IN_GetMeasuredSize(BufferedImage img, BorderInformation borderInfo, Pair imageSize) { Pair measuredSize; Pair upperRightBlackPixel, lowerLeftBlackPixel; Color pixel; // // = MESSE DIE BREITE DES QR-CODES = // upperRightBlackPixel = new Pair(borderInfo.firstBlackPixel); // Weil das Bild nicht immer gerade ist, messen wir in der Mitte des // schwarzen Rahmens upperRightBlackPixel.y += borderInfo.BorderWidth / 2f; while (upperRightBlackPixel.x < imageSize.x - 1 && !_IN_CompareColors(img.getRGB(upperRightBlackPixel.x + 1, upperRightBlackPixel.y), Color.WHITE)) upperRightBlackPixel.x++; // // = MESSE DIE HÖHE DES QR-CODES = // lowerLeftBlackPixel = new Pair(borderInfo.firstBlackPixel); // Immer mittig messen, kann bei arg schiefen Bildern jedoch auch wieder // zu fehlerhaften Ergebnissen führen // In solch einem Fall können wir nichts tun :/ lowerLeftBlackPixel.x += borderInfo.BorderWidth / 2f; while (lowerLeftBlackPixel.y < imageSize.y - 1 && !_IN_CompareColors(img.getRGB(upperRightBlackPixel.x, upperRightBlackPixel.y + 1), Color.WHITE)) lowerLeftBlackPixel.y++; // Errechne die Größe des QR-Codes aus den Differenzen der Punkte measuredSize = new Pair(upperRightBlackPixel.x - borderInfo.firstBlackPixel.x, lowerLeftBlackPixel.y - borderInfo.firstBlackPixel.y); measuredSize.x++; measuredSize.y++; System.out.println("Gemessene Größe: " + measuredSize.x + " x " + measuredSize.y); return measuredSize; } private static int _IN_HeuristicDetermination(BufferedImage img, BorderInformation borderInfo, Pair avgSquareSize) { boolean[][] bits = new boolean[AMNT_RECT_X][AMNT_RECT_Y]; float halfWidth = avgSquareSize.x / 2f, halfHeight = avgSquareSize.y / 2f; int c_value; for (int x = 0; x < AMNT_RECT_X; x++) { for (int y = 0; y < AMNT_RECT_Y; y++) { int _x = (int) (2 * borderInfo.BorderWidth); _x += (int) (x * avgSquareSize.x); _x += (int) halfWidth; _x += borderInfo.firstBlackPixel.x; int _y = (int) (2 * borderInfo.BorderWidth); _y += (int) (y * avgSquareSize.y); _y += (int) halfHeight; _y += borderInfo.firstBlackPixel.y; c_value = img.getRGB(_x, _y); System.out.println("X: " + _x + " Y: " + _y + " V: " + (_IN_CompareColors(c_value, Color.WHITE) ? false : true)); if (_IN_CompareColors(c_value, Color.WHITE)) bits[x][y] = false; else if (_IN_CompareColors(c_value, Color.BLACK)) bits[x][y] = true; } } int value = 0; if (bits[0][0]) value += 1; if (bits[1][0]) value += 2; if (bits[2][0]) value += 4; if (bits[3][0]) value += 8; if (bits[0][1]) value += 16; if (bits[1][1]) value += 32; if (bits[2][1]) value += 64; if (bits[3][1]) value += 128; if (bits[0][2]) value += 256; if (bits[1][2]) value += 512; if (bits[2][2]) value += 1024; if (bits[3][2]) value += 2048; return value; } private static boolean _IN_CompareColors(int rgb, Color template) { Color c = new Color(rgb); if (c.getRed() != template.getRed()) return false; if (c.getGreen() != template.getGreen()) return false; if (c.getBlue() != template.getBlue()) return false; return true; } private static boolean[][] _OUT_GetBits(int value) { // Die Bits in QR-Code-Darstellung boolean[][] bits = new boolean[AMNT_RECT_X][AMNT_RECT_Y]; // Die Bits als Binärstring String bitsAsString = Integer.toBinaryString(value); // Anzahl an führenden Nullen für 12-Bit: int amntLeadingZeros = 12 - bitsAsString.length(); // Stelle führende Nullen voran for (int i = 0; i < amntLeadingZeros; i++) bitsAsString = "0" + bitsAsString; bits[0][0] = bitsAsString.charAt(11) == '0' ? false : true; bits[1][0] = bitsAsString.charAt(10) == '0' ? false : true; bits[2][0] = bitsAsString.charAt(9) == '0' ? false : true; bits[3][0] = bitsAsString.charAt(8) == '0' ? false : true; bits[0][1] = bitsAsString.charAt(7) == '0' ? false : true; bits[1][1] = bitsAsString.charAt(6) == '0' ? false : true; bits[2][1] = bitsAsString.charAt(5) == '0' ? false : true; bits[3][1] = bitsAsString.charAt(4) == '0' ? false : true; bits[0][2] = bitsAsString.charAt(3) == '0' ? false : true; bits[1][2] = bitsAsString.charAt(2) == '0' ? false : true; bits[2][2] = bitsAsString.charAt(1) == '0' ? false : true; bits[3][2] = bitsAsString.charAt(0) == '0' ? false : true; return bits; } private static BufferedImage _OUT_DrawImage(boolean[][] bits) { // Bildgröße Pair imageSize; // Bild BufferedImage img; // Grafikobjekt zum zeichnen Graphics2D g; // Bildbreite und -höhe berechnen // Bei 72 dpi entspricht 1mm 3px, wir nehmen 2px imageSize = new Pair(BORDER_WIDTH * 4 + AMNT_RECT_X * RECT_WIDTH, BORDER_WIDTH * 4 + AMNT_RECT_Y * RECT_WIDTH); // Bild erstellen und Grafikobjekt erstellen img = new BufferedImage(imageSize.x, imageSize.y, BufferedImage.TYPE_INT_ARGB); g = img.createGraphics(); // // = QR-CODE-ZEICHEN = // // Äußeren Rahmen zeichnen g.setColor(Color.BLACK); g.fillRect(0, 0, imageSize.x, imageSize.y); // Inneren Rahmen zeichnen g.setColor(Color.WHITE); g.fillRect(BORDER_WIDTH, BORDER_WIDTH, imageSize.x - 2 * BORDER_WIDTH, imageSize.y - 2 * BORDER_WIDTH); g.setColor(Color.BLACK); // Rechtecke zeichnen for (int x = 0; x < AMNT_RECT_X; x++) { for (int y = 0; y < AMNT_RECT_Y; y++) { int _x = BORDER_WIDTH * 2 + x * RECT_WIDTH; int _y = BORDER_WIDTH * 2 + y * RECT_WIDTH; if (!bits[x][y]) continue; g.fillRect(_x, _y, RECT_WIDTH, RECT_WIDTH); } } // Rechte untere Ecke als Orientierungspunkt entfernen g.setColor(Color.WHITE); g.fillRect(imageSize.x - BORDER_WIDTH, imageSize.y - BORDER_WIDTH, BORDER_WIDTH, BORDER_WIDTH); return img; } }