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