/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.util;

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.Color;								// 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
import java.awt.Font;								// 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
import java.awt.Graphics2D;							// 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
import java.awt.FontMetrics;						// 6.0.2.3 (2014/10/10) mixImage(画像合成) 関係
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.Transparency;						// 7.0.1.1 (2018/10/22) 透過色処理 関係
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.util.Locale;
import java.util.Arrays;
import javax.media.jai.JAI;

import javax.imageio.ImageIO;
import javax.imageio.IIOException;

import com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.util.SimpleCMYKColorSpace;

import org.opengion.fukurou.system.Closer;							// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
import static org.opengion.fukurou.system.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring

/**
 * ImageUtil は、画像ファイル関連の処理を集めたユーティリティクラスです。
 *
 * ここでは、イメージファイルを BufferedImage にして取り扱います。
 * また、ImageResizer で処理していた static メソッドや、関連処理、
 * org.opengion.hayabusa.servlet.MakeImage の主要な処理もこちらに持ってきます。
 *
 * @version  6.0.2.3 (2014/10/10)
 * @author   Hiroki Nakamura
 * @since    JDK6.0,
 */
public final class ImageUtil {

	private static final String ICC_PROFILE = "ISOcoated_v2_eci.icc";		// 5.5.3.4 (2012/06/19)

	// 6.0.2.3 (2014/10/10) テキスト合成で指定できる設定値
	/** X軸に対して、テキストを画像の左寄せで表示します。 **/
	public static final int LEFT	= -1 ;
	/** X軸に対して、テキストを画像の中央揃えで表示します。 **/
	public static final int CENTER	= -2 ;
	/** X軸に対して、テキストを画像の右寄せで表示します。 **/
	public static final int RIGHT	= -3 ;

	/** Y軸に対して、テキストを画像の上揃えで表示します。 **/
	public static final int TOP		= -4 ;
	/** Y軸に対して、テキストを画像の中央揃えで表示します。 **/
	public static final int MIDDLE	= -5 ;
	/** Y軸に対して、テキストを画像の下揃えで表示します。 **/
	public static final int BOTTOM	= -6 ;

	/** 入力画像の形式 **/
	public static final String READER_SUFFIXES ;	// 5.6.5.3 (2013/06/28) 入力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp]
	/** 出力画像の形式 **/
	public static final String WRITER_SUFFIXES ;	// 5.6.5.3 (2013/06/28) 出力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp]
	// 5.6.5.3 (2013/06/28) 入力画像,出力画像の形式 を ImageIO から取り出します。
	static {
		final String[] rfn = ImageIO.getReaderFileSuffixes();
		Arrays.sort( rfn );
		READER_SUFFIXES = Arrays.toString( rfn );

		final String[] wfn = ImageIO.getWriterFileSuffixes();
		Arrays.sort( wfn );
		WRITER_SUFFIXES = Arrays.toString( wfn );
	}

	/**
	 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
	 *
	 */
	private ImageUtil() {}

