目次||

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 © 1993, 2020, Oracle and/or its affiliates. All rights reserved.