目次 | 前の項目 | 次の項目 JavaTM Image I/O API ガイド


4.4 書き込みプラグインの作成

MyFormatImageWriterSpi
MyFormatImageWriterSpi クラスは、前のセクションで説明した MyFormatImageReaderSpi クラスと同様の役割を果たします。ただし、特定のストリームを読み込めるかどうかを判別するのではなく、メモリ内のイメージを書き込めるかどうかを判別しなければなりません。また、実際のイメージを入手する前に書き込みプラグインを選択できるようにするため、イメージそのものを調べる代わりに、ImageTypeSpecifier を使用します。

package com.mycompany.imageio;

import java.io.IOException;
import java.util.Locale;
import javax.imageio.ImageWriter;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;

public class MyFormatImageWriterSpi extends ImageWriterSpi {

	static final String vendorName = "My Company";
	static final String version = "1.0_beta33_build9467";
	static final String writerClassName =
		"com.mycompany.imageio.MyFormatImageWriter";
	static final String[] names = { "myformat" };
	static final String[] suffixes = { "myf" };
	static final String[] MIMETypes = { "image/x-myformat" };
	static final String[] readerSpiNames = {
		"com.mycompany.imageio.MyFormatImageReaderSpi" };
    
	static final boolean supportsStandardStreamMetadataFormat = false;
	static final String nativeStreamMetadataFormatName = null;
	static final String nativeStreamMetadataFormatClassName = null;
	static final String[] extraStreamMetadataFormatNames = null;
	static final String[] extraStreamMetadataFormatClassNames = null;
	static final boolean supportsStandardImageMetadataFormat = false;
	static final String nativeImageMetadataFormatName =
		"com.mycompany.imageio.MyFormatMetadata_1.0";
	static final String nativeImageMetadataFormatClassName =
		"com.mycompany.imageio.MyFormatMetadata";
	static final String[] extraImageMetadataFormatNames = null;
	static final String[] extraImageMetadataFormatClassNames = null;
    
	public MyFormatImageWriterSpi() {
		super(vendorName, version,
		      names, suffixes, MIMETypes,
		      writerClassName,
		      STANDARD_OUTPUT_TYPE, // Write to ImageOutputStreams
		      readerSpiNames,
		      supportsStandardStreamMetadataFormat,
		      nativeStreamMetadataFormatName,
		      nativeStreamMetadataFormatClassName,
		      extraStreamMetadataFormatNames,
		      extraStreamMetadataFormatClassNames,
		      supportsStandardImageMetadataFormat,
		      nativeImageMetadataFormatName,
		      nativeImageMetadataFormatClassName,
		      extraImageMetadataFormatNames,
		      extraImageMetadataFormatClassNames);
	}

	public boolean canEncodeImage(ImageTypeSpecifier imageType) {
		int bands = imageType.getNumBands();
		return bands == 1 || bands == 3;
	}
    
	public String getDescription(Locale locale) {
		// Localize as appropriate
		return "Description goes here";
	}

	public ImageWriter createWriterInstance(Object extension) {
	    return new MyFormatImageWriter(this);
	}
}
MyFormatImageWriter

package com.mycompany.imageio;

import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;

public class MyFormatImageWriter extends ImageWriter {

	ImageOutputStream stream = null;

	public MyFormatImageWriter(ImageWriterSpi originatingProvider) {
		super(originatingProvider);
	}
 
	public void setOutput(Object output) {
		super.setOutput(output);
		if (output != null) {
			if (!(output instanceof ImageOutputStream)) {
				throw new IllegalArgumentException
					("output not an ImageOutputStream!");
			}
			this.stream = (ImageOutputStream)output;
		} else {
			this.stream = null;
		}
	}

getDefaultWriteParam から返される ImageWriteParam は、書き込みプラグインの機能に基づいてカスタマイズしなければなりません。この書き込みプラグインは、タイリング、プログレシブ方式のエンコーディング、および圧縮をサポートしていないため、それぞれ false または null という値を渡します。

	// Tiling, progressive encoding, compression are disabled by default
	public ImageWriteParam getDefaultWriteParam() {
		return new ImageWriteParam(getLocale());
	}

このファイル形式は、イメージメタデータだけを取り扱います。convertImageMetadata メソッドは、ほとんどなにも実行しませんが、必要なら、ほかのプラグインによって使用されるメタデータクラスを解釈するように定義することもできます。

	public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
		return null;
	}

	public IIOMetadata
		getDefaultImageMetadata(ImageTypeSpecifier imageType,
		                        ImageWriteParam param) {
		return new MyFormatMetadata();
	}

	public IIOMetadata convertStreamMetadata(IIOMetadata inData,
	                                         ImageWriteParam param) {
		return null;
	}

	public IIOMetadata convertImageMetadata(IIOMetadata inData,
	                                        ImageTypeSpecifier imageType,
	                                        ImageWriteParam param) {
		// We only understand our own metadata
		if (inData instanceof MyFormatMetadata) {
			return inData;
		} else {
			return null;
		}
	}