	/**
	 * 入力ファイル名を指定し、画像オブジェクトを作成します。
	 *
	 * @og.rev 5.4.3.5 (2012/01/17) CMYK対応
	 * @og.rev 5.4.3.7 (2012/01/20) FAIでのファイル取得方法変更
	 * @og.rev 5.4.3.8 (2012/01/24) エラーメッセージ追加
	 * @og.rev 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
	 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
	 *
	 * @param fin 入力ファイル名
	 * @return 読み込まれた画像オブジェクト(BufferedImage)
	 */
	public static BufferedImage readFile( final String fin ) {
		// 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
		if( !ImageUtil.isReaderSuffix( fin ) ) {
			final String errMsg = "入力ファイルは" + READER_SUFFIXES + "のいずれかの形式のみ指定可能です。"
							+ "File=[" + fin + "]";
			throw new OgRuntimeException( errMsg );
		}

		final File inFile = new File( fin );
		BufferedImage bi = null;
		try {
			bi = ImageIO.read( inFile );
		}
		catch( final IIOException ex ) { // 5.4.3.5 (2012/01/17) 決めうち
			// API的には、IllegalArgumentException と IOException しか記述されていない。
			// 何もせずに、下の処理に任せます。
			// 6.0.2.5 (2014/10/31) refactoring:Avoid empty catch blocks 警告対応
			final String errMsg = "cmykToSRGB 処理が必要です。" + ex.getMessage();
			System.err.println( errMsg );
		}
		catch( final IOException ex ) {
			final String errMsg = "イメージファイルの読込に失敗しました。" + "File=[" + fin + "]";
			throw new OgRuntimeException( errMsg,ex );
		}

		// 6.0.0.1 (2014/04/25) IIOException の catch ブロックからの例外出力を外に出します。
		// bi == null は、結果のストリームを読み込みできないような場合、または、IO例外が発生した場合。
		if( bi == null ) {
			FileSeekableStream fsstream = null;
			try{
				// 5.4.3.7 (2012/01/20) ファイルの開放がGC依存なので、streamで取得するように変更
				// bi = cmykToSRGB(JAI.create("FileLoad",inFile.toString()).getAsBufferedImage(null,null));
				fsstream = new FileSeekableStream(inFile.getAbsolutePath());
				bi = cmykToSRGB(JAI.create("stream",fsstream).getAsBufferedImage(null,null));
			}
			catch( final IOException ex ){
				final String errMsg = "イメージファイルの読込(JAI)に失敗しました。" + "File=[" + fin + "]";
				throw new OgRuntimeException( errMsg,ex );
			}
			catch( final RuntimeException ex ) {		// 5.4.3.8 (2012/01/23) その他エラーの場合追加
				final String errMsg = "イメージファイルの読込(JAI)に失敗しました。ファイルが壊れている可能性があります。" + "File=[" + fin + "]";
				throw new OgRuntimeException( errMsg,ex );
			}
			finally{
				Closer.ioClose(fsstream);
			}
		}

		return bi;
	}

	/**
	 * 画像オブジェクト と、出力ファイル名を指定し、ファイルに書き込みます。
	 *
	 * ImageIO に指定する formatName(ファイル形式)は、出力ファイル名の拡張子から取得します。
	 * [bmp, gif, jpeg, jpg, png, wbmp] 位がサポートされています。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param image 出力する画像オブジェクト(BufferedImage)
	 * @param fout 出力ファイル名
	 */
	public static void saveFile( final BufferedImage image , final String fout ) {
		final File outFile = new File( fout );
		try {
			final String outSuffix = ImageUtil.getSuffix( fout );
			ImageIO.write( image, outSuffix, outFile );
		}
		catch( final IOException ex ) {
			final String errMsg = "イメージファイルの書き込みに失敗しました。" + "File=[" + fout + "]";
			throw new OgRuntimeException( errMsg,ex );
		}
	}

	/**
	 * 入力ファイル名を指定し、画像ファイルの byte配列を作成します。
	 *
	 * ImageIO に指定する formatName(ファイル形式)は、出力ファイル名の拡張子から取得します。
	 * [bmp, gif, jpeg, jpg, png, wbmp] 位がサポートされています。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param fin 入力ファイル名
	 * @return 読み込まれた画像ファイルの byte配列
	 * @og.rtnNotNull
	 */
	public static byte[] byteImage( final String fin ) {
		final ByteArrayOutputStream baOut = new ByteArrayOutputStream();

		final BufferedImage img = ImageUtil.readFile( fin );
		try {
			final String suffix = ImageUtil.getSuffix( fin );
			ImageIO.write( img, suffix, baOut );
		}
		catch( final IOException ex ) {
			final String errMsg = "イメージファイルの読み込みに失敗しました。" + "File=[" + fin + "]";
			throw new OgRuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( baOut );		// ByteArrayOutputStreamを閉じても、何の影響もありません。
		}

		return baOut.toByteArray();
	}

	/**
	 * ファイル名から拡張子(小文字)を求めます。
	 * 拡張子 が存在しない場合は、null を返します。
	 *
	 * @og.rev 5.6.5.3 (2013/06/28) private ⇒ public へ変更
	 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
	 *
	 * @param fileName ファイル名
	 *
	 * @return 拡張子(小文字)。なければ、null
	 */
	public static String getSuffix( final String fileName ) {
		String suffix = null;
		if( fileName != null ) {
			final int sufIdx = fileName.lastIndexOf( '.' );
			if( sufIdx >= 0 ) {
				suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
			}
		}
		return suffix;
	}

