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 }