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