	/**
	 * ファイル名から入力画像になりうるかどうかを判定します。
	 * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で
	 * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
	 *
	 * @og.rev 5.6.5.3 (2013/06/28) 新規追加
	 * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応
	 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
	 *
	 * @param fileName ファイル名
	 *
	 * @return 入力画像として使用できるかどうか。できる場合は、true
	 */
	public static boolean isReaderSuffix( final String fileName ) {
		final String suffix = getSuffix( fileName );

		return suffix != null && READER_SUFFIXES.indexOf( suffix ) >= 0 ;
	}

	/**
	 * ファイル名から出力画像になりうるかどうかを判定します。
	 * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で
	 * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
	 *
	 * @og.rev 5.6.5.3 (2013/06/28) 新規追加
	 * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応
	 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。
	 *
	 * @param fileName ファイル名
	 *
	 * @return 出力画像として使用できるかどうか。できる場合は、true
	 */
	public static boolean isWriterSuffix( final String fileName ) {
		final String suffix = getSuffix( fileName );

		return suffix != null && WRITER_SUFFIXES.indexOf( suffix ) >= 0 ;
	}

	/**
	 * 色変換を行います。
	 * 変換元の色を、最初のビットから作ります。
	 * なお、スキャンしながら、色変換が行われなかった場合は、逆からスキャンします。
	 * つまり、背景色で輪郭抽出して、外周だけ透明にするという感じです。
	 *
	 * @og.rev 7.0.2.1 (2019/03/04) 元の色をイメージの端から自動取得(白決め打ちでない)属性追加
	 *
	 * @param img 変換対象のBufferedImage
	 * @param tCol 変換後の色
	 * @param mask 変換対象の色変動を抑えるためのﾏｽｸ(0x00f0f0f0など)
	 */
	public static void changeColor( final BufferedImage img , final Color tCol , final int mask ) {
		final int wd = img.getWidth();
		final int ht = img.getHeight();
		final int fc = img.getRGB( 0,0 ) & mask;		// 変換元のRGB値は、一番端のﾋﾟｸｾﾙ
		final int tc = tCol.getRGB();					// 変換後のRGB値。例：new Color( 255,255,255,0 ) なら、透明

		for( int y=0; y<ht; y++ ) {
			boolean isRev = false;
			for( int x=0; x<wd; x++ ) {
				final int ic = img.getRGB( x,y ) & mask;
				if( ic == fc ) {							// 変換色チェック
					img.setRGB( x,y,tc );
				}
				else {
					isRev = true;							// 反転処理を行う。
					break;									// 変換ができなかった。＝ 境界線
				}
			}
			if( isRev ) {
				for( int x=wd-1; x>=0; x-- ) {
					final int ic = img.getRGB( x,y ) & mask;
					if( ic == fc ) {						// 変換色チェック
						img.setRGB( x,y,tc );
					}
					else {
						break;								// 変換ができなかった。＝ 境界線
					}
				}
			}
		}
	}

	/**
	 * 色変換を行います。
	 * 例えば、背景色白を、透明に変換するなどです。
	 *
	 * ボーダー色は、背景色と異なる色の場合があるため、特別に用意しています。
	 * ボーダーは、画像の周辺、3px を対象とします。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規追加
	 * @og.rev 7.0.1.0 (2018/10/15) 色変換に、元の色の変動を吸収するﾏｽｸ属性追加
	 *
	 * @param img 変換対象のBufferedImage
	 * @param fCol 変換対象の色
	 * @param tCol 変換後の色
	 * @param mask 変換対象の色変動を抑えるためのﾏｽｸ(0x00f0f0f0など)
	 */
	public static void changeColor( final BufferedImage img , final Color fCol , final Color tCol , final int mask ) {
		final int wd = img.getWidth();
		final int ht = img.getHeight();
		final int fc = fCol.getRGB() & mask;			// 変換元のRGB値。
		final int tc = tCol.getRGB();					// 変換後のRGB値。例：new Color( 255,255,255,0 ) なら、透明

		for( int y=0; y<ht; y++ ) {
			for( int x=0; x<wd; x++ ) {
				final int ic = img.getRGB( x,y ) & mask;
				if( ic == fc ) {															// 変換色チェック
					img.setRGB( x,y,tc );
				}
			}
		}
	}

