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 は、3次元バーコードの 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 /** 1セル辺りの塗りつぶしピクセル 初期値:{@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 1セル辺りの塗りつぶしピクセル 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}