[LIB-9] Add ability to archive qrcode content
[chesshog.git] / chesshog-qrcode / src / main / java / org / hedgecode / chess / qrcode / ChessQRCodeWriter.java
1 /*
2  * Copyright (c) 2018-2019. Developed by Hedgecode.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.hedgecode.chess.qrcode;
18
19 import java.awt.image.BufferedImage;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.nio.file.Path;
23 import java.nio.file.Paths;
24 import java.util.EnumMap;
25 import java.util.Map;
26
27 import javax.imageio.ImageIO;
28
29 import com.google.zxing.EncodeHintType;
30 import com.google.zxing.WriterException;
31 import com.google.zxing.client.j2se.MatrixToImageWriter;
32 import com.google.zxing.common.BitMatrix;
33 import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
34 import com.google.zxing.qrcode.encoder.ByteMatrix;
35 import com.google.zxing.qrcode.encoder.Encoder;
36 import com.google.zxing.qrcode.encoder.QRCode;
37
38 import org.hedgecode.chess.img.ImageFormat;
39
40 /**
41  * Chess-specific QR Codes writer (image generator).
42  *
43  * @author Dmitry Samoshin aka gotty
44  */
45 public class ChessQRCodeWriter {
46
47     private static final int DEF_QUIET_ZONE_SIZE = 4;
48     private static final int DEF_QRCODE_BIT_SIZE = 250;
49     private static final int MIN_QRCODE_BIT_SIZE = 50;
50
51     private static final ErrorCorrectionLevel DEF_ERR_CORR_LEVEL = ErrorCorrectionLevel.H; // todo: Q
52
53     private static ChessQRCodeWriter _instance = new ChessQRCodeWriter();
54
55     protected ChessQRCodeWriter() {
56     }
57
58     public void write(ChessQRCodeMode mode, String contents, ImageFormat format, String qrCodeFilePath)
59             throws ChessQRCodeException
60     {
61         write(
62                 mode,
63                 contents,
64                 format,
65                 Paths.get(qrCodeFilePath)
66         );
67     }
68
69     public void write(ChessQRCodeMode mode, String contents, ImageFormat format, Path qrCodeFile)
70             throws ChessQRCodeException
71     {
72         write(
73                 mode,
74                 contents,
75                 format,
76                 DEF_QRCODE_BIT_SIZE,
77                 DEF_QRCODE_BIT_SIZE,
78                 qrCodeFile
79         );
80     }
81
82
83     public void write(
84             ChessQRCodeMode mode,
85             String contents,
86             ImageFormat format,
87             int width, int height,
88             Path qrCodeFile)
89             throws ChessQRCodeException
90     {
91         try {
92             BufferedImage image = generate(mode, contents, width, height);
93             if (!ImageIO.write(image, format.name(), qrCodeFile.toFile())) {
94                 throw new ChessQRCodeException(
95                         ChessQRCodeException.Type.WRITE, "write.output.qrcode.file", qrCodeFile.toFile().getName()
96                 );
97             }
98         } catch (IOException e) {
99             throw new ChessQRCodeException(
100                     ChessQRCodeException.Type.WRITE, "write.output.qrcode.file", e.getMessage()
101             );
102         }
103     }
104
105     public void write(ChessQRCodeMode mode, String contents, ImageFormat format, OutputStream stream)
106             throws ChessQRCodeException
107     {
108         try {
109             BufferedImage image = generate(mode, contents);
110             if (!ImageIO.write(image, format.name(), stream)) {
111                 throw new ChessQRCodeException(
112                         ChessQRCodeException.Type.WRITE, "write.output.qrcode.stream"
113                 );
114             }
115         } catch (IOException e) {
116             throw new ChessQRCodeException(
117                     ChessQRCodeException.Type.WRITE, "write.output.qrcode.stream", e.getMessage()
118             );
119         }
120     }
121
122     public BufferedImage generate(ChessQRCodeMode mode, String contents)
123             throws ChessQRCodeException
124     {
125         return generate(mode, contents, DEF_QRCODE_BIT_SIZE, DEF_QRCODE_BIT_SIZE);
126     }
127
128     public BufferedImage generate(ChessQRCodeMode mode, String contents, int width, int height)
129             throws ChessQRCodeException
130     {
131         BitMatrix bitMatrix = encode(
132                 mode, contents,
133                 width, height,
134                 null
135         );
136
137         return MatrixToImageWriter.toBufferedImage(
138                 bitMatrix
139         );
140     }
141
142     protected BitMatrix encode(
143             ChessQRCodeMode mode,
144             String contents,
145             int width, int height,
146             Map<EncodeHintType,?> hints)
147             throws ChessQRCodeException
148     {
149         if (mode == null) {
150             throw new ChessQRCodeException(
151                     ChessQRCodeException.Type.WRITE, "write.qrcode.mode.null"
152             );
153         }
154
155         if (contents == null || contents.isEmpty()) {
156             throw new ChessQRCodeException(
157                     ChessQRCodeException.Type.WRITE, "write.qrcode.contents.empty"
158             );
159         }
160
161         if (Math.min(width, height) < MIN_QRCODE_BIT_SIZE) { // todo: check for Version
162             throw new ChessQRCodeException(
163                     ChessQRCodeException.Type.WRITE, "write.qrcode.size.incorrect"
164             );
165         }
166
167         String qrCodeContents = mode.name().concat(contents);
168
169         int quietZone = DEF_QUIET_ZONE_SIZE;
170         ErrorCorrectionLevel errorCorrectionLevel = DEF_ERR_CORR_LEVEL;
171
172         Map<EncodeHintType,Object> newHints = new EnumMap<>(EncodeHintType.class);
173         if (hints != null) {
174             newHints.putAll(hints);
175             if (!hints.containsKey(EncodeHintType.CHARACTER_SET)) {
176                 newHints.put(EncodeHintType.CHARACTER_SET, ChessQRCodeConstants.CHARSET.name());
177             }
178             if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
179                 errorCorrectionLevel = ErrorCorrectionLevel.valueOf(
180                         hints.get(EncodeHintType.ERROR_CORRECTION).toString()
181                 );
182             }
183             if (hints.containsKey(EncodeHintType.MARGIN)) {
184                 quietZone = Integer.parseInt(
185                         hints.get(EncodeHintType.MARGIN).toString()
186                 );
187             }
188         } else {
189             newHints.put(EncodeHintType.CHARACTER_SET, ChessQRCodeConstants.CHARSET.name());
190         }
191         hints = newHints;
192
193         QRCode qrCode;
194         try {
195             qrCode = Encoder.encode(
196                     qrCodeContents, errorCorrectionLevel, hints
197             );
198         } catch (WriterException e) {
199             throw new ChessQRCodeException(
200                     ChessQRCodeException.Type.WRITE, "write.zxing.qrcode.error", e.getMessage()
201             );
202         }
203
204         return renderMatrix(
205                 qrCode, width, height, quietZone
206         );
207     }
208
209     private BitMatrix renderMatrix(QRCode qrCode, int width, int height, int quietZone)
210             throws ChessQRCodeException
211     {
212         ByteMatrix byteMatrix =
213                 ChessQRMatrixUtils.embedChessLogoPattern(
214                         qrCode.getMatrix()
215                 );
216
217         if (byteMatrix == null) {
218             throw new ChessQRCodeException(
219                     ChessQRCodeException.Type.WRITE, "write.qrcode.chess.incorrect"
220             );
221         }
222
223         return ChessQRMatrixUtils.renderMatrix(
224                 byteMatrix,
225                 width,
226                 height,
227                 quietZone
228         );
229     }
230
231     public static ChessQRCodeWriter getInstance() {
232         return _instance;
233     }
234
235
236     public static void main(String[] args) {
237         try {
238             ChessQRCodeWriter.getInstance().write(
239                     ChessQRCodeMode.FEN,
240                     "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2",
241                     ImageFormat.PNG,
242                     "./qrcode.png"
243             );
244 /*
245             ChessQRCodeWriter.getInstance().write(
246                     ChessQRCodeMode.PGN,
247                     "1.e4 c6 2.d4 d5 3.Nc3 dxe4 4.Nxe4 Nd7 5.Ng5 Ngf6 6.Bd3 e6 7.N1f3 h6\n" +
248                             "8.Nxe6 Qe7 9.O-O fxe6 10.Bg6+ Kd8 {Каспаров встряхнул головой} \n" +
249                             "11.Bf4 b5 12.a4 Bb7 13.Re1 Nd5 14.Bg3 Kc8 15.axb5 cxb5 16.Qd3 Bc6 \n" +
250                             "17.Bf5 exf5 18.Rxe7 Bxe7 19.c4 1-0",
251                     ImageFormat.PNG,
252                     "./qrcode.png"
253             );
254 */
255         } catch (ChessQRCodeException e) {
256             System.out.println("Could not generate QR Code, Exception: " + e.getMessage());
257         }
258     }
259
260 }