	/**
	 * BufferedImageをISOCoatedのICCプロファイルで読み込み、RGBにした結果を返します。
	 * (CMYKからRBGへの変換、ビット反転)
	 * なお、ここでは、外部の ICC_PROFILE(ISOcoated_v2_eci.icc) を利用して、処理速度アップを図りますが、
	 * 存在しない場合、標準の、com.sun.media.jai.util.SimpleCMYKColorSpace を利用しますので、エラーは出ません。
	 * ただし、ものすごく遅いため、実用的ではありません。
	 * ISOcoated_v2_eci.icc ファイルは、zip圧縮して、拡張子をjar に変更後、(ISOcoated_v2_eci.jar)
	 * javaエクステンション((JAVA_HOME\)jre\lib\ext) にコピーするか、実行時に、CLASSPATHに設定します。
	 *
	 * @og.rev 5.4.3.5 (2012/01/17)
	 * @og.rev 5.5.3.4 (2012/06/19) ICC_PROFILE の取得先を、ISOcoated_v2_eci.icc に変更
	 * @og.rev 6.0.2.3 (2014/10/10) ImageResizer から、移植しました。(static にして)
	 *
	 * @param readImage BufferedImageオブジェクト
	 *
	 * @return 変換後のBufferedImage
	 * @throws IOException 入出力エラーが発生したとき
	 */
	public static BufferedImage cmykToSRGB( final BufferedImage readImage ) throws IOException {
		final ClassLoader loader = Thread.currentThread().getContextClassLoader();
		final InputStream icc_stream = loader.getResourceAsStream( ICC_PROFILE );

		// 5.5.3.4 (2012/06/19) ICC_PROFILE が存在しない場合は、標準のSimpleCMYKColorSpace を使用。
		ColorSpace cmykCS = null;
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		if( icc_stream == null ) {
			// 遅いので標準のスペースは使えない
			final String errMsg = ICC_PROFILE + " が見つかりません。" + CR
							+ " CLASSPATHの設定されている場所に配備してください。"	+	CR
							+ " 標準のSimpleCMYKColorSpaceを使用しますのでエラーにはなりませんが、非常に遅いです。" ;
			System.out.println( errMsg );
			cmykCS = SimpleCMYKColorSpace.getInstance();
		}
		else {
			final ICC_Profile prof = ICC_Profile.getInstance(icc_stream);	//変換プロファイル
			cmykCS = new ICC_ColorSpace(prof);
		}

		final BufferedImage rgbImage = new BufferedImage(readImage.getWidth(),readImage.getHeight(), BufferedImage.TYPE_INT_RGB);
		final ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
		final ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
		cmykToRgb.filter(readImage, rgbImage);

		final int width  = rgbImage.getWidth();
		final int height = rgbImage.getHeight();
		// 反転が必要
		for( int i=0;i<width;i++ ) {
			for( int j=0;j<height;j++ ) {
				int rgb = rgbImage.getRGB(i, j);
				final int rr = (rgb & 0xff0000) >> 16;
				final int gg = (rgb & 0x00ff00) >> 8;
				final int bb =  rgb & 0x0000ff ;
				rgb = (Math.abs(rr - 255) << 16) + (Math.abs(gg - 255) << 8) + (Math.abs(bb - 255));
				rgbImage.setRGB(i, j, rgb);
			}
		}

		return rgbImage;
	}

