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    }