[LIB-9] Add chesshog-qrcode module
authorgotty <gotty@hedgecode.org>
Fri, 19 Apr 2019 01:04:13 +0000 (04:04 +0300)
committergotty <gotty@hedgecode.org>
Fri, 19 Apr 2019 01:04:13 +0000 (04:04 +0300)
chesshog-qrcode/pom.xml [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/ChessQRCodeApp.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/ChessQRCodeConstants.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeException.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeMode.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeReader.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeWriter.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRMatrixUtils.java [new file with mode: 0644]
chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRResult.java [new file with mode: 0644]
chesshog-qrcode/src/main/resources/org/hedgecode/chess/qrcode/LocalStrings.properties [new file with mode: 0644]
chesshog-qrcode/src/main/resources/org/hedgecode/chess/qrcode/LocalStrings_ru.properties [new file with mode: 0644]

diff --git a/chesshog-qrcode/pom.xml b/chesshog-qrcode/pom.xml
new file mode 100644 (file)
index 0000000..3892e5e
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ Copyright (c) 2018-2019. Developed by Hedgecode.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.hedgecode.chess</groupId>
+        <artifactId>chesshog</artifactId>
+        <version>0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>chesshog-qrcode</artifactId>
+    <version>0.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>Hedgecode ChessHog QR Code</name>
+    <description>
+        Hedgecode ChessHog QR Code Module.
+    </description>
+
+    <properties>
+        <chessHogVersion>0.1-SNAPSHOT</chessHogVersion>
+        <zxingVersion>3.3.3</zxingVersion>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.hedgecode.chess</groupId>
+            <artifactId>chesshog</artifactId>
+            <version>${chessHogVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hedgecode.chess</groupId>
+            <artifactId>chesshog-core</artifactId>
+            <version>${chessHogVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>${zxingVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>${zxingVersion}</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/ChessQRCodeApp.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/ChessQRCodeApp.java
new file mode 100644 (file)
index 0000000..59f64e3
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess;
+
+/**
+ * Chess QR Code Main Application.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public final class ChessQRCodeApp {
+
+    public static void main(String[] args) {
+
+
+    }
+
+}
\ No newline at end of file
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/ChessQRCodeConstants.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/ChessQRCodeConstants.java
new file mode 100644 (file)
index 0000000..396f0a5
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Store of Chess QR Code module constants.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public final class ChessQRCodeConstants {
+
+    public static final Charset CHARSET = StandardCharsets.UTF_8;
+
+    public static final String LOCALE_BUNDLE_FILE = "org.hedgecode.chess.qrcode.LocalStrings";
+
+    private ChessQRCodeConstants() {
+        throw new AssertionError(
+                "No org.hedgecode.chess.ChessQRCodeConstants instances!"
+        );
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeException.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeException.java
new file mode 100644 (file)
index 0000000..5c490de
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.qrcode;
+
+import java.util.ResourceBundle;
+
+import org.hedgecode.chess.ChessQRCodeConstants;
+
+/**
+ * Chess-specific QR Codes read/write/parse Exception.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ChessQRCodeException extends Exception {
+
+    public enum Type {
+
+        READ  ("[Read Error] "),
+        WRITE ("[Write Error] "),
+        PARSE ("[Parse Error] ");
+
+        private String msg;
+
+        Type(String msg) {
+            this.msg = msg;
+        }
+    }
+
+    private static final ResourceBundle LOCALE_BUNDLE =
+            ResourceBundle.getBundle(ChessQRCodeConstants.LOCALE_BUNDLE_FILE);
+
+    private Type type;
+    private String localeKey;
+    private String message;
+
+    public ChessQRCodeException(Type type, String localeKey) {
+        this.type = type;
+        this.localeKey = localeKey;
+        this.message = null;
+    }
+
+    public ChessQRCodeException(Type type, String localeKey, String message) {
+        this.type = type;
+        this.localeKey = localeKey;
+        this.message = message;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public String getLocaleKey() {
+        return localeKey;
+    }
+
+    public String getMessage() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(type.msg);
+        if (localeKey != null)
+            sb.append(
+                    LOCALE_BUNDLE.getString(localeKey)
+            );
+        if (message != null)
+            sb.append(": ").append(message);
+        return sb.toString();
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeMode.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeMode.java
new file mode 100644 (file)
index 0000000..1c8f373
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.qrcode;
+
+/**
+ * Mode of data type stored in Chess-specific QR Codes.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public enum ChessQRCodeMode {
+
+    FEN,
+    TCD,
+    PGN,
+    TCG;
+
+    static final int CODE_LENGTH = 3;
+
+    boolean isPosition() {
+        return FEN.equals(this) || TCD.equals(this);
+    }
+
+    boolean isGame() {
+        return PGN.equals(this) || TCG.equals(this);
+    }
+
+    public static ChessQRCodeMode byCode(String code) {
+        for (ChessQRCodeMode mode : ChessQRCodeMode.values()) {
+            if (mode.name().equals(code))
+                return mode;
+        }
+        return null;
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeReader.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeReader.java
new file mode 100644 (file)
index 0000000..a0bbbdf
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.qrcode;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.EnumMap;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.ReaderException;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.decoder.Decoder;
+import com.google.zxing.qrcode.detector.Detector;
+
+import org.hedgecode.chess.ChessQRCodeConstants;
+
+/**
+ * Chess-specific QR Codes reader (image decoder).
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ChessQRCodeReader {
+
+    private final Decoder decoder;
+
+    private static ChessQRCodeReader _instance = new ChessQRCodeReader();
+
+    private ChessQRCodeReader() {
+        decoder = new Decoder();
+    }
+
+    public ChessQRResult read(File qrCodeImageFile) throws ChessQRCodeException {
+        BufferedImage qrCodeImage;
+        try {
+            qrCodeImage = ImageIO.read(qrCodeImageFile);
+        } catch (IOException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.READ, "read.input.qrcode.file", qrCodeImageFile.getName()
+            );
+        }
+        return read(qrCodeImage);
+    }
+
+    public ChessQRResult read(InputStream qrCodeInputStream) throws ChessQRCodeException {
+        BufferedImage qrCodeImage;
+        try {
+            qrCodeImage = ImageIO.read(qrCodeInputStream);
+        } catch (IOException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.READ, "read.input.qrcode.stream", e.getMessage()
+            );
+        }
+        return read(qrCodeImage);
+    }
+
+    public ChessQRResult read(URI qrCodeUri) throws ChessQRCodeException {
+        BufferedImage qrCodeImage;
+        try {
+            qrCodeImage = ImageIO.read(qrCodeUri.toURL());
+        } catch (IOException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.READ, "read.input.qrcode.url", qrCodeUri.toASCIIString()
+            );
+        }
+        return read(qrCodeImage);
+    }
+
+    public ChessQRResult read(BufferedImage qrCodeImage) throws ChessQRCodeException {
+        BinaryBitmap bitmap = new BinaryBitmap(
+                new HybridBinarizer(
+                        new BufferedImageLuminanceSource(qrCodeImage)
+                )
+        );
+        return decode(bitmap, null);
+    }
+
+    private ChessQRResult decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
+            throws ChessQRCodeException
+    {
+        Map<DecodeHintType,Object> newHints = new EnumMap<>(DecodeHintType.class);
+        if (hints != null) {
+            newHints.putAll(hints);
+            if (!hints.containsKey(DecodeHintType.CHARACTER_SET))
+                newHints.put(DecodeHintType.CHARACTER_SET, ChessQRCodeConstants.CHARSET.name());
+        } else {
+            newHints.put(DecodeHintType.CHARACTER_SET, ChessQRCodeConstants.CHARSET.name());
+        }
+        hints = newHints;
+
+        DecoderResult decoderResult;
+        try {
+            DetectorResult detectorResult = new Detector(
+                    image.getBlackMatrix()
+            ).detect(hints);
+
+            decoderResult = decoder.decode(
+                    detectorResult.getBits(), hints
+            );
+        } catch (ReaderException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.READ, "read.zxing.qrcode.error", e.getMessage()
+            );
+        }
+
+        ChessQRCodeMode mode = ChessQRCodeMode.byCode(
+                decoderResult.getText().substring(0, ChessQRCodeMode.CODE_LENGTH)
+        );
+
+        if (mode == null) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.READ, "read.unknown.chess.mode"
+            );
+        }
+
+        return new ChessQRResult(
+                mode,
+                decoderResult.getText().substring(ChessQRCodeMode.CODE_LENGTH)
+        );
+/*
+        ResultPoint[] points = detectorResult.getPoints();
+
+        if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
+            ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
+        }
+
+        Result result = new Result(
+                decoderResult.getText(),
+                decoderResult.getRawBytes(),
+                points,
+                BarcodeFormat.QR_CODE
+        );
+
+        List<byte[]> byteSegments = decoderResult.getByteSegments();
+        if (byteSegments != null) {
+            result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+        }
+        String ecLevel = decoderResult.getECLevel();
+        if (ecLevel != null) {
+            result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+        }
+        if (decoderResult.hasStructuredAppend()) {
+            result.putMetadata(
+                    ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
+                    decoderResult.getStructuredAppendSequenceNumber()
+            );
+            result.putMetadata(
+                    ResultMetadataType.STRUCTURED_APPEND_PARITY,
+                    decoderResult.getStructuredAppendParity()
+            );
+        }
+        return result;
+*/
+    }
+
+    public static ChessQRCodeReader getInstance() {
+        return _instance;
+    }
+
+
+    public static void main(String[] args) {
+        try {
+            ChessQRResult qrCodeResult = ChessQRCodeReader.getInstance().read(
+                    new File("MyQRCode.png")
+            );
+            System.out.println("Decoded format = " + qrCodeResult.getMode());
+            System.out.println("Decoded text = " + qrCodeResult.getContents());
+        } catch (ChessQRCodeException e) {
+            System.out.println("Could not decode QR Code. Exception: " + e.getMessage());
+        }
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeWriter.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRCodeWriter.java
new file mode 100644 (file)
index 0000000..bc11750
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.qrcode;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.EnumMap;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.google.zxing.qrcode.encoder.ByteMatrix;
+import com.google.zxing.qrcode.encoder.Encoder;
+import com.google.zxing.qrcode.encoder.QRCode;
+
+import org.hedgecode.chess.ChessQRCodeConstants;
+import org.hedgecode.chess.img.ImageFormat;
+
+/**
+ * Chess-specific QR Codes writer (image generator).
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ChessQRCodeWriter {
+
+    private static final int DEF_QUIET_ZONE_SIZE = 4;
+    private static final int DEF_QRCODE_BIT_SIZE = 250;
+
+    private static final ErrorCorrectionLevel DEF_ERR_CORR_LEVEL = ErrorCorrectionLevel.H; // todo: Q
+
+    private static ChessQRCodeWriter _instance = new ChessQRCodeWriter();
+
+    private ChessQRCodeWriter() {
+    }
+
+    public void write(ChessQRCodeMode mode, String contents, ImageFormat format, String qrCodeFilePath)
+            throws ChessQRCodeException
+    {
+        write(
+                mode,
+                contents,
+                format,
+                Paths.get(qrCodeFilePath)
+        );
+    }
+
+    public void write(ChessQRCodeMode mode, String contents, ImageFormat format, Path qrCodeFile)
+            throws ChessQRCodeException
+    {
+        write(
+                mode,
+                contents,
+                format,
+                DEF_QRCODE_BIT_SIZE,
+                DEF_QRCODE_BIT_SIZE,
+                qrCodeFile
+        );
+    }
+
+
+    public void write(
+            ChessQRCodeMode mode,
+            String contents,
+            ImageFormat format,
+            int width, int height,
+            Path qrCodeFile)
+            throws ChessQRCodeException
+    {
+        try {
+            BufferedImage image = generate(mode, contents, width, height);
+            if (!ImageIO.write(image, format.name(), qrCodeFile.toFile())) {
+                throw new ChessQRCodeException(
+                        ChessQRCodeException.Type.WRITE, "write.output.qrcode.file", qrCodeFile.toFile().getName()
+                );
+            }
+        } catch (IOException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.output.qrcode.file", e.getMessage()
+            );
+        }
+    }
+
+    public void write(ChessQRCodeMode mode, String contents, ImageFormat format, OutputStream stream)
+            throws ChessQRCodeException
+    {
+        try {
+            BufferedImage image = generate(mode, contents);
+            if (!ImageIO.write(image, format.name(), stream)) {
+                throw new ChessQRCodeException(
+                        ChessQRCodeException.Type.WRITE, "write.output.qrcode.stream"
+                );
+            }
+        } catch (IOException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.output.qrcode.stream", e.getMessage()
+            );
+        }
+    }
+
+    public BufferedImage generate(ChessQRCodeMode mode, String contents)
+            throws ChessQRCodeException
+    {
+        return generate(mode, contents, DEF_QRCODE_BIT_SIZE, DEF_QRCODE_BIT_SIZE);
+    }
+
+    public BufferedImage generate(ChessQRCodeMode mode, String contents, int width, int height)
+            throws ChessQRCodeException
+    {
+        BitMatrix bitMatrix = encode(
+                mode, contents,
+                width, height,
+                null
+        );
+
+        return MatrixToImageWriter.toBufferedImage(
+                bitMatrix
+        );
+    }
+
+    private BitMatrix encode(
+            ChessQRCodeMode mode,
+            String contents,
+            int width, int height,
+            Map<EncodeHintType,?> hints)
+            throws ChessQRCodeException
+    {
+        if (mode == null) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.qrcode.mode.null"
+            );
+        }
+
+        if (contents == null || contents.isEmpty()) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.qrcode.contents.empty"
+            );
+        }
+
+        if (width < 0 || height < 0) { // todo
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.qrcode.size.incorrect"
+            );
+        }
+
+        int quietZone = DEF_QUIET_ZONE_SIZE;
+        ErrorCorrectionLevel errorCorrectionLevel = DEF_ERR_CORR_LEVEL;
+
+        Map<EncodeHintType,Object> newHints = new EnumMap<>(EncodeHintType.class);
+        if (hints != null) {
+            newHints.putAll(hints);
+            if (!hints.containsKey(EncodeHintType.CHARACTER_SET)) {
+                newHints.put(EncodeHintType.CHARACTER_SET, ChessQRCodeConstants.CHARSET.name());
+            }
+            if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
+                errorCorrectionLevel = ErrorCorrectionLevel.valueOf(
+                        hints.get(EncodeHintType.ERROR_CORRECTION).toString()
+                );
+            }
+            if (hints.containsKey(EncodeHintType.MARGIN)) {
+                quietZone = Integer.parseInt(
+                        hints.get(EncodeHintType.MARGIN).toString()
+                );
+            }
+        } else {
+            newHints.put(EncodeHintType.CHARACTER_SET, ChessQRCodeConstants.CHARSET.name());
+        }
+        hints = newHints;
+
+        String qrCodeText = mode.name().concat(contents);
+
+        QRCode qrCode;
+        try {
+            qrCode = Encoder.encode(
+                    qrCodeText, errorCorrectionLevel, hints
+            );
+        } catch (WriterException e) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.zxing.qrcode.error", e.getMessage()
+            );
+        }
+
+        return renderMatrix(
+                qrCode, width, height, quietZone
+        );
+    }
+
+    private BitMatrix renderMatrix(QRCode qrCode, int width, int height, int quietZone)
+            throws ChessQRCodeException
+    {
+        ByteMatrix byteMatrix =
+                ChessQRMatrixUtils.embedChessLogoPattern(
+                        qrCode.getMatrix()
+                );
+
+        if (byteMatrix == null) {
+            throw new ChessQRCodeException(
+                    ChessQRCodeException.Type.WRITE, "write.qrcode.chess.incorrect"
+            );
+        }
+
+        return ChessQRMatrixUtils.renderMatrix(
+                byteMatrix,
+                width,
+                height,
+                quietZone
+        );
+    }
+
+    public static ChessQRCodeWriter getInstance() {
+        return _instance;
+    }
+
+
+    public static void main(String[] args) {
+        try {
+            ChessQRCodeWriter.getInstance().write(
+                    ChessQRCodeMode.FEN,
+                    "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2",
+                    ImageFormat.PNG,
+                    "./MyQRCode.png"
+            );
+/*
+            ChessQRCodeWriter.getInstance().write(
+                    ChessQRCodeMode.PGN,
+                    "1.e4 c6 2.d4 d5 3.Nc3 dxe4 4.Nxe4 Nd7 5.Ng5 Ngf6 6.Bd3 e6 7.N1f3 h6\n" +
+                            "8.Nxe6 Qe7 9.O-O fxe6 10.Bg6+ Kd8 {Каспаров встряхнул головой} \n" +
+                            "11.Bf4 b5 12.a4 Bb7 13.Re1 Nd5 14.Bg3 Kc8 15.axb5 cxb5 16.Qd3 Bc6 \n" +
+                            "17.Bf5 exf5 18.Rxe7 Bxe7 19.c4 1-0",
+                    ImageFormat.PNG,
+                    "./MyQRCode.png"
+            );
+*/
+        } catch (ChessQRCodeException e) {
+            System.out.println("Could not generate QR Code, Exception: " + e.getMessage());
+        }
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRMatrixUtils.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRMatrixUtils.java
new file mode 100644 (file)
index 0000000..75df9f9
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.qrcode;
+
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.encoder.ByteMatrix;
+
+/**
+ * Chess-specific QR Codes binary matrix accessory utils.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public final class ChessQRMatrixUtils {
+
+/*
+    private static final int[][] CHESS_LOGO_PATTERN = {
+            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+            {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1},
+            {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+    };
+*/
+
+
+    private static final int[][] CHESS_LOGO_PATTERN = {
+            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+            {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1},
+            {0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1},
+            {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1},
+            {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1},
+            {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+            {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+    };
+
+/*
+    private static final int[][] CHESS_LOGO_PATTERN = {
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+            {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+            {1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1},
+            {1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1},
+            {1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1},
+            {1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1},
+            {1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1},
+            {1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1},
+            {1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1},
+            {1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1},
+            {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+            {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+    };
+*/
+
+    private static final int CHESS_LOGO_PATTERN_LENGTH = CHESS_LOGO_PATTERN[0].length;
+
+
+    static ByteMatrix embedChessLogoPattern(ByteMatrix byteMatrix) {
+        if (byteMatrix == null || byteMatrix.getWidth() < CHESS_LOGO_PATTERN_LENGTH + 14) // todo: 14
+            return null;
+
+        int xStart = (byteMatrix.getWidth() - CHESS_LOGO_PATTERN_LENGTH) / 2;
+        int yStart = (byteMatrix.getHeight() - CHESS_LOGO_PATTERN_LENGTH) / 2;
+
+        for (int y = 0; y < CHESS_LOGO_PATTERN_LENGTH; ++y) {
+            int[] patternY = CHESS_LOGO_PATTERN[y];
+            for (int x = 0; x < CHESS_LOGO_PATTERN_LENGTH; ++x) {
+                byteMatrix.set(xStart + x, yStart + y, patternY[x]);
+            }
+        }
+        return byteMatrix;
+    }
+
+
+    static boolean checkChessLogoPattern(ByteMatrix byteMatrix) {
+
+
+        return false;
+    }
+
+    static boolean checkChessLogoPattern(BitMatrix bitMatrix) {
+
+
+        return false;
+    }
+
+
+    static BitMatrix renderMatrix(ByteMatrix byteMatrix, int width, int height, int quietZone) {
+        int inputWidth = byteMatrix.getWidth();
+        int inputHeight = byteMatrix.getHeight();
+        int qrWidth = inputWidth + (quietZone * 2);
+        int qrHeight = inputHeight + (quietZone * 2);
+        int outputWidth = Math.max(width, qrWidth);
+        int outputHeight = Math.max(height, qrHeight);
+
+        int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
+        int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
+        int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
+
+        BitMatrix bitMatrix = new BitMatrix(outputWidth, outputHeight);
+
+        for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
+            for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
+                if (byteMatrix.get(inputX, inputY) == 1) {
+                    bitMatrix.setRegion(outputX, outputY, multiple, multiple);
+                }
+            }
+        }
+        return bitMatrix;
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRResult.java b/chesshog-qrcode/src/main/java/org/hedgecode/chess/qrcode/ChessQRResult.java
new file mode 100644 (file)
index 0000000..1b8bbf7
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2018-2019. Developed by Hedgecode.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.hedgecode.chess.qrcode;
+
+import org.hedgecode.chess.Parsers;
+import org.hedgecode.chess.game.Game;
+import org.hedgecode.chess.position.ParseException;
+import org.hedgecode.chess.position.Parser;
+import org.hedgecode.chess.position.Position;
+
+/**
+ * Result storage for Chess-specific QR Codes data.
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ChessQRResult {
+
+    private ChessQRCodeMode mode;
+    private String contents;
+    private Position position;
+    private Game game;
+
+    ChessQRResult(ChessQRCodeMode mode, String contents) {
+        this.mode = mode;
+        this.contents = contents;
+    }
+
+    ChessQRResult(Position position) {
+        this.position = position;
+    }
+
+    ChessQRResult(Game game) {
+        this.game = game;
+    }
+
+    public ChessQRCodeMode getMode() {
+        return mode;
+    }
+
+    public String getContents() {
+        return contents;
+    }
+
+    public Position getPosition() throws ChessQRCodeException {
+        if (position == null) {
+            if (mode != null && mode.isPosition()) {
+                try {
+                    position = getPositionParser().parse(contents);
+                } catch (ParseException e) {
+                    throw new ChessQRCodeException(
+                            ChessQRCodeException.Type.PARSE, null, e.getMessage()
+                    );
+                }
+            }
+        }
+        return position;
+    }
+
+    public Game getGame() {
+        // todo
+        return game;
+    }
+
+    private Parser getPositionParser() throws ChessQRCodeException {
+        switch (mode) {
+            case FEN:
+                return Parsers.FEN.parser();
+            case TCD:
+                return Parsers.TCD.parser();
+            default:
+                throw new ChessQRCodeException(
+                        ChessQRCodeException.Type.PARSE, "parse.unknown.position.format"
+                );
+        }
+    }
+
+}
diff --git a/chesshog-qrcode/src/main/resources/org/hedgecode/chess/qrcode/LocalStrings.properties b/chesshog-qrcode/src/main/resources/org/hedgecode/chess/qrcode/LocalStrings.properties
new file mode 100644 (file)
index 0000000..5c7f3a1
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright (c) 2018-2019. Developed by Hedgecode.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default localized string information
+# Localized for Locale en_US
+
+parse.unknown.position.format=Unknown chess position format for parsing
+read.input.qrcode.file=Failed to read QR Code from file
+read.input.qrcode.stream=Failed to read QR Code from input stream
+read.input.qrcode.url=Failed to read QR Code from URL
+read.unknown.chess.mode=Unknown chess mode in the QR Code
+read.zxing.qrcode.error=Error decoding of QR code
+write.output.qrcode.file=Failed to write QR Code to file
+write.output.qrcode.stream=Failed to write QR Code to output stream
+write.qrcode.mode.null=Input chess mode not specified
+write.qrcode.contents.empty=Input data is empty
+write.qrcode.size.incorrect=Incorrect QR Code size specified
+write.qrcode.chess.incorrect=Specified size is not suitable for chess QR Code
+write.zxing.qrcode.error=Error encoding of QR code
diff --git a/chesshog-qrcode/src/main/resources/org/hedgecode/chess/qrcode/LocalStrings_ru.properties b/chesshog-qrcode/src/main/resources/org/hedgecode/chess/qrcode/LocalStrings_ru.properties
new file mode 100644 (file)
index 0000000..b396326
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright (c) 2018-2019. Developed by Hedgecode.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Localized for Locale ru_RU
+
+parse.unknown.position.format=\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 \u0448\u0430\u0445\u043C\u0430\u0442\u043D\u043E\u0439 \u043F\u043E\u0437\u0438\u0446\u0438\u0438
+read.input.qrcode.file=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C QR \u043A\u043E\u0434 \u0438\u0437 \u0444\u0430\u0439\u043B\u0430
+read.input.qrcode.stream=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C QR \u043A\u043E\u0434 \u0438\u0437 \u0432\u0445\u043E\u0434\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u043E\u043A\u0430
+read.input.qrcode.url=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C QR \u043A\u043E\u0434 \u0438\u0437 URL
+read.unknown.chess.mode=\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0442\u0438\u043F \u0448\u0430\u0445\u043C\u0430\u0442\u043D\u043E\u0439 \u0437\u0430\u043F\u0438\u0441\u0438 \u0432 QR \u043A\u043E\u0434\u0435
+read.zxing.qrcode.error=\u041E\u0448\u0438\u0431\u043A\u0430 \u0434\u0435\u043A\u043E\u0434\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F QR \u043A\u043E\u0434\u0430
+write.output.qrcode.file=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C QR \u043A\u043E\u0434 \u0432 \u0444\u0430\u0439\u043B
+write.output.qrcode.stream=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C QR \u043A\u043E\u0434 \u0432 \u0432\u044B\u0445\u043E\u0434\u043D\u043E\u0439 \u043F\u043E\u0442\u043E\u043A
+write.qrcode.mode.null=\u041D\u0435 \u0437\u0430\u0434\u0430\u043D \u0442\u0438\u043F \u0432\u0445\u043E\u0434\u043D\u044B\u0445 \u0434\u0430\u043D\u043D\u044B\u0445
+write.qrcode.contents.empty=\u0412\u0445\u043E\u0434\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u0443\u0441\u0442\u044B
+write.qrcode.size.incorrect=\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u043E \u0437\u0430\u0434\u0430\u043D \u0440\u0430\u0437\u043C\u0435\u0440 QR \u043A\u043E\u0434\u0430
+write.qrcode.chess.incorrect=\u0417\u0430\u0434\u0430\u043D\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 \u043D\u0435 \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 \u0434\u043B\u044F \u0448\u0430\u0445\u043C\u0430\u0442\u043D\u043E\u0433\u043E QR \u043A\u043E\u0434\u0430
+write.zxing.qrcode.error=\u041E\u0448\u0438\u0431\u043A\u0430 \u043A\u043E\u0434\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F QR \u043A\u043E\u0434\u0430