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.color.ColorSpace; 020import java.awt.color.ICC_ColorSpace; 021import java.awt.color.ICC_Profile; 022import java.awt.Color; // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係 023import java.awt.Font; // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係 024import java.awt.Graphics2D; // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係 025import java.awt.FontMetrics; // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係 026import java.awt.image.BufferedImage; 027import java.awt.image.ColorConvertOp; 028import java.awt.Transparency; // 7.0.1.1 (2018/10/22) 透過色処理 関係 029import java.io.File; 030import java.io.IOException; 031import java.io.InputStream; 032import java.io.ByteArrayOutputStream; 033import java.util.Locale; 034import java.util.Arrays; 035import javax.media.jai.JAI; 036 037import javax.imageio.ImageIO; 038import javax.imageio.IIOException; 039 040import com.sun.media.jai.codec.FileSeekableStream; 041import com.sun.media.jai.util.SimpleCMYKColorSpace; 042 043import org.opengion.fukurou.system.Closer; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 044import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 045 046/** 047 * ImageUtil は、画像ファイル関連の処理を集めたユーティリティクラスです。 048 * 049 * ここでは、イメージファイルを BufferedImage にして取り扱います。 050 * また、ImageResizer で処理していた static メソッドや、関連処理、 051 * org.opengion.hayabusa.servlet.MakeImage の主要な処理もこちらに持ってきます。 052 * 053 * @version 6.0.2.3 (2014/10/10) 054 * @author Hiroki Nakamura 055 * @since JDK6.0, 056 */ 057public final class ImageUtil { 058 059 private static final String ICC_PROFILE = "ISOcoated_v2_eci.icc"; // 5.5.3.4 (2012/06/19) 060 061 // 6.0.2.3 (2014/10/10) テキスト合成で指定できる設定値 062 /** X軸に対して、テキストを画像の左寄せで表示します。 **/ 063 public static final int LEFT = -1 ; 064 /** X軸に対して、テキストを画像の中央揃えで表示します。 **/ 065 public static final int CENTER = -2 ; 066 /** X軸に対して、テキストを画像の右寄せで表示します。 **/ 067 public static final int RIGHT = -3 ; 068 069 /** Y軸に対して、テキストを画像の上揃えで表示します。 **/ 070 public static final int TOP = -4 ; 071 /** Y軸に対して、テキストを画像の中央揃えで表示します。 **/ 072 public static final int MIDDLE = -5 ; 073 /** Y軸に対して、テキストを画像の下揃えで表示します。 **/ 074 public static final int BOTTOM = -6 ; 075 076 /** 入力画像の形式 **/ 077 public static final String READER_SUFFIXES ; // 5.6.5.3 (2013/06/28) 入力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp] 078 /** 出力画像の形式 **/ 079 public static final String WRITER_SUFFIXES ; // 5.6.5.3 (2013/06/28) 出力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp] 080 // 5.6.5.3 (2013/06/28) 入力画像,出力画像の形式 を ImageIO から取り出します。 081 static { 082 final String[] rfn = ImageIO.getReaderFileSuffixes(); 083 Arrays.sort( rfn ); 084 READER_SUFFIXES = Arrays.toString( rfn ); 085 086 final String[] wfn = ImageIO.getWriterFileSuffixes(); 087 Arrays.sort( wfn ); 088 WRITER_SUFFIXES = Arrays.toString( wfn ); 089 } 090 091 /** 092 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。 093 * 094 */ 095 private ImageUtil() {} 096 097 /** 098 * 入力ファイル名を指定し、画像オブジェクトを作成します。 099 * 100 * @og.rev 5.4.3.5 (2012/01/17) CMYK対応 101 * @og.rev 5.4.3.7 (2012/01/20) FAIでのファイル取得方法変更 102 * @og.rev 5.4.3.8 (2012/01/24) エラーメッセージ追加 103 * @og.rev 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。 104 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。 105 * 106 * @param fin 入力ファイル名 107 * @return 読み込まれた画像オブジェクト(BufferedImage) 108 */ 109 public static BufferedImage readFile( final String fin ) { 110 // 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。 111 if( !ImageUtil.isReaderSuffix( fin ) ) { 112 final String errMsg = "入力ファイルは" + READER_SUFFIXES + "のいずれかの形式のみ指定可能です。" 113 + "File=[" + fin + "]"; 114 throw new OgRuntimeException( errMsg ); 115 } 116 117 final File inFile = new File( fin ); 118 BufferedImage bi = null; 119 try { 120 bi = ImageIO.read( inFile ); 121 } 122 catch( final IIOException ex ) { // 5.4.3.5 (2012/01/17) 決めうち 123 // API的には、IllegalArgumentException と IOException しか記述されていない。 124 // 何もせずに、下の処理に任せます。 125 // 6.0.2.5 (2014/10/31) refactoring:Avoid empty catch blocks 警告対応 126 final String errMsg = "cmykToSRGB 処理が必要です。" + ex.getMessage(); 127 System.err.println( errMsg ); 128 } 129 catch( final IOException ex ) { 130 final String errMsg = "イメージファイルの読込に失敗しました。" + "File=[" + fin + "]"; 131 throw new OgRuntimeException( errMsg,ex ); 132 } 133 134 // 6.0.0.1 (2014/04/25) IIOException の catch ブロックからの例外出力を外に出します。 135 // bi == null は、結果のストリームを読み込みできないような場合、または、IO例外が発生した場合。 136 if( bi == null ) { 137 FileSeekableStream fsstream = null; 138 try{ 139 // 5.4.3.7 (2012/01/20) ファイルの開放がGC依存なので、streamで取得するように変更 140 // bi = cmykToSRGB(JAI.create("FileLoad",inFile.toString()).getAsBufferedImage(null,null)); 141 fsstream = new FileSeekableStream(inFile.getAbsolutePath()); 142 bi = cmykToSRGB(JAI.create("stream",fsstream).getAsBufferedImage(null,null)); 143 } 144 catch( final IOException ex ){ 145 final String errMsg = "イメージファイルの読込(JAI)に失敗しました。" + "File=[" + fin + "]"; 146 throw new OgRuntimeException( errMsg,ex ); 147 } 148 catch( final RuntimeException ex ) { // 5.4.3.8 (2012/01/23) その他エラーの場合追加 149 final String errMsg = "イメージファイルの読込(JAI)に失敗しました。ファイルが壊れている可能性があります。" + "File=[" + fin + "]"; 150 throw new OgRuntimeException( errMsg,ex ); 151 } 152 finally{ 153 Closer.ioClose(fsstream); 154 } 155 } 156 157 return bi; 158 } 159 160 /** 161 * 画像オブジェクト と、出力ファイル名を指定し、ファイルに書き込みます。 162 * 163 * ImageIO に指定する formatName(ファイル形式)は、出力ファイル名の拡張子から取得します。 164 * [bmp, gif, jpeg, jpg, png, wbmp] 位がサポートされています。 165 * 166 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 167 * 168 * @param image 出力する画像オブジェクト(BufferedImage) 169 * @param fout 出力ファイル名 170 */ 171 public static void saveFile( final BufferedImage image , final String fout ) { 172 final File outFile = new File( fout ); 173 try { 174 final String outSuffix = ImageUtil.getSuffix( fout ); 175 ImageIO.write( image, outSuffix, outFile ); 176 } 177 catch( final IOException ex ) { 178 final String errMsg = "イメージファイルの書き込みに失敗しました。" + "File=[" + fout + "]"; 179 throw new OgRuntimeException( errMsg,ex ); 180 } 181 } 182 183 /** 184 * 入力ファイル名を指定し、画像ファイルの byte配列を作成します。 185 * 186 * ImageIO に指定する formatName(ファイル形式)は、出力ファイル名の拡張子から取得します。 187 * [bmp, gif, jpeg, jpg, png, wbmp] 位がサポートされています。 188 * 189 * @og.rev 6.0.2.3 (2014/10/10) 新規作成 190 * 191 * @param fin 入力ファイル名 192 * @return 読み込まれた画像ファイルの byte配列 193 * @og.rtnNotNull 194 */ 195 public static byte[] byteImage( final String fin ) { 196 final ByteArrayOutputStream baOut = new ByteArrayOutputStream(); 197 198 final BufferedImage img = ImageUtil.readFile( fin ); 199 try { 200 final String suffix = ImageUtil.getSuffix( fin ); 201 ImageIO.write( img, suffix, baOut ); 202 } 203 catch( final IOException ex ) { 204 final String errMsg = "イメージファイルの読み込みに失敗しました。" + "File=[" + fin + "]"; 205 throw new OgRuntimeException( errMsg,ex ); 206 } 207 finally { 208 Closer.ioClose( baOut ); // ByteArrayOutputStreamを閉じても、何の影響もありません。 209 } 210 211 return baOut.toByteArray(); 212 } 213 214 /** 215 * ファイル名から拡張子(小文字)を求めます。 216 * 拡張子 が存在しない場合は、null を返します。 217 * 218 * @og.rev 5.6.5.3 (2013/06/28) private ⇒ public へ変更 219 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。 220 * 221 * @param fileName ファイル名 222 * 223 * @return 拡張子(小文字)。なければ、null 224 */ 225 public static String getSuffix( final String fileName ) { 226 String suffix = null; 227 if( fileName != null ) { 228 final int sufIdx = fileName.lastIndexOf( '.' ); 229 if( sufIdx >= 0 ) { 230 suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN ); 231 } 232 } 233 return suffix; 234 } 235 236 /** 237 * ファイル名から入力画像になりうるかどうかを判定します。 238 * コンストラクターの引数(入力画像)や、実際の処理の中(出力画像)で 239 * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。 240 * 241 * @og.rev 5.6.5.3 (2013/06/28) 新規追加 242 * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応 243 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。 244 * 245 * @param fileName ファイル名 246 * 247 * @return 入力画像として使用できるかどうか。できる場合は、true 248 */ 249 public static boolean isReaderSuffix( final String fileName ) { 250 final String suffix = getSuffix( fileName ); 251 252 return suffix != null && READER_SUFFIXES.indexOf( suffix ) >= 0 ; 253 } 254 255 /** 256 * ファイル名から出力画像になりうるかどうかを判定します。 257 * コンストラクターの引数(入力画像)や、実際の処理の中(出力画像)で 258 * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。 259 * 260 * @og.rev 5.6.5.3 (2013/06/28) 新規追加 261 * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応 262 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。 263 * 264 * @param fileName ファイル名 265 * 266 * @return 出力画像として使用できるかどうか。できる場合は、true 267 */ 268 public static boolean isWriterSuffix( final String fileName ) { 269 final String suffix = getSuffix( fileName ); 270 271 return suffix != null && WRITER_SUFFIXES.indexOf( suffix ) >= 0 ; 272 } 273 274 /** 275 * 色変換を行います。 276 * 変換元の色を、最初のビットから作ります。 277 * なお、スキャンしながら、色変換が行われなかった場合は、逆からスキャンします。 278 * つまり、背景色で輪郭抽出して、外周だけ透明にするという感じです。 279 * 280 * @og.rev 7.0.2.1 (2019/03/04) 元の色をイメージの端から自動取得(白決め打ちでない)属性追加 281 * 282 * @param img 変換対象のBufferedImage 283 * @param tCol 変換後の色 284 * @param mask 変換対象の色変動を抑えるためのマスク(0x00f0f0f0など) 285 */ 286 public static void changeColor( final BufferedImage img , final Color tCol , final int mask ) { 287 final int wd = img.getWidth(); 288 final int ht = img.getHeight(); 289 final int fc = img.getRGB( 0,0 ) & mask; // 変換元のRGB値は、一番端のピクセル 290 final int tc = tCol.getRGB(); // 変換後のRGB値。例:new Color( 255,255,255,0 ) なら、透明 291 292 for( int y=0; y<ht; y++ ) { 293 boolean isRev = false; 294 for( int x=0; x<wd; x++ ) { 295 final int ic = img.getRGB( x,y ) & mask; 296 if( ic == fc ) { // 変換色チェック 297 img.setRGB( x,y,tc ); 298 } 299 else { 300 isRev = true; // 反転処理を行う。 301 break; // 変換ができなかった。= 境界線 302 } 303 } 304 if( isRev ) { 305 for( int x=wd-1; x>=0; x-- ) { 306 final int ic = img.getRGB( x,y ) & mask; 307 if( ic == fc ) { // 変換色チェック 308 img.setRGB( x,y,tc ); 309 } 310 else { 311 break; // 変換ができなかった。= 境界線 312 } 313 } 314 } 315 } 316 } 317 318 /** 319 * 色変換を行います。 320 * 例えば、背景色白を、透明に変換するなどです。 321 * 322 * ボーダー色は、背景色と異なる色の場合があるため、特別に用意しています。 323 * ボーダーは、画像の周辺、3px を対象とします。 324 * 325 * @og.rev 6.0.2.3 (2014/10/10) 新規追加 326 * @og.rev 7.0.1.0 (2018/10/15) 色変換に、元の色の変動を吸収するマスク属性追加 327 * 328 * @param img 変換対象のBufferedImage 329 * @param fCol 変換対象の色 330 * @param tCol 変換後の色 331 * @param mask 変換対象の色変動を抑えるためのマスク(0x00f0f0f0など) 332 */ 333 public static void changeColor( final BufferedImage img , final Color fCol , final Color tCol , final int mask ) { 334 final int wd = img.getWidth(); 335 final int ht = img.getHeight(); 336 final int fc = fCol.getRGB() & mask; // 変換元のRGB値。 337 final int tc = tCol.getRGB(); // 変換後のRGB値。例:new Color( 255,255,255,0 ) なら、透明 338 339 for( int y=0; y<ht; y++ ) { 340 for( int x=0; x<wd; x++ ) { 341 final int ic = img.getRGB( x,y ) & mask; 342 if( ic == fc ) { // 変換色チェック 343 img.setRGB( x,y,tc ); 344 } 345 } 346 } 347 } 348 349 /** 350 * BufferedImageをISOCoatedのICCプロファイルで読み込み、RGBにした結果を返します。 351 * (CMYKからRBGへの変換、ビット反転) 352 * なお、ここでは、外部の ICC_PROFILE(ISOcoated_v2_eci.icc) を利用して、処理速度アップを図りますが、 353 * 存在しない場合、標準の、com.sun.media.jai.util.SimpleCMYKColorSpace を利用しますので、エラーは出ません。 354 * ただし、ものすごく遅いため、実用的ではありません。 355 * ISOcoated_v2_eci.icc ファイルは、zip圧縮して、拡張子をjar に変更後、(ISOcoated_v2_eci.jar) 356 * javaエクステンション((JAVA_HOME\)jre\lib\ext) にコピーするか、実行時に、CLASSPATHに設定します。 357 * 358 * @og.rev 5.4.3.5 (2012/01/17) 359 * @og.rev 5.5.3.4 (2012/06/19) ICC_PROFILE の取得先を、ISOcoated_v2_eci.icc に変更 360 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。(static にして) 361 * 362 * @param readImage BufferedImageオブジェクト 363 * 364 * @return 変換後のBufferedImage 365 * @throws IOException 入出力エラーが発生したとき 366 */ 367 public static BufferedImage cmykToSRGB( final BufferedImage readImage ) throws IOException { 368 final ClassLoader loader = Thread.currentThread().getContextClassLoader(); 369 final InputStream icc_stream = loader.getResourceAsStream( ICC_PROFILE ); 370 371 // 5.5.3.4 (2012/06/19) ICC_PROFILE が存在しない場合は、標準のSimpleCMYKColorSpace を使用。 372 ColorSpace cmykCS = null; 373 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 374 if( icc_stream == null ) { 375 // 遅いので標準のスペースは使えない 376 final String errMsg = ICC_PROFILE + " が見つかりません。" + CR 377 + " CLASSPATHの設定されている場所に配備してください。" + CR 378 + " 標準のSimpleCMYKColorSpaceを使用しますのでエラーにはなりませんが、非常に遅いです。" ; 379 System.out.println( errMsg ); 380 cmykCS = SimpleCMYKColorSpace.getInstance(); 381 } 382 else { 383 final ICC_Profile prof = ICC_Profile.getInstance(icc_stream); //変換プロファイル 384 cmykCS = new ICC_ColorSpace(prof); 385 } 386 387 final BufferedImage rgbImage = new BufferedImage(readImage.getWidth(),readImage.getHeight(), BufferedImage.TYPE_INT_RGB); 388 final ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace(); 389 final ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null); 390 cmykToRgb.filter(readImage, rgbImage); 391 392 final int width = rgbImage.getWidth(); 393 final int height = rgbImage.getHeight(); 394 // 反転が必要 395 for( int i=0;i<width;i++ ) { 396 for( int j=0;j<height;j++ ) { 397 int rgb = rgbImage.getRGB(i, j); 398 final int rr = (rgb & 0xff0000) >> 16; 399 final int gg = (rgb & 0x00ff00) >> 8; 400 final int bb = rgb & 0x0000ff ; 401 rgb = (Math.abs(rr - 255) << 16) + (Math.abs(gg - 255) << 8) + (Math.abs(bb - 255)); 402 rgbImage.setRGB(i, j, rgb); 403 } 404 } 405 406 return rgbImage; 407 } 408 409 /** 410 * 画像イメージに、文字列を動的に合成作成して返します。 411 * 412 * 描画指定の位置(x,y)は、テキストの左下の位置を、画像イメージの、左上を起点(0,0)とした 413 * 位置になります。 414 * maxW , maxH を指定すると、テキストのフォントサイズをその範囲に収まるように自動調整します。 415 * 416 * @og.rev 6.0.2.3 (2014/10/10) 新規追加 417 * 418 * @param image 合成する元の画像オブジェクト 419 * @param text 描画される文字列 420 * @param xAxis テキストが描画される位置のx座標。または、{@link #LEFT LEFT},{@link #CENTER CENTER},{@link #RIGHT RIGHT} 指定で、自動計算する。 421 * @param yAxis テキストが描画される位置のy座標。または、{@link #TOP TOP},{@link #MIDDLE MIDDLE},{@link #BOTTOM BOTTOM} 指定で、自動計算する。 422 * @param maxW テキストの最大幅(imageの幅と比較して小さい方の値。0以下の場合は、imageの幅) 423 * @param maxH テキストの最大高さ(imageの高さと比較して小さい方の値。0以下の場合は、imageの高さ) 424 * @param font 描画されるテキストのフォント。null の場合は、初期値(Dialog.plain,12px)が使われる 425 * @param color 描画されるテキストの色(Color)。null の場合は、Color.BLACK が使われる 426 * 427 * @return 合成された画像オブジェクト(BufferedImage) 428 * @og.rtnNotNull 429 * @see #mixImage( BufferedImage, String, int, int, Font, Color ) 430 */ 431 public static BufferedImage mixImage( final BufferedImage image, 432 final String text, final int xAxis, final int yAxis, final int maxW, final int maxH, 433 final Font font, final Color color ) { 434 435 final int imgWidth = image.getWidth(); // 画像の幅 436 final int imgHeight = image.getHeight(); // 画像の高さ 437 438 final int maxWidth = maxW <= 0 ? imgWidth : Math.min( maxW,imgWidth ); 439 final int maxHeight = maxH <= 0 ? imgHeight : Math.min( maxH,imgHeight ); 440 441 final Graphics2D gph = image.createGraphics(); 442 if( font != null ) { gph.setFont( font ); } // new Font("Serif", Font.BOLD, 14) 443 444 float size = 5.0f; // 小さすぎると見えないので、開始はこれくらいから行う。 445 final float step = 0.5f; // 刻み幅 446 while( true ) { 447 final Font tmpFont = gph.getFont().deriveFont( size ); 448 gph.setFont( tmpFont ); 449 450 final FontMetrics fm = gph.getFontMetrics(); 451 final int txtWidth = fm.stringWidth( text ); 452 final int txtHeight = fm.getAscent(); 453 454 if( maxWidth < txtWidth || maxHeight < txtHeight ) { 455 size -= step; // 一つ戻しておく。場合によっては、step分戻して、stepを小さくして続ける方法もある。 456 break; 457 } 458 size += step; 459 } 460 final Font newFont = gph.getFont().deriveFont( size ); 461 462 return mixImage( image, text, xAxis, yAxis, newFont, color ); 463 } 464 465 /** 466 * 画像イメージに、文字列を動的に合成作成して返します。 467 * 468 * 描画指定の位置(x,y)は、テキストの左下の位置を、画像イメージの、左上を起点(0,0)とした 469 * 位置になります。 470 * 471 * @og.rev 6.0.2.3 (2014/10/10) org.opengion.hayabusa.servlet.MakeImage から、移植しました。 472 * 473 * @param image 合成する元の画像オブジェクト 474 * @param text 描画される文字列 475 * @param xAxis テキストが描画される位置のx座標。または、{@link #LEFT LEFT},{@link #CENTER CENTER},{@link #RIGHT RIGHT} 指定で、自動計算する。 476 * @param yAxis テキストが描画される位置のy座標。または、{@link #TOP TOP},{@link #MIDDLE MIDDLE},{@link #BOTTOM BOTTOM} 指定で、自動計算する。 477 * @param font 描画されるテキストのフォント。null の場合は、初期値(Dialog.plain,12px)が使われる 478 * @param color 描画されるテキストの色(Color)。null の場合は、Color.BLACK が使われる 479 * 480 * @return 合成された画像オブジェクト(BufferedImage) 481 * @og.rtnNotNull 482 * @see #mixImage( BufferedImage, String, int, int, int, int, Font, Color ) 483 */ 484 public static BufferedImage mixImage( final BufferedImage image, 485 final String text, final int xAxis, final int yAxis, 486 final Font font, final Color color ) { 487 488 final Graphics2D gph = image.createGraphics(); 489 490 // gph.setRenderingHint( java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); 491 492 if( font != null ) { gph.setFont( font ); } // new Font("Serif", Font.BOLD, 14) 493 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 494 if( color == null ) { gph.setColor( Color.BLACK ); } // new Color(0,0,255) など 495 else { gph.setColor( color ); } 496 497 // 実際の位置ではなく、X軸が、LEFT,CENTER,RIGHT 等の指定 498 int x1 = xAxis ; 499 if( x1 < 0 ) { 500 final int imgWidth = image.getWidth(); // 画像の幅 501 final FontMetrics fm = gph.getFontMetrics(); 502 final int txtWidth = fm.stringWidth( text ); // テキストの長さ 503 504 switch( x1 ) { 505 case LEFT : x1 = 0; // 左寄せなので、0 506 break; 507 case CENTER : x1 = imgWidth/2 - txtWidth/2; // 画像の中心から、テキストの中心を引き算 508 break; 509 case RIGHT : x1 = imgWidth - txtWidth; // 右寄せは、画像の右端からテキスト分を引き算 510 break; 511 default : 512 final String errMsg = "X軸 で範囲外のデータが指定されました。" + "text=[" + text + "]" 513 + " (x,y)=[" + xAxis + "," + yAxis + "]" ; 514 throw new OgRuntimeException( errMsg ); 515 // break; 制御は移りません。 516 } 517 } 518 519 // 実際の位置ではなく、Y軸が、TOP,MIDDLE,BOTTOM 等の指定 520 final int Ydef = 2 ; // 良く判らないが、位置合わせに必要。 521 int y1 = yAxis ; 522 if( y1 < 0 ) { 523 final int imgHeight = image.getHeight() -Ydef; // 画像の高さ 524 final FontMetrics fm = gph.getFontMetrics(); 525 final int txtHeight = fm.getAscent() -Ydef; // テキストの幅(=Ascent) 526 527 switch( y1 ) { 528 case TOP : y1 = txtHeight; // 上寄せは、テキストの幅分だけ下げる 529 break; 530 case MIDDLE : y1 = (imgHeight)/2 + (txtHeight)/2 ; // 画像の中心から、テキストの中心分下げる(加算) 531 break; 532 case BOTTOM : y1 = imgHeight; // 下寄せは、画像の高さ分-2 533 break; 534 default : 535 final String errMsg = "Y軸 で範囲外のデータが指定されました。" + "text=[" + text + "]" 536 + " (x,y)=[" + xAxis + "," + yAxis + "]" ; 537 throw new OgRuntimeException( errMsg ); 538 // break; 制御は移りません。 539 } 540 } 541 542 gph.drawString( text, x1, y1 ); 543 gph.dispose(); // グラフィックス・コンテキストを破棄 544 545 return image; 546 } 547 548 /** 549 * アプリケーションのサンプルです。 550 * 551 * 入力イメージファイルを読み取って、テキストを合成して、出力イメージファイル に書き込みます。 552 * テキストの挿入位置を、X軸、Y軸で指定します。 553 * X軸とY軸には、特別な記号があり、左寄せ、右寄せ等の指示が可能です。 554 * 555 * サンプルでは、new Font("Serif", Font.PLAIN, 14); と、new Color(0,0,255);(青色)を固定で渡しています。 556 * 557 * Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル 558 * -mix テキスト X軸 Y軸 [-fname=フォント名 -fstyle=スタイル -fsize=サイズ -color=カラー] 559 * X軸 指定(正の値は実際の位置) 560 * -1 ・・・ LEFT 左寄せ 561 * -2 ・・・ CENTER 中央揃え 562 * -3 ・・・ RIGHT 右寄せ 563 * 564 * Y軸 指定(正の値は実際の位置) 565 * -4 ・・・ TOP 上揃え 566 * -5 ・・・ MIDDLE 中央揃え 567 * -6 ・・・ BOTTOM 下揃え 568 * 569 * -fname=フォント名(初期値:Serif) 570 * Serif , SansSerif , Monospaced , Dialog , DialogInput 571 * 572 * -fstyle=スタイル(初期値:0:PLAIN)を、数字で選びます。 573 * 0:PLAIN , 1:BOLD , 2:ITALIC 574 * 575 * -fsize=サイズ(初期値:14) 576 * フォントサイズを整数で指定します。 577 * 578 * -color=カラー 579 * 色を表す文字列(BLUE,GREEN か、#808000 などの16bitRGB表記) 580 * 581 * Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル 582 * -trans [-color=カラー -alpha=透過率(0-100%)] 583 * -color=カラー(初期値:WHITE) 584 * 透明色にする色を指定(BLUE,GREEN か、#808000 などの16bitRGB表記) 585 * 586 * -alpha=透過率(0-100%)(初期値:0) 587 * 透過率は、0:透明から100不透明まで指定します。 588 * 589 * -mask=元の色にマスクを16進数24Bitで指定します(初期値:00f0f0f0) 590 * 591 * -useBGColor 透明色にする色を元の一番端の色を使用する(初期値:false) 592 * 593 * @og.rev 6.4.5.1 (2016/04/28) mainメソッドの起動方法を変更します。 594 * @og.rev 7.0.1.0 (2018/10/15) 色変換に、元の色の変動を吸収するマスク属性追加 595 * @og.rev 7.0.2.1 (2019/03/04) 元の色をイメージの端から自動取得(白決め打ちでない)属性追加 596 * 597 * @param args 引数文字列配列 入力ファイル、出力ファイル、縦横最大サイズ 598 */ 599 public static void main( final String[] args ) { 600 if( args.length < 3 ) { 601 final String usage = "Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル\n" + 602 " -mix テキスト X軸 Y軸 [-fname=フォント名 -fstyle=スタイル -fsize=サイズ -color=カラー]\n" + 603 "\tX軸とY軸には、特別な記号があり、左寄せ、右寄せ等の指示が可能です。\n" + 604 "\t X軸 指定(正の値は実際の位置)\n" + 605 "\t -1 ・・・ LEFT 左寄せ\n" + 606 "\t -2 ・・・ CENTER 中央揃え\n" + 607 "\t -3 ・・・ RIGHT 右寄せ\n" + 608 "\t\n" + 609 "\t Y軸 指定(正の値は実際の位置)\n" + 610 "\t -4 ・・・ TOP 上揃え\n" + 611 "\t -5 ・・・ MIDDLE 中央揃え\n" + 612 "\t -6 ・・・ BOTTOM 下揃え\n" + 613 "\t\n" + 614 "\t -fname=フォント名(初期値:Serif)\n" + 615 "\t Serif , SansSerif , Monospaced , Dialog , DialogInput\n" + 616 "\t\n" + 617 "\t -fstyle=スタイル(初期値:0:PLAIN)\n" + 618 "\t 0:PLAIN , 1:BOLD , 2:ITALIC\n" + 619 "\t\n" + 620 "\t -fsize=サイズ(初期値:14)\n" + 621 "\t フォントサイズを整数で指定\n" + 622 "\t\n" + 623 "\t -color=カラー\n" + 624 "\t 色を表す文字列(BLUE,GREEN か、#808000 などの16bitRGB表記)\n" + 625 "\t\n" + 626 "Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル\n" + 627 " -trans [-color=カラー -alpha=透過率(0-100%)]\n" + 628 "\t -color=カラー\n" + 629 "\t 透明色にする色を指定(BLUE,GREEN か、#808000 などの16bitRGB表記)" + 630 "\t -alpha=透過率(0-100%)\n" + 631 "\t 透過率は、0:透明から100不透明まで指定します。\n" + 632 "\t -mask=元の色にマスクを16進数24Bitで指定します(初期値:00f0f0f0)\n" + // 7.0.1.0 (2018/10/15) 色変換に、マスク属性追加 633 "\t -useBGColor 透明色にする色を元の一番端の色を使用する(初期値:false)\n" ; // 7.0.2.1 (2019/03/04) 透明色にする色を端から取得 634 System.out.println( usage ); 635 return ; 636 } 637 638 final String inImg = args[0]; 639 final String outImg = args[1]; 640 final String imgType= args[2]; 641 642// final boolean isMix = imgType.equals( "-mix" ); // 文字列合成 643// final boolean isTrn = imgType.equals( "-trans" ); // 透過色指定 644 645 final BufferedImage image = ImageUtil.readFile( inImg ); 646 647 final boolean isMix = imgType.equals( "-mix" ); // 文字列合成 648 if( isMix ) { 649 final String text = args[3]; 650 final int x = Integer.parseInt( args[4] ); 651 final int y = Integer.parseInt( args[5] ); 652 653 String fname = "Serif"; 654 int fstyle = Font.PLAIN; // =0; 655 int fsize = 14; 656 Color color = Color.BLUE; 657 658 for( int i=6; i<args.length; i++ ) { 659 if( args[i].startsWith( "-fname=" ) ) { fname = args[i].substring( 7 ); } // 7 = "-fname=".length() 660 if( args[i].startsWith( "-fstyle=" ) ) { fstyle = Integer.parseInt( args[i].substring( 8 ) ); } // 8 = "-fstyle=".length() 661 if( args[i].startsWith( "-fsize=" ) ) { fsize = Integer.parseInt( args[i].substring( 7 ) ); } // 7 = "-fsize=".length() 662 if( args[i].startsWith( "-color=" ) ) { color = ColorMap.getColorInstance( args[i].substring( 7 ) ); } // 7 = "-color=".length() 663 } 664 665 // 6.9.8.0 (2018/05/28) FindBugs:条件は効果がない 666// if( isMix ) { 667 final Font font = new Font( fname, fstyle, fsize ); 668 ImageUtil.mixImage( image , text , x , y , font , color ); 669// } 670 ImageUtil.saveFile( image , outImg ); 671 } 672 673 final boolean isTrn = imgType.equals( "-trans" ); // 透過色指定 674 675 if( isTrn ) { 676 Color fColor = Color.WHITE; // 初期値は、白を透明に変換する。 677 int alpha = 0; 678 int mask = 0x00f0f0f0; // 7.0.1.0 (2018/10/15) 色変換時の誤差を吸収 679 boolean useBGcol= false; // 7.0.2.1 (2019/03/04) 680// boolean debug = false; 681 682 for( int i=3; i<args.length; i++ ) { 683 if( args[i].startsWith( "-color=" ) ) { fColor = ColorMap.getColorInstance( args[i].substring( 7 ) ); } // 7 = "-color=".length() 684 if( args[i].startsWith( "-alpha=" ) ) { alpha = 255/100 * Integer.parseInt( args[i].substring( 7 ) ); } // 7 = "-alpha=".length() 685 if( args[i].startsWith( "-mask=" ) ) { mask = Integer.parseInt( args[i].substring( 6 ) , 16 ); } // 6 = "-mask=".length() 686 if( args[i].startsWith( "-useBGColor" ) ) { useBGcol = true; } // あればtrue 7.0.2.1 (2019/03/04) 687 } 688 689 final Color tColor = new Color( fColor.getRed() , fColor.getGreen() , fColor.getBlue() , alpha ); 690 691 // 元のPNGが、完全な不透明だと、アルファ地設定が無視されるので、BufferedImage を作り直す必要がある。 692 final BufferedImage transImg ; 693 if( Transparency.OPAQUE == image.getTransparency() ) { // 完全に不透明 694 final int wd = image.getWidth(); 695 final int ht = image.getHeight(); 696 final int[] px = image.getRGB( 0,0, wd, ht, null, 0, wd ); 697 698 transImg = new BufferedImage( wd,ht,BufferedImage.TYPE_INT_ARGB ); // 透明を持てる 699 transImg.setRGB( 0,0, wd, ht, px, 0 , wd ); 700 } 701 else { 702 transImg = image; 703 } 704 705 // 7.0.2.1 (2019/03/04) 元の色をイメージの端から自動取得(白決め打ちでない)属性追加 706 if( useBGcol ) { 707 System.out.println( inImg + " : 端色 → " + tColor + " 変換" ); 708 ImageUtil.changeColor( transImg , tColor , mask ); // 7.0.2.1 (2019/03/04) 709 } 710 else { 711 System.out.println( inImg + " : " + fColor + " → " + tColor + " 変換" ); 712 ImageUtil.changeColor( transImg , fColor , tColor , mask ); // 7.0.1.0 (2018/10/15) 713 } 714 715 ImageUtil.saveFile( transImg , outImg ); 716 } 717 } 718}