目次 | 前の項目 | 次の項目 | JavaTM Image I/O API ガイド |
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); } } }