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