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;
	}

}








