	/**
	 * 画像イメージに、文字列を動的に合成作成して返します。
	 *
	 * 描画指定の位置(x,y)は、テキストの左下の位置を、画像イメージの、左上を起点(0,0)とした
	 * 位置になります。
	 * maxW , maxH を指定すると、テキストのフォントサイズをその範囲に収まるように自動調整します。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規追加
	 *
	 * @param image 合成する元の画像オブジェクト
	 * @param text  描画される文字列
	 * @param xAxis テキストが描画される位置のx座標。または、{@link #LEFT LEFT},{@link #CENTER CENTER},{@link #RIGHT RIGHT} 指定で、自動計算する。
	 * @param yAxis テキストが描画される位置のy座標。または、{@link #TOP TOP},{@link #MIDDLE MIDDLE},{@link #BOTTOM BOTTOM} 指定で、自動計算する。
	 * @param maxW	テキストの最大幅(imageの幅と比較して小さい方の値。0以下の場合は、imageの幅)
	 * @param maxH	テキストの最大高さ(imageの高さと比較して小さい方の値。0以下の場合は、imageの高さ)
	 * @param font  描画されるテキストのフォント。null の場合は、初期値(Dialog.plain,12px)が使われる
	 * @param color 描画されるテキストの色(Color)。null の場合は、Color.BLACK が使われる
	 *
	 * @return 合成された画像オブジェクト(BufferedImage)
	 * @og.rtnNotNull
	 * @see		#mixImage( BufferedImage, String, int, int, Font, Color )
	 */
	public static BufferedImage mixImage( final BufferedImage image,
											final String text, final int xAxis, final int yAxis, final int maxW, final int maxH,
											final Font font, final Color color ) {

		final int imgWidth  = image.getWidth();					// 画像の幅
		final int imgHeight = image.getHeight();					// 画像の高さ

		final int maxWidth  = maxW <= 0 ? imgWidth  : Math.min( maxW,imgWidth );
		final int maxHeight = maxH <= 0 ? imgHeight : Math.min( maxH,imgHeight );

		final Graphics2D gph = image.createGraphics();
		if( font != null ) { gph.setFont(  font  ); }		// new Font("Serif", Font.BOLD, 14)

		float size = 5.0f;		// 小さすぎると見えないので、開始はこれくらいから行う。
		final float step = 0.5f;		// 刻み幅
		while( true ) {
			final Font tmpFont = gph.getFont().deriveFont( size );
			gph.setFont( tmpFont );

			final FontMetrics fm = gph.getFontMetrics();
			final int txtWidth  = fm.stringWidth( text );
			final int txtHeight = fm.getAscent();

			if( maxWidth < txtWidth || maxHeight < txtHeight ) {
				size -= step;	// 一つ戻しておく。場合によっては、step分戻して、stepを小さくして続ける方法もある。
				break;
			}
			size += step;
		}
		final Font newFont = gph.getFont().deriveFont( size );

		return mixImage( image, text, xAxis, yAxis, newFont, color );
	}

