485 lines
14 KiB
Java
485 lines
14 KiB
Java
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;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|