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