	/**
	 * 画像イメージに、文字列を動的に合成作成して返します。
	 *
	 * 描画指定の位置(x,y)は、テキストの左下の位置を、画像イメージの、左上を起点(0,0)とした
	 * 位置になります。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) org.opengion.hayabusa.servlet.MakeImage から、移植しました。
	 *
	 * @param image 合成する元の画像オブジェクト
	 * @param text  描画される文字列
	 * @param xAxis テキストが描画される位置のx座標。または、{@link #LEFT LEFT},{@link #CENTER CENTER},{@link #RIGHT RIGHT} 指定で、自動計算する。
	 * @param yAxis テキストが描画される位置のy座標。または、{@link #TOP TOP},{@link #MIDDLE MIDDLE},{@link #BOTTOM BOTTOM} 指定で、自動計算する。
	 * @param font  描画されるテキストのフォント。null の場合は、初期値(Dialog.plain,12px)が使われる
	 * @param color 描画されるテキストの色(Color)。null の場合は、Color.BLACK が使われる
	 *
	 * @return 合成された画像オブジェクト(BufferedImage)
	 * @og.rtnNotNull
	 * @see		#mixImage( BufferedImage, String, int, int, int, int, Font, Color )
	 */
	public static BufferedImage mixImage( final BufferedImage image,
											final String text, final int xAxis, final int yAxis,
											final Font font, final Color color ) {

		final Graphics2D gph = image.createGraphics();

	//	gph.setRenderingHint( java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON );

		if( font  != null ) { gph.setFont(  font  ); }			// new Font("Serif", Font.BOLD, 14)
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		if( color == null ) { gph.setColor( Color.BLACK ); }	// new Color(0,0,255) など
		else {				  gph.setColor( color ); }

		// 実際の位置ではなく、X軸が、LEFT,CENTER,RIGHT 等の指定
		int x1 = xAxis ;
		if( x1 < 0 ) {
			final int imgWidth = image.getWidth();					// 画像の幅
			final FontMetrics fm = gph.getFontMetrics();
			final int txtWidth = fm.stringWidth( text );				// テキストの長さ

			switch( x1 ) {
				case LEFT   : x1 = 0;							// 左寄せなので、0
								break;
				case CENTER : x1 = imgWidth/2 - txtWidth/2;		// 画像の中心から、テキストの中心を引き算
								break;
				case RIGHT  : x1 = imgWidth - txtWidth;			// 右寄せは、画像の右端からテキスト分を引き算
								break;
				default :
					final String errMsg = "X軸 で範囲外のデータが指定されました。" + "text=[" + text + "]"
										+ " (x,y)=[" + xAxis + "," + yAxis + "]" ;
					throw new OgRuntimeException( errMsg );
				//	break;		制御は移りません。
			}
		}

		// 実際の位置ではなく、Y軸が、TOP,MIDDLE,BOTTOM 等の指定
		final int Ydef = 2 ;	// 良く判らないが、位置合わせに必要。
		int y1 = yAxis ;
		if( y1 < 0 ) {
			final int imgHeight = image.getHeight() -Ydef;			// 画像の高さ
			final FontMetrics fm = gph.getFontMetrics();
			final int txtHeight = fm.getAscent() -Ydef;				// テキストの幅(=Ascent)

			switch( y1 ) {
				case TOP    : y1 = txtHeight;					// 上寄せは、テキストの幅分だけ下げる
								break;
				case MIDDLE : y1 = (imgHeight)/2 + (txtHeight)/2 ;	// 画像の中心から、テキストの中心分下げる(加算)
								break;
				case BOTTOM : y1 = imgHeight;					// 下寄せは、画像の高さ分-2
								break;
				default :
					final String errMsg = "Y軸 で範囲外のデータが指定されました。" + "text=[" + text + "]"
										+ " (x,y)=[" + xAxis + "," + yAxis + "]" ;
					throw new OgRuntimeException( errMsg );
				//	break;		制御は移りません。
			}
		}

		gph.drawString( text, x1, y1 );
		gph.dispose();		// グラフィックス・コンテキストを破棄

		return image;
	}

