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 */ 016 package org.opengion.fukurou.util; 017 018 import java.awt.color.ColorSpace; 019 import java.awt.color.ICC_ColorSpace; 020 import java.awt.color.ICC_Profile; 021 import java.awt.geom.AffineTransform; 022 import java.awt.image.AffineTransformOp; 023 import java.awt.image.BufferedImage; 024 import java.awt.image.ColorConvertOp; 025 import java.io.File; 026 import java.io.IOException; 027 import java.io.InputStream; 028 import java.util.Locale; 029 import java.util.Arrays; 030 import javax.media.jai.JAI; 031 032 import javax.imageio.ImageIO; 033 034 import com.sun.media.jai.codec.FileSeekableStream; 035 import com.sun.media.jai.util.SimpleCMYKColorSpace; 036 037 /** 038 * ImageResizer は、画像ファイルのリサイズを行うためのクラスです? 039 * ここでの使?は、?期化時に、オリジナルの画像ファイルを指定し? 040 * 変換時に?小方法に対応したメソ?を呼び出し?画像を変換します? 041 * 変換方法としては、以下?3つがあります? 042 * ?大サイズ(px)?による変換 043 * 縦横の?サイズ(px)を指定し、変換を行います? 044 * 横長の画像につ?は、変換後?横??サイズとなり?縦?つ?は、横?? 045 * 縮小率に従って決定されます? 046 * ?縦長の画像につ?は、変換後?縦??サイズとなり?横?つ?は、縦?? 047 * 縮小率に従って決定されます? 048 * ②縦横サイズ(px)?による変換 049 * 縦横の変換後?サイズ(px)を?別に?し、変換を行います? 050 * ③縮小率?による変換 051 * "1"を?サイズとする縮小率を指定し、変換を行います? 052 * 縮小率は、縦横で同じ縮小率が適用されます? 053 * 入力フォーマットとしてはJPEG/PNG/GIFに、?力フォーマットとしてはJPEG/PNGに対応して?す? 054 * 出力フォーマットにつ?は、?力ファイル名?拡張子より?動的に決定されますが、??は 055 * サイズが小さくなるjpegファイルを推奨します? 056 * 入出力フォーマットにつ?、対応して??ォーマットが?された場合?例外が発生します? 057 * また?縦横の出力サイズが?力サイズの縦横よりも両方大きい場合?変換は行われず、?力ファイル? 058 * そ?ままコピ?されて出力されます?(拡大変換は行われません) 059 * 060 * @version 4.0 061 * @author Hiroki Nakamura 062 * @since JDK5.0, 063 */ 064 public class ImageResizer { 065 private static final String CR = System.getProperty("line.separator"); // 5.5.3.4 (2012/06/19) 066 067 // private static final String ICC_PROFILE = "org/opengion/fukurou/util/ISOcoated_v2_eci.icc"; // 5.4.3.5 (2012/01/17) 068 private static final String ICC_PROFILE = "ISOcoated_v2_eci.icc"; // 5.5.3.4 (2012/06/19) 069 070 private final File inFile; 071 private final BufferedImage inputImage; // 入力画像オブジェク? 072 073 private final int inSizeX; // 入力画像?横サイズ 074 private final int inSizeY; // 入力画像?縦サイズ 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 String[] rfn = ImageIO.getReaderFileSuffixes(); 081 Arrays.sort( rfn ); 082 READER_SUFFIXES = Arrays.toString( rfn ); 083 084 String[] wfn = ImageIO.getWriterFileSuffixes(); 085 Arrays.sort( wfn ); 086 WRITER_SUFFIXES = Arrays.toString( wfn ); 087 } 088 089 /** 090 * 入力ファイル名を?し、画像縮小オブジェクトを初期化します? 091 * 092 * @og.rev 5.4.3.5 (2012/01/17) CMYK対? 093 * @og.rev 5.4.3.7 (2012/01/20) FAIでのファイル取得方法変更 094 * @og.rev 5.4.3.8 (2012/01/24) エラーメ?ージ追? 095 * @og.rev 5.6.5.3 (2013/06/28) 入力画像?形??ImageIO から取り出します? 096 * 097 * @param in 入力ファイル? 098 */ 099 public ImageResizer( final String in ) { 100 // String inSuffix = getSuffix( in ); 101 BufferedImage bi; 102 // 5.6.5.3 (2013/06/28) 入力画像?形??ImageIO から取り出します? 103 // if( "|jpeg|jpg|png|gif|".indexOf( inSuffix ) < 0 ) { 104 if( !isReaderSuffix( in ) ) { 105 // String errMsg = "入力ファイルは(JPEG|PNG|GIF)の?れかの形式?み?可能です?" + "File=[" + in + "]"; 106 String errMsg = "入力ファイルは" + READER_SUFFIXES + "の?れかの形式?み?可能です?" + "File=[" + in + "]"; 107 throw new RuntimeException( errMsg ); 108 } 109 110 inFile = new File( in ); 111 try { 112 // inputImage = ImageIO.read( inFile ); 113 bi = ImageIO.read( inFile ); 114 } 115 catch (javax.imageio.IIOException ex){ // 5.4.3.5 (2012/01/17) 決めうち 116 FileSeekableStream fsstream = null; 117 try{ 118 // 5.4.3.7 (2012/01/20) ファイルの開放がGC依存なので、streamで取得するよ?変更 119 // bi = cmykToSRGB(JAI.create("FileLoad",inFile.toString()).getAsBufferedImage(null,null)); 120 fsstream = new FileSeekableStream(inFile.getAbsolutePath()); 121 bi = cmykToSRGB(JAI.create("stream",fsstream).getAsBufferedImage(null,null)); 122 } 123 catch( IOException ioe ){ 124 String errMsg = "イメージファイルの読込(JAI)に失敗しました? + "File=[" + in + "]"; 125 throw new RuntimeException( errMsg,ioe ); 126 } 127 catch( Exception oe ){ // 5.4.3.8 (2012/01/23) そ?他エラーの場合追? 128 String errMsg = "イメージファイルの読込(JAI)に失敗しました。ファイルが壊れて?可能性があります?" + "File=[" + in + "]"; 129 throw new RuntimeException( errMsg,oe ); 130 } 131 finally{ 132 Closer.ioClose(fsstream); 133 } 134 135 } 136 catch( IOException ex ) { 137 String errMsg = "イメージファイルの読込に失敗しました? + "File=[" + in + "]"; 138 throw new RuntimeException( errMsg,ex ); 139 } 140 141 inputImage = bi; 142 inSizeX = inputImage.getWidth(); 143 inSizeY = inputImage.getHeight(); 144 } 145 146 /** 147 * 縦横の?サイズ(px)を指定し、変換を行います? 148 * 横長の画像につ?は、変換後?横??サイズとなり?縦?つ?は、横?? 149 * 縮小率に従って決定されます? 150 * ?縦長の画像につ?は、変換後?縦??サイズとなり?横?つ?は、縦?? 151 * 縮小率に従って決定されます? 152 * 153 * @param out 出力ファイル? 154 * @param maxSize 変換後?縦横の?サイズ 155 */ 156 public void resizeByPixel( final String out, final int maxSize ) { 157 int sizeX = 0; 158 int sizeY = 0; 159 if( inSizeX > inSizeY ) { 160 sizeX = maxSize; 161 sizeY = inSizeY * maxSize / inSizeX; 162 } 163 else { 164 sizeX = inSizeX * maxSize / inSizeY; 165 sizeY = maxSize; 166 } 167 convert( inputImage, out, sizeX, sizeY ); 168 } 169 170 /** 171 * 縦横の変換後?サイズ(px)を?別に?し、変換を行います? 172 * 173 * @param out 出力ファイル? 174 * @param sizeX 変換後?横サイズ(px) 175 * @param sizeY 変換後?縦サイズ(px) 176 */ 177 public void resizeByPixel( final String out, final int sizeX, final int sizeY ) { 178 convert( inputImage, out, sizeX, sizeY ); 179 } 180 181 /** 182 * "1"を?サイズとする縮小率を指定し、変換を行います? 183 * 縮小率は、縦横で同じ縮小率が適用されます? 184 * 185 * @param out 出力ファイル? 186 * @param ratio 縮小率 187 */ 188 public void resizeByRatio( final String out, final double ratio ) { 189 int sizeX = (int)( inSizeX * ratio ); 190 int sizeY = (int)( inSizeY * ratio ); 191 convert( inputImage, out, sizeX, sizeY ); 192 } 193 194 /** 195 * 画像?変換を行うための?共通メソ?です? 196 * 197 * @og.rev 5.4.1.0 (2011/11/01) 画像によってgetType?を返し、エラーになる不?合を修正 198 * @og.rev 5.6.5.3 (2013/06/28) 出力画像?形??ImageIO から取り出します? 199 * @og.rev 5.6.5.3 (2013/06/28) 5.6.6.1 (2013/07/12) getSuffix するタイミングを後ろにする? 200 * @og.rev 5.6.6.1 (2013/07/12) 拡張子?変更がある?で、変換しな????、な?? 201 * 202 * @param inputImage 入力画像オブジェク? 203 * @param out 出力ファイル? 204 * @param sizeX 横サイズ(px) 205 * @param sizeY 縦サイズ(px) 206 */ 207 private void convert( final BufferedImage inputImage, final String out, final int sizeX, final int sizeY ) { 208 // 5.6.6.1 (2013/07/12) getSuffix するタイミングを後ろにする? 209 // String outSuffix = getSuffix( out ); 210 // 5.6.5.3 (2013/06/28) 出力画像?形??ImageIO から取り出します? 211 // if( "|jpeg|jpg|png|".indexOf( outSuffix ) < 0 ) { 212 if( !isWriterSuffix( out ) ) { 213 // String errMsg = "出力ファイルは(JPEG|PNG)の?れかの形式?み?可能です?" + "File=[" + out + "]"; 214 String errMsg = "出力ファイルは" + WRITER_SUFFIXES + "の?れかの形式?み?可能です?" + "File=[" + out + "]"; 215 throw new RuntimeException( errMsg ); 216 } 217 218 File outFile = new File( out ); 219 // 5.6.6.1 (2013/07/12) 拡張子?変更がある?で、変換しな????、な?? 220 // 変換後?縦横サイズが大きい場合?コピ?して終わ?変換しな? 221 // if( sizeX > inSizeX && sizeY > inSizeY ) { 222 // FileUtil.copy( inFile, outFile ); 223 // return; 224 // } 225 226 // 5.4.1.0 (2011/11/01) 画像によってgetType?を返し、エラーになる不?合を修正 227 int type = inputImage.getType(); 228 BufferedImage resizeImage = null; 229 if( type == 0 ) { 230 resizeImage = new BufferedImage( sizeX, sizeY, BufferedImage.TYPE_4BYTE_ABGR_PRE ); 231 } 232 else { 233 resizeImage = new BufferedImage( sizeX, sizeY, inputImage.getType() ); 234 } 235 AffineTransformOp ato = null; 236 ato = new AffineTransformOp( 237 AffineTransform.getScaleInstance( 238 (double)sizeX/inSizeX, (double)sizeY/inSizeY ), null ); 239 ato.filter( inputImage, resizeImage ); 240 241 try { 242 // 5.6.6.1 (2013/07/12) getSuffix するタイミングを後ろにする? 243 String outSuffix = getSuffix( out ); 244 ImageIO.write( resizeImage, outSuffix, outFile ); 245 } 246 catch( IOException ex ) { 247 String errMsg = "イメージファイルの作?に失敗しました? + "File=[" + out + "]"; 248 throw new RuntimeException( errMsg,ex ); 249 } 250 } 251 252 /** 253 * ファイル名から拡張?小文?を求めます? 254 * 拡張?が存在しな??合?、null を返します? 255 * 256 * @og.rev 5.6.5.3 (2013/06/28) private ?public へ変更 257 * 258 * @param fileName ファイル? 259 * 260 * @return 拡張?小文?。なければ、null 261 */ 262 // private static String getSuffix( final String fileName ) { 263 public static String getSuffix( final String fileName ) { 264 String suffix = null; 265 if( fileName != null ) { 266 int sufIdx = fileName.lastIndexOf( '.' ); 267 if( sufIdx >= 0 ) { 268 suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN ); 269 // if( suffix.length() > 5 ) { 270 // suffix = null; 271 // } 272 } 273 } 274 return suffix; 275 } 276 277 /** 278 * ファイル名から?力画像になりうるかど?を判定します? 279 * コンストラクターの引数??力画像)や、実際の処??中??力画像)で 280 * 、変換対象となるかど?をチェ?して?すが、それを事前に確認できるようにします? 281 * 282 * @og.rev 5.6.5.3 (2013/06/28) 新規追? 283 * @og.rev 5.6.6.1 (2013/07/12) getSuffix ?null を返すケースへの対? 284 * 285 * @param fileName ファイル? 286 * 287 * @return 入力画像として使用できるかど?。できる場合?、true 288 */ 289 public static final boolean isReaderSuffix( final String fileName ) { 290 String suffix = getSuffix( fileName ); 291 292 // return READER_SUFFIXES.indexOf( suffix ) >= 0 ; 293 return (suffix != null) && READER_SUFFIXES.indexOf( suffix ) >= 0 ; 294 } 295 296 /** 297 * ファイル名から?力画像になりうるかど?を判定します? 298 * コンストラクターの引数??力画像)や、実際の処??中??力画像)で 299 * 、変換対象となるかど?をチェ?して?すが、それを事前に確認できるようにします? 300 * 301 * @og.rev 5.6.5.3 (2013/06/28) 新規追? 302 * @og.rev 5.6.6.1 (2013/07/12) getSuffix ?null を返すケースへの対? 303 * 304 * @param fileName ファイル? 305 * 306 * @return 出力画像として使用できるかど?。できる場合?、true 307 */ 308 public static final boolean isWriterSuffix( final String fileName ) { 309 String suffix = getSuffix( fileName ); 310 311 // return WRITER_SUFFIXES.indexOf( suffix ) >= 0 ; 312 return (suffix != null) && WRITER_SUFFIXES.indexOf( suffix ) >= 0 ; 313 } 314 315 /** 316 * BufferedImageをISOCoatedのICCプロファイルで読み込み、RGBにした結果を返します? 317 * (CMYKからRBGへの変換、ビ?反転) 318 * なお?ここでは、外部の ICC_PROFILE(ISOcoated_v2_eci.icc) を利用して、???度ア??を図りますが? 319 * 存在しな??合?標準?、com.sun.media.jai.util.SimpleCMYKColorSpace を利用します?で、エラーは出ません? 320 * ただし?も?すごく遅?め?実用?はありません? 321 * ISOcoated_v2_eci.icc ファイルは、zip圧縮して、拡張子をjar に変更後?(ISOcoated_v2_eci.jar) 322 * javaエクス?ション((JAVA_HOME\)jre\lib\ext) にコピ?するか?実行時に、CLASSPATHに設定します? 323 * 324 * @og.rev 5.4.3.5 (2012/01/17) 325 * @og.rev 5.5.3.4 (2012/06/19) ICC_PROFILE の取得?を?ISOcoated_v2_eci.icc に変更 326 * 327 * @param readImage BufferedImageオブジェク? 328 * 329 * @return 変換後?BufferedImage 330 * @throws IOException 入出力エラーが発生したと? 331 */ 332 public BufferedImage cmykToSRGB( final BufferedImage readImage ) throws IOException { 333 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 334 InputStream icc_stream = loader.getResourceAsStream( ICC_PROFILE ); 335 336 // 5.5.3.4 (2012/06/19) ICC_PROFILE が存在しな??合?、標準?SimpleCMYKColorSpace を使用? 337 ColorSpace cmykCS = null; 338 if( icc_stream != null ) { 339 ICC_Profile prof = ICC_Profile.getInstance(icc_stream); //変換プロファイル 340 // ColorSpace cmykCS = new ICC_ColorSpace(prof); 341 cmykCS = new ICC_ColorSpace(prof); 342 } 343 else { 344 // ?ので標準?スペ?スは使えな? 345 // ColorSpace cmykCS = SimpleCMYKColorSpace.getInstance(); 346 String errMsg = ICC_PROFILE + " が見つかりません? + CR 347 + " CLASSPATHの設定されて?場?配備してください? + CR 348 + " 標準?SimpleCMYKColorSpaceを使用します?でエラーにはなりませんが?非常に?です?" ; 349 System.out.println( errMsg ); 350 cmykCS = SimpleCMYKColorSpace.getInstance(); 351 } 352 BufferedImage rgbImage = new BufferedImage(readImage.getWidth(), 353 readImage.getHeight(), BufferedImage.TYPE_INT_RGB); 354 ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace(); 355 ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null); 356 cmykToRgb.filter(readImage, rgbImage); 357 358 int width = rgbImage.getWidth(); 359 int height = rgbImage.getHeight(); 360 // 反転が?? 361 for (int i=0;i<width;i++) { 362 for (int j=0;j<height;j++) { 363 int rgb = rgbImage.getRGB(i, j); 364 int rr = (rgb & 0xff0000) >> 16; 365 int gg = (rgb & 0x00ff00) >> 8; 366 int bb = (rgb & 0x0000ff); 367 rgb = ((Math.abs(rr - 255) << 16) + (Math.abs(gg - 255) << 8) + (Math.abs(bb - 255))); 368 rgbImage.setRGB(i, j, rgb); 369 } 370 } 371 372 return rgbImage; 373 } 374 375 /** 376 * メイン処?す? 377 * Usage: java org.opengion.fukurou.util.ImageResizer [Input Filename] [OutputFilename] [MaxResize] 378 * 379 * @param args 引数??配? 入力ファイル、?力ファイル、縦横?サイズ 380 */ 381 public static void main( final String[] args ) { 382 if( args.length < 3 ) { 383 LogWriter.log( "Usage: java org.opengion.fukurou.util.ImageResizer [Input Filename] [OutputFilename] [MaxResize]" ); 384 return ; 385 } 386 387 ImageResizer ir = new ImageResizer( args[0] ); 388 // ir.resizeByPixel( args[1], Integer.parseInt( args[2] ),Integer.parseInt( args[2] ) ); 389 ir.resizeByPixel( args[1], Integer.parseInt( args[2] ) ); 390 // ir.resizeByRatio( args[1], Double.parseDouble( args[2] ) ); 391 } 392 }