001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.fukurou.util;
017
018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
019import java.awt.Component;
020import java.awt.Graphics;
021import java.awt.image.BufferedImage;
022import java.io.File;
023import java.io.IOException;
024import javax.imageio.ImageIO;
025
026import com.swetake.util.Qrcode;
027
028import org.opengion.fukurou.system.HybsConst;                                           // fukurou.util.StringUtil → fukurou.system.HybsConst に変更
029import org.opengion.fukurou.system.LogWriter;                                           // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
030
031/**
032 * QrcodeImage は、3次元バーコードの QRコードイメージを生成する
033 * 独立したコンポーネントです。
034 * ここでの使い方は、初期化時に、エンコードする文字列(120Byte以内)と、
035 * 出力ファイル名を指定して、Graphics に描画したQRコードイメージを
036 * JPEG 変換し、指定のファイルに上書き保存しています。
037 * QRコード作成に、http://www.swetake.com/ の Qrcode クラスを使用しています。
038 * これが、2004/11/7  ver.0.50beta9 とのことなので、動作チェック、および、
039 * 製品としての保証という意味では、まだ使えるレベルではありませんが、
040 * コード計算さえバグっていなければ使えうる為、試験的導入致します。
041 *
042 * @version  4.0
043 * @author   Kazuhiko Hasegawa
044 * @since    JDK5.0,
045 */
046public class QrcodeImage extends Component {
047        /** このプログラムのVERSION文字列を設定します。   {@value} */
048        private static final String VERSION = "7.2.1.0 (2020/03/13)" ;
049        private static final long serialVersionUID = 721020200313L ;
050
051        // version  0 ~ 39 => 1 ~ 40
052//      // errCo    H,M    0:H 1:M
053        // errCo    L,M,Q,H                                             7.2.1.0 (2020/03/13) 
054        // encMd    N,A,B  0:N 1:A 2:B
055        private static final int[][][] QC_DATA = new int[][][] {
056                {{   17 ,   10 ,   4 } , {   34 ,   20 ,    8 }} ,
057                {{   34 ,   20 ,   8 } , {   63 ,   38 ,   16 }} ,
058                {{   58 ,   35 ,  15 } , {  101 ,   61 ,   26 }} ,
059                {{   82 ,   50 ,  21 } , {  149 ,   90 ,   38 }} ,
060                {{  106 ,   64 ,  27 } , {  202 ,  122 ,   52 }} ,
061                {{  139 ,   84 ,  36 } , {  255 ,  154 ,   65 }} ,
062                {{  154 ,   93 ,  39 } , {  293 ,  178 ,   75 }} ,
063                {{  202 ,  122 ,  52 } , {  365 ,  221 ,   93 }} ,
064                {{  235 ,  143 ,  60 } , {  432 ,  262 ,  111 }} ,
065                {{  288 ,  174 ,  74 } , {  513 ,  311 ,  131 }} ,
066                {{  331 ,  200 ,  85 } , {  604 ,  366 ,  155 }} ,
067                {{  374 ,  227 ,  96 } , {  691 ,  419 ,  177 }} ,
068                {{  427 ,  259 , 109 } , {  796 ,  483 ,  204 }} ,
069                {{  468 ,  283 , 120 } , {  871 ,  528 ,  223 }} ,
070                {{  530 ,  321 , 136 } , {  991 ,  600 ,  254 }} ,
071                {{  602 ,  365 , 154 } , { 1082 ,  656 ,  277 }} ,
072                {{  674 ,  408 , 173 } , { 1212 ,  734 ,  310 }} ,
073                {{  746 ,  452 , 191 } , { 1346 ,  816 ,  345 }} ,
074                {{  813 ,  493 , 208 } , { 1500 ,  909 ,  384 }} ,
075                {{  919 ,  557 , 235 } , { 1600 ,  970 ,  410 }} ,
076                {{  969 ,  587 , 248 } , { 1708 , 1035 ,  438 }} ,
077                {{ 1056 ,  640 , 270 } , { 1872 , 1134 ,  480 }} ,
078                {{ 1108 ,  672 , 284 } , { 2059 , 1248 ,  528 }} ,
079                {{ 1228 ,  744 , 315 } , { 2188 , 1326 ,  561 }} ,
080                {{ 1286 ,  779 , 330 } , { 2395 , 1451 ,  614 }} ,
081                {{ 1425 ,  864 , 365 } , { 2544 , 1542 ,  652 }} ,
082                {{ 1501 ,  910 , 385 } , { 2701 , 1637 ,  692 }} ,
083                {{ 1581 ,  958 , 405 } , { 2857 , 1732 ,  732 }} ,
084                {{ 1677 , 1016 , 430 } , { 3035 , 1839 ,  778 }} ,
085                {{ 1782 , 1080 , 457 } , { 3289 , 1994 ,  843 }} ,
086                {{ 1897 , 1150 , 486 } , { 3486 , 2113 ,  894 }} ,
087                {{ 2022 , 1226 , 518 } , { 3693 , 2238 ,  947 }} ,
088                {{ 2157 , 1307 , 553 } , { 3909 , 2369 , 1002 }} ,
089                {{ 2301 , 1394 , 590 } , { 4134 , 2506 , 1060 }} ,
090                {{ 2361 , 1431 , 605 } , { 4343 , 2632 , 1113 }} ,
091                {{ 2524 , 1530 , 647 } , { 4588 , 2780 , 1176 }} ,
092                {{ 2625 , 1591 , 673 } , { 4775 , 2894 , 1224 }} ,
093                {{ 2735 , 1658 , 701 } , { 5039 , 3054 , 1292 }} ,
094                {{ 2927 , 1774 , 750 } , { 5313 , 3220 , 1362 }} ,
095                {{ 3057 , 1852 , 784 } , { 5596 , 3391 , 1435 }} } ;
096
097        /** エラー訂正レベル ('L','M','Q','H')  */
098        public static enum ErrCrct {
099//                                      H(0),M(1);
100                                        L(1),M(0),Q(3),H(2);                                            // 7.2.1.0 (2020/03/13)
101                                        private int no;
102                                        public static final ErrCrct DEF = M;
103                                        /** 
104                                         * enum コンストラクター
105                                         *
106                                         * @param       no  番号
107                                         */
108                                        ErrCrct( final int no ) { this.no = no; }
109                                        /** 
110                                         * 番号を取得します。
111                                         *
112                                         * @return      番号
113                                         */
114                                        public int getNo() { return no; }
115                                        /** 
116                                         * チャネルを取得します。
117                                         *
118                                         * @og.rev 7.2.1.0 (2020/03/13) QrcodeImage 見直し。
119                                         *
120                                         * @return      チャネルキャラクタ
121                                         */
122                                        public char getCh() {
123                                                final char ch ;
124                                                switch(this) {
125                                                        case L:  ch = 'L'; break;                       // 7.2.1.0 (2020/03/13)
126                                                        case Q:  ch = 'Q'; break;                       // 7.2.1.0 (2020/03/13)
127                                                        case H:  ch = 'H'; break;
128                                                        case M:
129                                                        default: ch = 'M'; break;
130                                                }
131                                                return ch ;
132                                        }
133                                        /** 
134                                         * チャネルに応じたエラー訂正を取得します。
135                                         *
136                                         * @og.rev 7.2.1.0 (2020/03/13) QrcodeImage 見直し。
137                                         *
138                                         * @param       ch      チャネルキャラクタ
139                                         * @return      エラー訂正
140                                         */
141                                        public static ErrCrct get( final char ch ) {
142                                                final ErrCrct rtn ;
143                                                switch(ch) {
144                                                        case 'L': rtn = L; break;                       // 7.2.1.0 (2020/03/13)
145                                                        case 'Q': rtn = Q; break;                       // 7.2.1.0 (2020/03/13)
146                                                        case 'H': rtn = H; break;
147                                                        case 'M':
148                                                        default:  rtn = M; break;
149                                                }
150                                                return rtn ;
151                                        }
152        };
153
154        /** エンコードモード ('N':数字モード 'A':英数字モード 'B':8bit byteモード) */
155        public static enum EncMode {
156                                        N(0),A(1),B(1);
157                                        private int no;
158                                        public static final EncMode DEF = B;
159
160                                        /**
161                                         * エンコードモード enum を構築するコンストラクタ
162                                         *
163                                         * @param       エンコードモードの番号
164                                         */
165                                        EncMode( final int no ) { this.no = no; }
166
167                                        /**
168                                         * エンコードモードの番号を返します。
169                                         * 'N'=0:数字モード
170                                         * 'A'=1:英数字モード
171                                         * 'B'=1:8bit byteモード
172                                         *
173                                         * @return      エンコードモードの番号
174                                         */
175                                        public int getNo() { return no; }
176
177                                        /**
178                                         * エンコードモードを表す文字
179                                         * 'N':数字モード
180                                         * 'A':英数字モード
181                                         * 'B':8bit byteモード
182                                         *
183                                         * @return      エンコードモードを指定する文字('N':数字モード 'A':英数字モード 'B':8bit byteモード)
184                                         */
185                                        public char getCh() {
186                                                final char ch ;
187                                                switch(this) {
188                                                        case N:  ch = 'N'; break;
189                                                        case A:  ch = 'A'; break;
190                                                        case B:
191                                                        default: ch = 'B'; break;
192                                                }
193                                                return ch ;
194                                        }
195
196                                        /**
197                                         * エンコードモードを指定する文字から、エンコードモード enum を作成します。
198                                         * 'N':数字モード
199                                         * 'A':英数字モード
200                                         * 'B':8bit byteモード
201                                         *
202                                         * @param       ch      エンコードモードを指定する文字
203                                         * @return      エンコードモード
204                                         */
205                                        public static EncMode get( final char ch ) {
206                                                final EncMode rtn ;
207                                                switch(ch) {
208                                                        case 'N': rtn = N; break;
209                                                        case 'A': rtn = A; break;
210                                                        case 'B':
211                                                        default:  rtn = B; break;
212                                                }
213                                                return rtn ;
214                                        }
215        };
216
217        /** バージョン (1から40の整数。0を設定すると自動設定になります。) 初期値:{@value}     */
218        public static final int DEF_VERSION = 5;                // 5.7.1.1 (2013/12/13) VERSION チェックのために、IDを変更します。
219
220        /** セルのマージン 初期値:{@value}        */
221        public static final int MARGIN = 4;
222
223        /** 1セル辺りの塗りつぶしピクセル 初期値:{@value}        */
224        public static final int PIXEL = 3;
225
226        /** 出力イメージのタイプ(PNG/JPEG) 初期値:{@value}   */
227        public static final String IMAGE_TYPE = "PNG";
228
229        private String qrData;
230        private String saveFile;
231        private String imgType  = IMAGE_TYPE ;
232        private ErrCrct errCo   = ErrCrct.DEF ;
233        private EncMode encMd   = EncMode.DEF ;
234        private int             version = DEF_VERSION ;                 // 5.7.1.1 (2013/12/13) VERSION チェックのために、IDを変更します。
235        private int             pixel   = PIXEL ;
236
237        private int imageSize ;
238
239        /**
240         * 初期化メソッド
241         *
242         * エラー訂正レベル:M , マージン:4(セル分) , 塗りつぶしピクセル:3
243         * エンコードモード:B(バイナリ) 、バージョン:5 , イメージのタイプ:PNG
244         * に初期化されます。
245         *
246         * @og.rev 5.7.1.1 (2013/12/13) VERSION チェックのために、VERSION ⇒ DEF_VERSION に変更します。
247         *
248         * @param   qrData  エンコードする文字列(120Byte 以内)
249         * @param   saveFile 出力ファイル名
250         */
251        public void init( final String qrData,final String saveFile ) {
252                init( qrData,saveFile,DEF_VERSION,EncMode.DEF,ErrCrct.DEF,IMAGE_TYPE,PIXEL );   // 5.7.1.1 (2013/12/13)
253        }
254
255        /**
256         * 初期化メソッド
257         *
258         * エラー訂正レベル:M , マージン:4(セル分) , 塗りつぶしピクセル:3
259         * イメージのタイプ:PNG に初期化されます。
260         *
261         * @param   qrData   エンコードする文字列(120Byte 以内)
262         * @param   saveFile 出力ファイル名
263         * @param   version  バージョン (1から40の整数。0を設定すると自動設定になります。)
264         * @param   encMd    エンコードモード ('N':数字モード 'A':英数字モード 'B':8bit byteモード)
265         */
266        public void init( final String qrData,final String saveFile,final int version,final EncMode encMd ) {
267                init( qrData,saveFile,version,encMd,ErrCrct.DEF,IMAGE_TYPE,PIXEL );
268        }
269
270        /**
271         * 初期化メソッド。
272         *
273         * @param   qrData   エンコードする文字列(120Byte 以内)
274         * @param   saveFile 出力ファイル名
275         * @param   version  バージョン (1から40の整数。0を設定すると自動設定になります。)
276         * @param   encMd    エンコードモード('N':数字モード 'A':英数字モード 'B':8bit byteモード)
277         * @param   errCo    エラー訂正レベル ('L','M','Q','H')
278         * @param   imgType  イメージファイル形式(PNG/JPEG)
279         * @param   pixel    1セル辺りの塗りつぶしピクセル
280         */
281        public void init( final String qrData,final String saveFile,final int version,final EncMode encMd,
282                                                final ErrCrct errCo ,final String imgType,final int pixel ) {
283
284                this.qrData   = qrData;
285                this.saveFile = saveFile;
286                this.imgType  = imgType;
287                this.errCo    = errCo;
288                this.encMd    = encMd;
289                this.version  = version;
290                this.pixel    = pixel;
291
292                imageSize = ( MARGIN*2 + 17 + version*4 )*pixel;
293        }
294
295        /**
296         * 描画処理を行います。
297         *
298         * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更
299         *
300         * @param   gpx  Graphicsオブジェクト
301         */
302        @Override
303        public void paint( final Graphics gpx ) {
304                final Qrcode qrc =new Qrcode();
305                qrc.setQrcodeErrorCorrect(errCo.getCh());
306                qrc.setQrcodeEncodeMode(encMd.getCh());
307                qrc.setQrcodeVersion(version);
308
309                final byte[] dt =qrData.getBytes( HybsConst.DEFAULT_CHARSET );  // 6.4.2.0 (2016/01/29)
310                final boolean[][] sfg = qrc.calQrcode( dt );
311
312                final int size = sfg.length;
313                final int mgn  = MARGIN*pixel ;
314                for( int i=0; i<size; i++ ) {
315                        for( int j=0; j<size; j++ ) {
316                                if( sfg[j][i] ) {
317                                        gpx.fillRect( mgn+j*pixel,mgn+i*pixel,pixel,pixel );
318                                }
319                        }
320                }
321        }
322
323        /**
324         * 描画処理を行います。
325         *
326         */
327        public void saveImage() {
328                // 出力用イメージの生成
329                final BufferedImage image = new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_BGR );
330
331                // イメージからグラフィックコンテキストを取得
332                final Graphics grph = image.getGraphics();
333                grph.setColor( java.awt.Color.WHITE );
334                grph.fillRect( 0,0,imageSize, imageSize );
335                grph.setColor( java.awt.Color.BLACK );
336
337                // JEditorPane をイメージに書き込む
338                // paintComponent は proteced なので使用できない
339                paint( grph );
340
341                // 使い終わったグラフィックコンテキストを開放
342                grph.dispose();
343
344                try {
345                        // イメージの出力 Image I/O を使用
346                        ImageIO.write( image, imgType, new File( saveFile ) );
347                } catch( final IOException ex ) {
348                        final String errMsg = "イメージファイルの出力に失敗しました。"
349                                        + "File=[" + saveFile + "]" ;
350                        throw new OgRuntimeException( errMsg,ex );
351                }
352        }
353
354        /**
355         * メイン処理です。
356         * Usage: java org.opengion.fukurou.util.QrcodeImage Encode [SaevFile]
357         *
358         * @param  args  引数文字列配列
359         */
360        public static void main( final String[] args ) {
361                if( args.length == 0 ) {
362                        LogWriter.log( "Usage: java org.opengion.fukurou.util.QrcodeImage Encode [SaevFile]" );
363                        return ;
364                }
365
366                final String qrcode = args[0];
367                final String file   = args.length > 1 ? args[1] : "img.png";
368
369                final QrcodeImage qrImage = new QrcodeImage();
370                qrImage.init( qrcode,file );
371                qrImage.saveImage();
372        }
373
374        /**
375         * 内部データを標準出力へ出力します。
376         *
377         * @og.rev 7.2.1.0 (2020/03/13) QrcodeImage 見直し。
378         */
379        public static void printQcData() {
380//              final char[] strJ = new char[] { 'H','M' };
381                final char[] strJ = new char[] { 'L','M','Q','H' };                                                     // 7.2.1.0 (2020/03/13)
382                final char[] strK = new char[] { 'N','A','B' };
383
384                for( int i=0; i<QC_DATA.length; i++ ) {
385                        System.out.print( "version=[" + (i+1) + "] " );
386                        for( int j=0; j<QC_DATA[i].length; j++ ) {
387                                final char errCo = strJ[j];
388                                for( int k=0; k<QC_DATA[i][j].length; k++ ) {
389                                        System.out.print( errCo + strK[k] + "=[" + QC_DATA[i][j][k] + "] " );
390                                }
391                        }
392                        System.out.println();
393                }
394        }
395
396        /**
397         * バージョン情報を取得します。
398         *
399         * @param   errCo       エラー訂正レベル ('L','M','Q','H')
400         * @param       encMd   エンコードモード ('N':数字モード 'A':英数字モード 'B':8bit byteモード)
401         * @param       len             対象範囲
402         *
403         * @return      バージョン情報
404         */
405        public static int getVersion( final ErrCrct errCo, final EncMode encMd, final int len ) {
406                final int errCoInt = errCo.getNo() ;
407                final int encMdInt = encMd.getNo() ;
408
409                int rtn = -1;
410                for( int i=0; i<QC_DATA.length; i++ ) {
411                        if( QC_DATA[i][errCoInt][encMdInt] >= len ) {
412                                rtn = i;
413                                break;
414                        }
415                }
416
417                if( rtn < 0 ) {
418                        final String errMsg = "データ量が対象範囲を超えています。エラーレベルや、モードを調整してください。"
419                                                + "ErrCo:" + errCo + " EncMd:" + encMd + " len=[" + len + "]"
420                                                + " MaxLen=[" + QC_DATA[QC_DATA.length-1][errCoInt][encMdInt] + "]" ;
421                        throw new OgRuntimeException( errMsg );
422                }
423
424                return rtn + 1 ;
425        }
426
427        /**
428         * 最大サイズを取得します。
429         *
430         * @param       version バージョン情報
431         * @param   errCo       エラー訂正レベル ('L','M','Q','H')
432         * @param       encMd   エンコードモード ('N':数字モード 'A':英数字モード 'B':8bit byteモード)
433         *
434         * @return      最大サイズ
435         */
436        public static int getMaxSize( final int version, final ErrCrct errCo, final EncMode encMd ) {
437                final int errCoInt = errCo.getNo() ;
438                final int encMdInt = encMd.getNo() ;
439
440                return QC_DATA[version][errCoInt][encMdInt] ;
441        }
442}