実際にイメージを書き込むには、まず、ソース領域、ソースバンド、およびサブサンプリング係数を ImageWriteParam から適用する必要があります。ソース領域とソースバンドは、子 Raster を作成することで処理できます。簡単にするため、ソースイメージから 1 つの Raster を抽出します。ソースイメージがタイリングされている場合は、必要に応じて小さい Raster を抽出することでメモリを節約できます。

	public void write(IIOMetadata streamMetadata,
	                  IIOImage image,
	                  ImageWriteParam param) throws IIOException {
		RenderedImage im = image.getRenderedImage();

		Rectangle sourceRegion =
			new Rectangle(0, 0, im.getWidth(), im.getHeight());
		int sourceXSubsampling = 1;
		int sourceYSubsampling = 1;
		int[] sourceBands = null;
		if (param != null) {
			sourceRegion =
				sourceRegion.intersection(param.getSourceRegion());
			sourceXSubsampling = param.getSourceXSubsampling();
			sourceYSubsampling = param.getSourceYSubsampling();
			sourceBands = param.getSourceBands();

			int subsampleXOffset = param.getSubsamplingXOffset();
			int subsampleYOffset = param.getSubsamplingYOffset();
			sourceRegion.x += subsampleXOffset;
			sourceRegion.y += subsampleYOffset;
			sourceRegion.width -= subsampleXOffset;
			sourceRegion.height -= subsampleYOffset;
		}

		// Grab a Raster containing the region of interest
		int width = sourceRegion.width;
		int height = sourceRegion.height;
		Raster imRas = im.getData(sourceRegion);
		int numBands = imRas.getNumBands();

		// Check that sourceBands values are in range
		if (sourceBands != null) {
			for (int i = 0; i < sourceBands.length; i++) {
				if (sourceBands[i] >= numBands) {
					throw new IllegalArgumentException("bad band!");
				}
			}
		}

		// Translate imRas to start at (0, 0) and subset the bands
		imRas = imRas.createChild(sourceRegion.x, sourceRegion.y,
		                          width, height,
		                          0, 0,
		                          sourceBands);

		// Reduce width and height according to subsampling factors
		width = (width + sourceXSubsampling - 1)/sourceXSubsampling;
		height = (height + sourceYSubsampling - 1)/sourceYSubsampling;

		// Assume 1 band image is grayscale, 3 band image is RGB
		int colorType;
		if (numBands == 1) {
			colorType = MyFormatImageReader.COLOR_TYPE_GRAY;
		} else if (numBands == 3) {
			colorType = MyFormatImageReader.COLOR_TYPE_RGB;
		} else {
			throw new IIOException("Image must have 1 or 3 bands!");
		}
イメージの寸法とカラータイプを確認できたら、プラグインは、いつでもファイルヘッダを書き込むことができます。

		try {
			byte[] signature = {
				(byte)'m', (byte)'y',  (byte)'f', (byte)'o',
				(byte)'r', (byte)'m',  (byte)'a', (byte)'t'
			};
			// Output header information
			stream.write(signature);
			stream.write(`\n');
			stream.writeInt(width);
			stream.writeInt(height);
			stream.writeByte(colorType);
			stream.write(`\n');
			
次に、write メソッドの IIOImage 引数からイメージメタデータを抽出し、convertImageMetadata を呼び出してメタデータを MyFormatMetadata オブジェクトに変換してみます。その結果が null 以外であれば、メタデータからキーワードと値を抽出して、出力に書き込みます。

			// Attempt to convert metadata, if present
			IIOMetadata imd = image.getMetadata();
			MyFormatMetadata metadata = null;
			if (imd != null) {
				ImageTypeSpecifier type =
					ImageTypeSpecifier.createFromRenderedImage(im);
				metadata =
					(MyFormatMetadata)convertImageMetadata(imd,
					                                       type,
					                                       null);
			}

			// Output metadata if present
			if (metadata != null) {
				Iterator keywordIter = metadata.keywords.iterator();
				Iterator valueIter = metadata.values.iterator();
				while (keywordIter.hasNext()) {
					String keyword = (String)keywordIter.next();
					String value = (String)valueIter.next();
					
					stream.writeUTF(keyword);
					stream.write(`\n');
					stream.writeUTF(value);
					stream.write(`\n');
				}
			}
			stream.writeUTF("END");
			stream.write(`\n');
これで、ピクセルデータを書き込む準備ができました。イメージ Raster を、getPixels メソッドを使用して一度に 1 行ずつ int 配列にコピーします。その後、それらの値を水平サブサンプリング係数を使ってサブサンプリングし、その結果を byte 配列にコピーし、その配列を 1 回の write 呼び出しで出力に書き込みます。そして、ソース行を垂直サブサンプリング係数の分だけインクリメントし、ソース領域の最後まで繰り返します。最後に、出力ストリームをフラッシュします。

			// Output (subsampled) pixel values
			int rowLength = width*numBands;
			int xSkip = sourceXSubsampling*numBands;
			int[] rowPixels = imRas.getPixels(0, 0, width, 1,
			                                  (int[])null);
			byte[] rowSamples = new byte[rowLength];

			// Output every (sourceYSubsampling)^th row
			for (int y = 0; y < height; y += sourceYSubsampling) {
				imRas.getPixels(0, y, width, 1, rowPixels);

				// Subsample horizontally and convert to bytes
				int count = 0;
				for (int x = 0; x < width; x += xSkip) {
					if (colorType ==
						MyFormatImageReader.COLOR_TYPE_GRAY) {
						rowSamples[count++] = (byte)rowPixels[x];
					} else {
						rowSamples[count++] = (byte)rowPixels[x];
						rowSamples[count++] =
							(byte)rowPixels[x + 1];
						rowSamples[count++] =
							(byte)rowPixels[x + 2];
					}
				}

				// Output a row's worth of bytes
				stream.write(rowSamples, 0, width*numBands);
			}
			stream.flush();
		} catch (IOException e) {
			throw new IIOException("I/O error!", e);
		}
	}
}


目次 | 前の項目 | 次の項目
Copyright © 2001 Sun Microsystems, Inc. All Rights Reserved.