    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 static org.opengion.fukurou.util.HybsConst.CR;                           // 6.1.0.0 (2014/12/26) refactoring
    019
    020import java.awt.color.ColorSpace;
    021import java.awt.color.ICC_ColorSpace;
    022import java.awt.color.ICC_Profile;
    023import java.awt.Color;                                                          // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
    024import java.awt.Font;                                                           // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
    025import java.awt.Graphics2D;                                                     // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
    026import java.awt.FontMetrics;                                            // 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
    027import java.awt.image.BufferedImage;
    028import java.awt.image.ColorConvertOp;
    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
    043/**
    044 * ImageUtil は、画像ファイル関連の処理を集めたユーティリティクラスです。
    045 *
    046 * ここでは、イメージファイルを BufferedImage にして取り扱います。
    047 * また、ImageResizer で処理していた static メソッドや、関連処理、
    048 * org.opengion.hayabusa.servlet.MakeImage の主要な処理もこちらに持ってきます。
    049 *
    050 * @version  6.0.2.3 (2014/10/10)
    051 * @author   Hiroki Nakamura
    052 * @since    JDK6.0,
    053 */
    054public final class ImageUtil {
    055
    056        private static final String ICC_PROFILE = "ISOcoated_v2_eci.icc";               // 5.5.3.4 (2012/06/19)
    057
    058        // 6.0.2.3 (2014/10/10) テキスト合成で指定できる設定値
    059        /** X軸に対して、テキストを画像の左寄せで表示します。 **/
    060        public static final int LEFT    = -1 ;
    061        /** X軸に対して、テキストを画像の中央揃えで表示します。 **/
    062        public static final int CENTER  = -2 ;
    063        /** X軸に対して、テキストを画像の右寄せで表示します。 **/
    064        public static final int RIGHT   = -3 ;
    065
    066        /** Y軸に対して、テキストを画像の上揃えで表示します。 **/
    067        public static final int TOP     = -4 ;
    068        /** Y軸に対して、テキストを画像の中央揃えで表示します。 **/
    069        public static final int MIDDLE  = -5 ;
    070        /** Y軸に対して、テキストを画像の下揃えで表示します。 **/
    071        public static final int BOTTOM  = -6 ;
    072
    073        public static final String READER_SUFFIXES ;    // 5.6.5.3 (2013/06/28) 入力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp]
    074        public static final String WRITER_SUFFIXES ;    // 5.6.5.3 (2013/06/28) 出力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp]
    075        // 5.6.5.3 (2013/06/28) 入力画像,出力画像の形式 を ImageIO から取り出します。
    076        static {
    077                final String[] rfn = ImageIO.getReaderFileSuffixes();
    078                Arrays.sort( rfn );
    079                READER_SUFFIXES = Arrays.toString( rfn );
    080
    081                final String[] wfn = ImageIO.getWriterFileSuffixes();
    082                Arrays.sort( wfn );
    083                WRITER_SUFFIXES = Arrays.toString( wfn );
    084        }
    085
    086        /**
    087         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
    088         *
    089         */
    090        private ImageUtil() {}
    091
    092        /**
    093         * 入力ファイル名を指定し、画像オブジェクトを作成します。
    094         *
    095         * @og.rev 5.4.3.5 (2012/01/17) CMYK対応
    096         * @og.rev 5.4.3.7 (2012/01/20) FAIでのファイル取得方法変更
    097         * @og.rev 5.4.3.8 (2012/01/24) エラーメッセージ追加
    098         * @og.rev 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
    099         * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
    100         *
    101         * @param fin 入力ファイル名
    102         * @return 読み込まれた画像オブジェクト(BufferedImage)
    103         */
    104        public static BufferedImage readFile( final String fin ) {
    105                // 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
    106                if( !ImageUtil.isReaderSuffix( fin ) ) {
    107                        final String errMsg = "入力ファイルは" + READER_SUFFIXES + "のいずれかの形式のみ指定可能です。"
    108                                                        + "File=[" + fin + "]";
    109                        throw new RuntimeException( errMsg );
    110                }
    111
    112                final File inFile = new File( fin );
    113                BufferedImage bi = null;
    114                try {
    115                        bi = ImageIO.read( inFile );
    116                }
    117                catch ( IIOException ex ) { // 5.4.3.5 (2012/01/17) 決めうち
    118                        // API的には、IllegalArgumentException と IOException しか記述されていない。
    119                        // 何もせずに、下の処理に任せます。
    120                        // 6.0.2.5 (2014/10/31) refactoring:Avoid empty catch blocks 警告対応
    121                        final String errMsg = "cmykToSRGB 処理が必要です。" + ex.getMessage();
    122                        System.err.println( errMsg );
    123                }
    124                catch( IOException ex ) {
    125                        final String errMsg = "イメージファイルの読込に失敗しました。" + "File=[" + fin + "]";
    126                        throw new RuntimeException( errMsg,ex );
    127                }
    128
    129                // 6.0.0.1 (2014/04/25) IIOException の catch ブロックからの例外出力を外に出します。
    130                // bi == null は、結果のストリームを読み込みできないような場合、または、IO例外が発生した場合。
    131                if( bi == null ) {
    132                        FileSeekableStream fsstream = null;
    133                        try{
    134                                // 5.4.3.7 (2012/01/20) ファイルの開放がGC依存なので、streamで取得するように変更
    135                                // bi = cmykToSRGB(JAI.create("FileLoad",inFile.toString()).getAsBufferedImage(null,null));
    136                                fsstream = new FileSeekableStream(inFile.getAbsolutePath());
    137                                bi = cmykToSRGB(JAI.create("stream",fsstream).getAsBufferedImage(null,null));
    138                        }
    139                        catch( IOException ex ){
    140                                final String errMsg = "イメージファイルの読込(JAI)に失敗しました。" + "File=[" + fin + "]";
    141                                throw new RuntimeException( errMsg,ex );
    142                        }
    143                        catch( RuntimeException ex ) {          // 5.4.3.8 (2012/01/23) その他エラーの場合追加
    144                                final String errMsg = "イメージファイルの読込(JAI)に失敗しました。ファイルが壊れている可能性があります。" + "File=[" + fin + "]";
    145                                throw new RuntimeException( errMsg,ex );
    146                        }
    147                        finally{
    148                                Closer.ioClose(fsstream);
    149                        }
    150                }
    151
    152                return bi;
    153        }
    154
    155        /**
    156         * 画像オブジェクト と、出力ファイル名を指定し、ファイルに書き込みます。
    157         *
    158         * ImageIO に指定する formatName(ファイル形式)は、出力ファイル名の拡張子から取得します。
    159         * [bmp, gif, jpeg, jpg, png, wbmp] 位がサポートされています。
    160         *
    161         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    162         *
    163         * @param image 出力する画像オブジェクト(BufferedImage)
    164         * @param fout 出力ファイル名
    165         */
    166        public static void saveFile( final BufferedImage image , final String fout ) {
    167                final File outFile = new File( fout );
    168                try {
    169                        final String outSuffix = ImageUtil.getSuffix( fout );
    170                        ImageIO.write( image, outSuffix, outFile );
    171                }
    172                catch( IOException ex ) {
    173                        final String errMsg = "イメージファイルの書き込みに失敗しました。" + "File=[" + fout + "]";
    174                        throw new RuntimeException( errMsg,ex );
    175                }
    176        }
    177
    178        /**
    179         * 入力ファイル名を指定し、画像ファイルの byte配列を作成します。
    180         *
    181         * ImageIO に指定する formatName(ファイル形式)は、出力ファイル名の拡張子から取得します。
    182         * [bmp, gif, jpeg, jpg, png, wbmp] 位がサポートされています。
    183         *
    184         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    185         *
    186         * @param fin 入力ファイル名
    187         * @return 読み込まれた画像ファイルの byte配列
    188         * @og.rtnNotNull
    189         */
    190        public static byte[] byteImage( final String fin ) {
    191                final ByteArrayOutputStream baOut = new ByteArrayOutputStream();
    192
    193                final BufferedImage img = ImageUtil.readFile( fin );
    194                try {
    195                        final String suffix = ImageUtil.getSuffix( fin );
    196                        ImageIO.write( img, suffix, baOut );
    197                }
    198                catch( IOException ex ) {
    199                        final String errMsg = "イメージファイルの読み込みに失敗しました。" + "File=[" + fin + "]";
    200                        throw new RuntimeException( errMsg,ex );
    201                }
    202                finally {
    203                        Closer.ioClose( baOut );                // ByteArrayOutputStreamを閉じても、何の影響もありません。
    204                }
    205
    206                return baOut.toByteArray();
    207        }
    208
    209        /**
    210         * ファイル名から拡張子(小文字)を求めます。
    211         * 拡張子 が存在しない場合は、null を返します。
    212         *
    213         * @og.rev 5.6.5.3 (2013/06/28) private ⇒ public へ変更
    214         * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
    215         *
    216         * @param fileName ファイル名
    217         *
    218         * @return 拡張子(小文字)。なければ、null
    219         */
    220        public static String getSuffix( final String fileName ) {
    221                String suffix = null;
    222                if( fileName != null ) {
    223                        final int sufIdx = fileName.lastIndexOf( '.' );
    224                        if( sufIdx >= 0 ) {
    225                                suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
    226                        }
    227                }
    228                return suffix;
    229        }
    230
    231        /**
    232         * ファイル名から入力画像になりうるかどうかを判定します。
    233         * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で
    234         * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
    235         *
    236         * @og.rev 5.6.5.3 (2013/06/28) 新規追加
    237         * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応
    238         * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
    239         *
    240         * @param fileName ファイル名
    241         *
    242         * @return 入力画像として使用できるかどうか。できる場合は、true
    243         */
    244        public static boolean isReaderSuffix( final String fileName ) {
    245                final String suffix = getSuffix( fileName );
    246
    247                return suffix != null && READER_SUFFIXES.indexOf( suffix ) >= 0 ;
    248        }
    249
    250        /**
    251         * ファイル名から出力画像になりうるかどうかを判定します。
    252         * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で
    253         * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
    254         *
    255         * @og.rev 5.6.5.3 (2013/06/28) 新規追加
    256         * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応
    257         * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
    258         *
    259         * @param fileName ファイル名
    260         *
    261         * @return 出力画像として使用できるかどうか。できる場合は、true
    262         */
    263        public static boolean isWriterSuffix( final String fileName ) {
    264                final String suffix = getSuffix( fileName );
    265
    266                return suffix != null && WRITER_SUFFIXES.indexOf( suffix ) >= 0 ;
    267        }
    268
    269        /**
    270         * ファイル名から入力画像になりうるかどうかを判定します。
    271         * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で、
    272         * 変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
    273         *
    274         * @og.rev 6.0.2.3 (2014/10/10) 新規追加
    275         *
    276         * @param img 変換対象のBufferedImage
    277         * @param fCol 変換対象の色
    278         * @param tCol 変換後の色
    279         */
    280        public static void changeColor( final BufferedImage img , final Color fCol , final Color tCol ) {
    281                final int wd = img.getWidth();
    282                final int ht = img.getHeight();
    283                final int fc = fCol.getRGB();           // 変換元のRGB値。
    284                final int tc = tCol.getRGB();           // 変換後のRGB値。例：new Color( 255,255,255,0 ) なら、透明
    285
    286                for(int y=0; y<ht; y++) {
    287                        for(int x=0; x<wd; x++) {
    288                                if( img.getRGB( x,y ) == fc ) {
    289                                        img.setRGB( x,y,tc );
    290                                }
    291                        }
    292                }
    293        }
    294
    295        /**
    296         * BufferedImageをISOCoatedのICCプロファイルで読み込み、RGBにした結果を返します。
    297         * (CMYKからRBGへの変換、ビット反転)
    298         * なお、ここでは、外部の ICC_PROFILE(ISOcoated_v2_eci.icc) を利用して、処理速度アップを図りますが、
    299         * 存在しない場合、標準の、com.sun.media.jai.util.SimpleCMYKColorSpace を利用しますので、エラーは出ません。
    300         * ただし、ものすごく遅いため、実用的ではありません。
    301         * ISOcoated_v2_eci.icc ファイルは、zip圧縮して、拡張子をjar に変更後、(ISOcoated_v2_eci.jar)
    302         * javaエクステンション((JAVA_HOME\)jre\lib\ext) にコピーするか、実行時に、CLASSPATHに設定します。
    303         *
    304         * @og.rev 5.4.3.5 (2012/01/17)
    305         * @og.rev 5.5.3.4 (2012/06/19) ICC_PROFILE の取得先を、ISOcoated_v2_eci.icc に変更
    306         * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。(static にして)
    307         *
    308         * @param readImage BufferedImageオブジェクト
    309         *
    310         * @return 変換後のBufferedImage
    311         * @throws IOException 入出力エラーが発生したとき
    312         */
    313        public static BufferedImage cmykToSRGB( final BufferedImage readImage ) throws IOException {
    314                final ClassLoader loader = Thread.currentThread().getContextClassLoader();
    315                final InputStream icc_stream = loader.getResourceAsStream( ICC_PROFILE );
    316
    317                // 5.5.3.4 (2012/06/19) ICC_PROFILE が存在しない場合は、標準のSimpleCMYKColorSpace を使用。
    318                ColorSpace cmykCS = null;
    319                if( icc_stream != null ) {
    320                        final ICC_Profile prof =        ICC_Profile.getInstance(icc_stream);    //変換プロファイル
    321                        cmykCS = new ICC_ColorSpace(prof);
    322                }
    323                else {
    324                        // 遅いので標準のスペースは使えない
    325                        final String errMsg = ICC_PROFILE + " が見つかりません。" + CR
    326                                                        + " CLASSPATHの設定されている場所に配備してください。"      +       CR
    327                                                        + " 標準のSimpleCMYKColorSpaceを使用しますのでエラーにはなりませんが、非常に遅いです。" ;
    328                        System.out.println( errMsg );
    329                        cmykCS = SimpleCMYKColorSpace.getInstance();
    330                }
    331
    332                final BufferedImage rgbImage = new BufferedImage(readImage.getWidth(),readImage.getHeight(), BufferedImage.TYPE_INT_RGB);
    333                final ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
    334                final ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
    335                cmykToRgb.filter(readImage, rgbImage);
    336
    337                final int width  = rgbImage.getWidth();
    338                final int height = rgbImage.getHeight();
    339                // 反転が必要
    340                for (int i=0;i<width;i++) {
    341                        for (int j=0;j<height;j++) {
    342                                int rgb = rgbImage.getRGB(i, j);
    343                                final int rr = (rgb & 0xff0000) >> 16;
    344                                final int gg = (rgb & 0x00ff00) >> 8;
    345                                final int bb = (rgb & 0x0000ff);
    346                                rgb = (Math.abs(rr - 255) << 16) + (Math.abs(gg - 255) << 8) + (Math.abs(bb - 255));
    347                                rgbImage.setRGB(i, j, rgb);
    348                        }
    349                }
    350
    351                return rgbImage;
    352        }
    353
    354        /**
    355         * 画像イメージに、文字列を動的に合成作成して返します。
    356         *
    357         * 描画指定の位置(x,y)は、テキストの左下の位置を、画像イメージの、左上を起点(0,0)とした
    358         * 位置になります。
    359         * maxW , maxH を指定すると、テキストのフォントサイズをその範囲に収まるように自動調整します。
    360         *
    361         * @og.rev 6.0.2.3 (2014/10/10) 新規追加
    362         *
    363         * @param image 合成する元の画像オブジェクト
    364         * @param text  描画される文字列
    365         * @param xAxis テキストが描画される位置のx座標。または、{@link #LEFT LEFT},{@link #CENTER CENTER},{@link #RIGHT RIGHT} 指定で、自動計算する。
    366         * @param yAxis テキストが描画される位置のy座標。または、{@link #TOP TOP},{@link #MIDDLE MIDDLE},{@link #BOTTOM BOTTOM} 指定で、自動計算する。
    367         * @param maxW  テキストの最大幅(imageの幅と比較して小さい方の値。0以下の場合は、imageの幅)
    368         * @param maxH  テキストの最大高さ(imageの高さと比較して小さい方の値。0以下の場合は、imageの高さ)
    369         * @param font  描画されるテキストのフォント。null の場合は、初期値(Dialog.plain,12px)が使われる
    370         * @param color 描画されるテキストの色(Color)。null の場合は、Color.BLACK が使われる
    371         *
    372         * @return 合成された画像オブジェクト(BufferedImage)
    373         * @og.rtnNotNull
    374         * @see         #mixImage( BufferedImage, String, int, int, Font, Color )
    375         */
    376        public static BufferedImage mixImage( final BufferedImage image,
    377                                                                                        final String text, final int xAxis, final int yAxis, final int maxW, final int maxH,
    378                                                                                        final Font font, final Color color ) {
    379
    380                final int imgWidth  = image.getWidth();                                 // 画像の幅
    381                final int imgHeight = image.getHeight();                                        // 画像の高さ
    382
    383                final int maxWidth  = maxW <= 0 ? imgWidth  : Math.min( maxW,imgWidth );
    384                final int maxHeight = maxH <= 0 ? imgHeight : Math.min( maxH,imgHeight );
    385
    386                final Graphics2D gph = image.createGraphics();
    387                if( font != null ) { gph.setFont(  font  ); }           // new Font("Serif", Font.BOLD, 14)
    388
    389                float size = 5.0f;              // 小さすぎると見えないので、開始はこれくらいから行う。
    390                final float step = 0.5f;                // 刻み幅
    391                while( true ) {
    392                        final Font tmpFont = gph.getFont().deriveFont( size );
    393                        gph.setFont( tmpFont );
    394
    395                        final FontMetrics fm = gph.getFontMetrics();
    396                        final int txtWidth  = fm.stringWidth( text );
    397                        final int txtHeight = fm.getAscent();
    398
    399                        if( maxWidth < txtWidth || maxHeight < txtHeight ) {
    400                                size -= step;   // 一つ戻しておく。場合によっては、step分戻して、stepを小さくして続ける方法もある。
    401                                break;
    402                        }
    403                        size += step;
    404                }
    405                final Font newFont = gph.getFont().deriveFont( size );
    406
    407                return mixImage( image, text, xAxis, yAxis, newFont, color );
    408        }
    409
    410        /**
    411         * 画像イメージに、文字列を動的に合成作成して返します。
    412         *
    413         * 描画指定の位置(x,y)は、テキストの左下の位置を、画像イメージの、左上を起点(0,0)とした
    414         * 位置になります。
    415         *
    416         * @og.rev 6.0.2.3 (2014/10/10) org.opengion.hayabusa.servlet.MakeImage から、移植しました。
    417         *
    418         * @param image 合成する元の画像オブジェクト
    419         * @param text  描画される文字列
    420         * @param xAxis テキストが描画される位置のx座標。または、{@link #LEFT LEFT},{@link #CENTER CENTER},{@link #RIGHT RIGHT} 指定で、自動計算する。
    421         * @param yAxis テキストが描画される位置のy座標。または、{@link #TOP TOP},{@link #MIDDLE MIDDLE},{@link #BOTTOM BOTTOM} 指定で、自動計算する。
    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, int, int, Font, Color )
    428         */
    429        public static BufferedImage mixImage( final BufferedImage image,
    430                                                                                        final String text, final int xAxis, final int yAxis,
    431                                                                                        final Font font, final Color color ) {
    432
    433                final Graphics2D gph = image.createGraphics();
    434
    435        //      gph.setRenderingHint( java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
    436
    437                if( font  != null ) { gph.setFont(  font  ); }          // new Font("Serif", Font.BOLD, 14)
    438                if( color != null ) { gph.setColor( color ); }          // new Color(0,0,255) など
    439                else {                            gph.setColor( Color.BLACK ); }
    440
    441                // 実際の位置ではなく、X軸が、LEFT,CENTER,RIGHT 等の指定
    442                int x1 = xAxis ;
    443                if( x1 < 0 ) {
    444                        final int imgWidth = image.getWidth();                                  // 画像の幅
    445                        final FontMetrics fm = gph.getFontMetrics();
    446                        final int txtWidth = fm.stringWidth( text );                            // テキストの長さ
    447
    448                        switch( x1 ) {
    449                                case LEFT   : x1 = 0;                                                   // 左寄せなので、0
    450                                                                break;
    451                                case CENTER : x1 = imgWidth/2 - txtWidth/2;             // 画像の中心から、テキストの中心を引き算
    452                                                                break;
    453                                case RIGHT  : x1 = imgWidth - txtWidth;                 // 右寄せは、画像の右端からテキスト分を引き算
    454                                                                break;
    455                                default :
    456                                        final String errMsg = "X軸 で範囲外のデータが指定されました。" + "text=[" + text + "]"
    457                                                                                + " (x,y)=[" + xAxis + "," + yAxis + "]" ;
    458                                        throw new RuntimeException( errMsg );
    459                                //      break;          制御は移りません。
    460                        }
    461                }
    462
    463                // 実際の位置ではなく、Y軸が、TOP,MIDDLE,BOTTOM 等の指定
    464                final int Ydef = 2 ;    // 良く判らないが、位置合わせに必要。
    465                int y1 = yAxis ;
    466                if( y1 < 0 ) {
    467                        final int imgHeight = image.getHeight() -Ydef;                  // 画像の高さ
    468                        final FontMetrics fm = gph.getFontMetrics();
    469                        final int txtHeight = fm.getAscent() -Ydef;                             // テキストの幅(=Ascent)
    470
    471                        switch( y1 ) {
    472                                case TOP    : y1 = txtHeight;                                   // 上寄せは、テキストの幅分だけ下げる
    473                                                                break;
    474                                case MIDDLE : y1 = (imgHeight)/2 + (txtHeight)/2 ;      // 画像の中心から、テキストの中心分下げる(加算)
    475                                                                break;
    476                                case BOTTOM : y1 = imgHeight;                                   // 下寄せは、画像の高さ分-2
    477                                                                break;
    478                                default :
    479                                        final String errMsg = "Y軸 で範囲外のデータが指定されました。" + "text=[" + text + "]"
    480                                                                                + " (x,y)=[" + xAxis + "," + yAxis + "]" ;
    481                                        throw new RuntimeException( errMsg );
    482                                //      break;          制御は移りません。
    483                        }
    484                }
    485
    486                gph.drawString( text, x1, y1 );
    487                gph.dispose();          // グラフィックス・コンテキストを破棄
    488
    489                return image;
    490        }
    491
    492        /**
    493         * アプリケーションのサンプルです。
    494         *
    495         * 入力イメージファイルを読み取って、テキストを合成して、出力イメージファイル に書き込みます。
    496         * テキストの挿入位置を、X軸、Y軸で指定します。
    497         * X軸とY軸には、特別な記号があり、左寄せ、右寄せ等の指示が可能です。
    498         *
    499         * サンプルでは、new Font("Serif", Font.PLAIN, 14); と、new Color(0,0,255);（青色）を固定で渡しています。
    500         *
    501         * Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル テキスト X軸 Y軸 [フォントサイズ]
    502         *   X軸 指定
    503         *    -1 ･･･ LEFT    左寄せ
    504         *    -2 ･･･ CENTER  中央揃え
    505         *    -3 ･･･ RIGHT   右寄せ
    506         *
    507         *   Y軸 指定
    508         *    -4 ･･･ TOP     上揃え
    509         *    -5 ･･･ MIDDLE  中央揃え
    510         *    -6 ･･･ BOTTOM  下揃え
    511         *
    512         * @param  args  引数文字列配列 入力ファイル、出力ファイル、縦横最大サイズ
    513         */
    514        public static void main( final String[] args ) {
    515                if( args.length < 5 ) {
    516                        final String usage = "Usage: java org.opengion.fukurou.util.ImageUtil \n" +
    517                                                        "               入力ファイル 出力ファイル テキスト X軸 Y軸 [フォントサイズ]\n" +
    518                                                        "\tX軸とY軸には、特別な記号があり、左寄せ、右寄せ等の指示が可能です。\n" +
    519                                                        "\t   X軸 指定\n"                                  +
    520                                                        "\t    -1 ･･･ LEFT    左寄せ\n"            +
    521                                                        "\t    -2 ･･･ CENTER  中央揃え\n"   +
    522                                                        "\t    -3 ･･･ RIGHT   右寄せ\n"            +
    523                                                        "\t\n"                                                          +
    524                                                        "\t   Y軸 指定 \n"                                 +
    525                                                        "\t    -4 ･･･ TOP     上揃え\n"            +
    526                                                        "\t    -5 ･･･ MIDDLE  中央揃え\n"   +
    527                                                        "\t    -6 ･･･ BOTTOM  下揃え\n" ;
    528                        System.out.println( usage );
    529                        return ;
    530                }
    531
    532                final String inImg  = args[0];
    533                final String outImg = args[1];
    534                final String text   = args[2];
    535                final int x = Integer.parseInt( args[3] );
    536                final int y = Integer.parseInt( args[4] );
    537                int fs = 14 ;
    538                if( args.length > 5 ) { fs = Integer.parseInt( args[5] ); }
    539
    540                final BufferedImage image = ImageUtil.readFile( inImg );
    541
    542                final Font  font  = new Font("Serif", Font.BOLD, fs);
    543                final Color color = new Color(255,0,0);                         // 赤色
    544                ImageUtil.mixImage( image , text , x , y , font , color );
    545
    546                ImageUtil.saveFile( image , outImg );
    547        }
    548}