	/**
	 * アプリケーションのサンプルです。
	 *
	 * 入力イメージファイルを読み取って、テキストを合成して、出力イメージファイル に書き込みます。
	 * テキストの挿入位置を、X軸、Y軸で指定します。
	 * X軸とY軸には、特別な記号があり、左寄せ、右寄せ等の指示が可能です。
	 *
	 * サンプルでは、new Font("Serif", Font.PLAIN, 14); と、new Color(0,0,255);（青色）を固定で渡しています。
	 *
	 * Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル
	 *                                                     -mix テキスト X軸 Y軸 [-fname=フォント名 -fstyle=スタイル -fsize=サイズ -color=カラー]
	 *   X軸 指定(正の値は実際の位置)
	 *    -1 ･･･ LEFT    左寄せ
	 *    -2 ･･･ CENTER  中央揃え
	 *    -3 ･･･ RIGHT   右寄せ
	 *
	 *   Y軸 指定(正の値は実際の位置)
	 *    -4 ･･･ TOP     上揃え
	 *    -5 ･･･ MIDDLE  中央揃え
	 *    -6 ･･･ BOTTOM  下揃え
	 *
	 *   -fname=フォント名(初期値:Serif)
	 *    Serif , SansSerif , Monospaced , Dialog , DialogInput
	 *
	 *   -fstyle=スタイル(初期値:0:PLAIN)を、数字で選びます。
	 *    0:PLAIN ,  1:BOLD , 2:ITALIC
	 *
	 *   -fsize=サイズ(初期値:14)
	 *    フォントサイズを整数で指定します。
	 *
	 *   -color=カラー
	 *    色を表す文字列(BLUE,GREEN か、#808000 などの16bitRGB表記)
	 *
	 * Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル
	 *                                                     -trans [-color=カラー -alpha=透過率(0-100%)]
	 *   -color=カラー(初期値:WHITE)
	 *     透明色にする色を指定(BLUE,GREEN か、#808000 などの16bitRGB表記)
	 *
	 *   -alpha=透過率(0-100%)(初期値:0)
	 *     透過率は、0:透明から100不透明まで指定します。
	 *
	 *   -mask=元の色にﾏｽｸを16進数24Bitで指定します(初期値:00f0f0f0)
	 *
	 *   -useBGColor 透明色にする色を元の一番端の色を使用する(初期値:false)
	 *
	 * @og.rev 6.4.5.1 (2016/04/28) mainメソッドの起動方法を変更します。
	 * @og.rev 7.0.1.0 (2018/10/15) 色変換に、元の色の変動を吸収するﾏｽｸ属性追加
	 * @og.rev 7.0.2.1 (2019/03/04) 元の色をイメージの端から自動取得(白決め打ちでない)属性追加
	 *
	 * @param  args  引数文字列配列 入力ファイル、出力ファイル、縦横最大サイズ
	 */
	public static void main( final String[] args ) {
		if( args.length < 3 ) {
			final String usage = "Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル\n" +
							"               -mix テキスト X軸 Y軸 [-fname=フォント名 -fstyle=スタイル -fsize=サイズ -color=カラー]\n" +
							"\tX軸とY軸には、特別な記号があり、左寄せ、右寄せ等の指示が可能です。\n" +
							"\t   X軸 指定(正の値は実際の位置)\n"	+
							"\t    -1 ･･･ LEFT    左寄せ\n"			+
							"\t    -2 ･･･ CENTER  中央揃え\n"		+
							"\t    -3 ･･･ RIGHT   右寄せ\n"			+
							"\t\n"									+
							"\t   Y軸 指定(正の値は実際の位置)\n"	+
							"\t    -4 ･･･ TOP     上揃え\n"			+
							"\t    -5 ･･･ MIDDLE  中央揃え\n"		+
							"\t    -6 ･･･ BOTTOM  下揃え\n"			+
							"\t\n"									+
							"\t   -fname=フォント名(初期値:Serif)\n"	+
							"\t    Serif , SansSerif , Monospaced , Dialog , DialogInput\n"	+
							"\t\n"									+
							"\t   -fstyle=スタイル(初期値:0:PLAIN)\n"	+
							"\t    0:PLAIN ,  1:BOLD , 2:ITALIC\n"	+
							"\t\n"									+
							"\t   -fsize=サイズ(初期値:14)\n"		+
							"\t    フォントサイズを整数で指定\n"	+
							"\t\n"									+
							"\t   -color=カラー\n"					+
							"\t    色を表す文字列(BLUE,GREEN か、#808000 などの16bitRGB表記)\n"	+
							"\t\n"									+
							"Usage: java org.opengion.fukurou.util.ImageUtil 入力ファイル 出力ファイル\n" +
							"               -trans [-color=カラー -alpha=透過率(0-100%)]\n"		+
							"\t   -color=カラー\n"					+
							"\t    透明色にする色を指定(BLUE,GREEN か、#808000 などの16bitRGB表記)"	+
							"\t   -alpha=透過率(0-100%)\n"							+
							"\t    透過率は、0:透明から100不透明まで指定します。\n" +
							"\t   -mask=元の色にﾏｽｸを16進数24Bitで指定します(初期値:00f0f0f0)\n" +			// 7.0.1.0 (2018/10/15) 色変換に、ﾏｽｸ属性追加
							"\t   -useBGColor 透明色にする色を元の一番端の色を使用する(初期値:false)\n" ;	// 7.0.2.1 (2019/03/04) 透明色にする色を端から取得
			System.out.println( usage );
			return ;
		}

		final String inImg  = args[0];
		final String outImg = args[1];
		final String imgType= args[2];

//		final boolean isMix = imgType.equals( "-mix" );			// 文字列合成
//		final boolean isTrn = imgType.equals( "-trans" );		// 透過色指定

		final BufferedImage image = ImageUtil.readFile( inImg );

		final boolean isMix = imgType.equals( "-mix" );			// 文字列合成
		if( isMix ) {
			final String text   = args[3];
			final int x = Integer.parseInt( args[4] );
			final int y = Integer.parseInt( args[5] );

			String	fname  = "Serif";
			int		fstyle = Font.PLAIN;		// =0;
			int		fsize  = 14;
			Color	color  = Color.BLUE;

			for( int i=6; i<args.length; i++ ) {
				if( args[i].startsWith( "-fname="	) ) { fname		= args[i].substring( 7 ); }										// 7 = "-fname=".length()
				if( args[i].startsWith( "-fstyle="	) ) { fstyle	= Integer.parseInt(			 args[i].substring( 8 ) ); }		// 8 = "-fstyle=".length()
				if( args[i].startsWith( "-fsize="	) ) { fsize		= Integer.parseInt(			 args[i].substring( 7 ) ); }		// 7 = "-fsize=".length()
				if( args[i].startsWith( "-color="	) ) { color		= ColorMap.getColorInstance( args[i].substring( 7 ) ); }		// 7 = "-color=".length()
			}

			// 6.9.8.0 (2018/05/28) FindBugs:条件は効果がない
//			if( isMix ) {
				final Font font = new Font( fname, fstyle, fsize );
				ImageUtil.mixImage( image , text , x , y , font , color );
//			}
			ImageUtil.saveFile( image , outImg );
		}

		final boolean isTrn = imgType.equals( "-trans" );	// 透過色指定

		if( isTrn ) {
			Color	fColor	= Color.WHITE;			// 初期値は、白を透明に変換する。
			int		alpha	= 0;
			int		mask	= 0x00f0f0f0;			// 7.0.1.0 (2018/10/15) 色変換時の誤差を吸収
			boolean	useBGcol= false;				// 7.0.2.1 (2019/03/04)
//			boolean	debug	= false;

			for( int i=3; i<args.length; i++ ) {
				if( args[i].startsWith( "-color="		) ) { fColor 	= ColorMap.getColorInstance(	args[i].substring( 7 ) ); }			// 7 = "-color=".length()
				if( args[i].startsWith( "-alpha="		) ) { alpha	 	= 255/100 * Integer.parseInt(	args[i].substring( 7 ) ); }			// 7 = "-alpha=".length()
				if( args[i].startsWith( "-mask="		) ) { mask	 	= Integer.parseInt(				args[i].substring( 6 ) , 16 ); }	// 6 = "-mask=".length()
				if( args[i].startsWith( "-useBGColor"	) ) { useBGcol	= true; }															// あればtrue 7.0.2.1 (2019/03/04)
			}

			final Color tColor = new Color( fColor.getRed() , fColor.getGreen() , fColor.getBlue() , alpha );

			// 元のPNGが、完全な不透明だと、アルファ地設定が無視されるので、BufferedImage を作り直す必要がある。
			final BufferedImage transImg ;
			if( Transparency.OPAQUE == image.getTransparency() ) {		// 完全に不透明
				final int   wd = image.getWidth();
				final int   ht = image.getHeight();
				final int[] px = image.getRGB( 0,0, wd, ht, null, 0, wd );

				transImg = new BufferedImage( wd,ht,BufferedImage.TYPE_INT_ARGB );	// 透明を持てる
				transImg.setRGB( 0,0, wd, ht, px, 0 , wd );
			}
			else {
				transImg = image;
			}

			// 7.0.2.1 (2019/03/04) 元の色をイメージの端から自動取得(白決め打ちでない)属性追加
			if( useBGcol ) {
				System.out.println( inImg + " : 端色 → " + tColor + " 変換" );
				ImageUtil.changeColor( transImg , tColor , mask );					// 7.0.2.1 (2019/03/04)
			}
			else {
				System.out.println( inImg + " : " + fColor + " → " + tColor + " 変換" );
				ImageUtil.changeColor( transImg , fColor , tColor , mask );			// 7.0.1.0 (2018/10/15)
			}

			ImageUtil.saveFile( transImg , outImg );
		}
	}
}
