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             * 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    }