目次||

4.3読込みプラグインの作成

最小構成の読込みプラグインは、ImageReaderSpiのサブクラスと、ImageReaderのサブクラスで構成されます。さらにオプションとして、ストリーム・メタデータおよびイメージ・メタデータを表現するIIOMetadataインタフェースの実装や、メタデータの構造を記述するIIOMetadataFormatオブジェクトを含めることもできます。

この後の説明では、「MyFormat」という架空のファイル形式用の単純な読込みプラグインを実装する過程を概観します。このプラグインは、MyFormatImageReaderSpiMyFormatImageReader、およびMyFormatMetadataの各クラスで構成されます。

この架空のファイル形式は、「myformat\n」という文字列で始まり、そのあと、イメージの幅と高さを表す4バイトの整数が2つ、イメージのカラー・タイプ(モノクロまたはRGB)を表す1バイトが1つ続きます。次に、改行文字のあと、メタデータの値が、キーワードの入った行と値の入った行が交互に続き、最後に特殊なキーワード「END」が置かれます。文字列値は、UTF8エンコーディングを使用して格納され、末尾に改行が置かれます。最後に、イメージ・データが、左から右、上から下の順に、1バイトのグレー・スケール値か、赤/緑/青を表す3バイトの値として格納されます。

MyFormatImageReaderSpi MyFormatImageReaderSpiクラスは、プラグインについての情報を提供します。その情報には、ベンダー名、プラグインのバージョン文字列と説明、ファイル形式の名前、その形式に対応するファイル拡張子、その形式に対応するMIMEタイプ、プラグインが処理できる入力ソースのクラス、および特にこの読取りプラグインと一緒に問題なく利用できるImageWriterSpiが含まれます。さらに、このクラスは、canDecodeInputメソッドの実装も提供する必要があります。このメソッドは、ソース・イメージ・ファイルの内容に基づいてプラグインを検索するために使用されます。

ImageReaderSpiクラスは、自身のメソッドのほとんどについて実装を提供しています。それらのメソッドは、主として、各種のprotectedインスタンス変数の値を返します。MyFormatImageReaderSpiは、そのインスタンス変数の値を、直接に、またはスーパー・クラス・コンストラクタを介して設定できます。次の例を参照してください。

package com.mycompany.imageio;

import java.io.IOException;
import java.util.Locale;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;

public class MyFormatImageReaderSpi extends ImageReaderSpi {

        static final String vendorName = "My Company";
        static final String version = "1.0_beta33_build9467";
        static final String readerClassName =
                "com.mycompany.imageio.MyFormatImageReader";
        static final String[] names = { "myformat" };
        static final String[] suffixes = { "myf" };
        static final String[] MIMETypes = {
                "image/x-myformat" };
        static final String[] writerSpiNames = {
                "com.mycompany.imageio.MyFormatImageWriterSpi" };

        // Metadata formats, more information below
        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 MyFormatImageReaderSpi() {
                super(vendorName, version,
                      names, suffixes, MIMETypes,
                      readerClassName,
                      STANDARD_INPUT_TYPE, // Accept ImageInputStreams
                      writerSpiNames,
                      supportsStandardStreamMetadataFormat,
                      nativeStreamMetadataFormatName,
                      nativeStreamMetadataFormatClassName,
                      extraStreamMetadataFormatNames,
                      extraStreamMetadataFormatClassNames,
                      supportsStandardImageMetadataFormat,
                      nativeImageMetadataFormatName,
                      extraImageMetadataFormatNames,
                      extraImageMetadataFormatClassNames);
                }

                public String getDescription(Locale locale) {
                        // Localize as appropriate
                        return "Description goes here";
                }

                public boolean canDecodeInput(Object input)
                        throws IOException {
                        // see below
                }

            public ImageReader createReaderInstance(Object extension) {
                return new MyFormatImageReader(this);
            }
        }

大部分のプラグインは、ImageInputStreamソースからのみ読み取れれば十分です。その他の種類の入力はほとんど、適切なImageInputStreamで「ラップする」ことが可能であるためです。しかし、プラグインが他のObject (たとえば、デジタル・カメラまたはスキャナへのインタフェースを提供するObject)を直接に処理することもできます。このインタフェースは、デバイスの「ストリーム」ビューを提供する必要はまったくありません。そうではなく、このインタフェースを認識しているプラグインは、対象のデバイスを直接に駆動するためにそのインタフェースを使用できます。

プラグインが処理できる入力クラスは、getInputTypesメソッドを介して通知します。このメソッドからは、Classオブジェクトの配列を返します。getInputTypesの実装はスーパー・クラスの中に提供されています。これはinputTypesインスタンス変数の値を返します。この値はスーパー・クラス・コンストラクタの7番目の引数によって設定されます。上の例で使用されている値STANDARD_INPUT_TYPEは、単一のjavax.imageio.stream.ImageInputStream.class要素が入っている配列の省略表記で、このプラグインがImageInputStreamのみを受け入れることを示しています。

canDecodeInputメソッドは、2つの事柄を判別する役割を果たします。第1に、入力パラメータが、そのプラグインが理解できるクラスのインスタンスであるかどうかを判別します。第2に、ファイルの内容が、そのプラグインによって処理される形式になっているかどうかを判別します。入力ソースの状態は、入力パラメータがメソッドに渡された時と同じ状態にしておかなければなりません。ImageInputStream入力ソースの場合は、markおよびresetメソッドを利用できます。たとえば、「MyFormat」形式のファイルはすべて「myformat」という文字列で始まっているので、canDecodeInputメソッドは次のように実装できます。

public boolean canDecodeInput(Object input) {
        if (!(input instanceof ImageInputStream)) {
                return false;
        }

        ImageInputStream stream = (ImageInputStream)input;
        byte[] b = new byte[8];
        try {
                stream.mark();
                stream.readFully(b);
                stream.reset();
        } catch (IOException e) {
                return false;
        }

        // Cast unsigned character constants prior to comparison
        return (b[0] == (byte)'m' && b[1] == (byte)'y' &&
                b[2] == (byte)'f' && b[3] == (byte)'o' &&
                b[4] == (byte)'r' && b[5] == (byte)'m' &&               
                b[6] == (byte)'a' && b[7] == (byte)'t');
}
MyFormatImageReader 読込みプラグインを作成する際に中心となるのは、ImageReaderクラスを拡張する作業です。このクラスは、入力ファイルまたは入力ストリームに格納されているイメージについての照会に応答し、イメージ、サムネイル、およびメタデータを実際に読み込む役割を果たします。説明を簡潔にするため、この例ではサムネイル・イメージを省略します。

架空のMyFormatImageReaderクラスのメソッドをいくつか紹介すると、次のようになります。

package com.mycompany.imageio;

public class MyFormatImageReader extends ImageReader {

        ImageInputStream stream = null;
        int width, height;
        int colorType;
        
        // Constants enumerating the values of colorType
        static final int COLOR_TYPE_GRAY = 0;
        static final int COLOR_TYPE_RGB = 1;

        boolean gotHeader = false;

        public MyFormatImageReader(ImageReaderSpi originatingProvider) {
                super(originatingProvider);
        }

        public void setInput(Object input, boolean isStreamable) {
                super.setInput(input, isStreamable);
                if (input == null) {
                        this.stream = null;
                        return;
                }
                if (input instanceof ImageInputStream) {
                        this.stream = (ImageInputStream)input;
                } else {
                        throw new IllegalArgumentException("bad input");
                }
        }

        public int getNumImages(boolean allowSearch)
                throws IIOException {
                return 1; // format can only encode a single image
        }

        private void checkIndex(int imageIndex) {
                if (imageIndex != 0) {
                        throw new IndexOutOfBoundsException("bad index");
                }
        }

        public int getWidth(int imageIndex)
                throws IIOException {
                checkIndex(imageIndex); // must throw an exception if != 0
                readHeader();
                return width;
        }

        public int getHeight(int imageIndex)
                throws IIOException {
                checkIndex(imageIndex);
                readHeader();
                return height;
        }
getImageTypesメソッド 読込みプラグインは、デコードされた出力を保持するためにどんな種類のイメージを使用できるかを示さなければなりません。そのために、ImageTypeSpecifierクラスを使用して、有効なイメージ・レイアウトを示すSampleModelおよびColorModelを保持します。getImageTypesメソッドは、ImageTypeSpecifierIteratorを返します。
        public Iterator getImageTypes(int imageIndex)
                throws IIOException {
                checkIndex(imageIndex);
                readHeader();

                ImageTypeSpecifier imageType = null;
                int datatype = DataBuffer.TYPE_BYTE;
                java.util.List l = new ArrayList();
                switch (colorType) {
                case COLOR_TYPE_GRAY:
                        imageType = ImageTypeSpecifier.createGrayscale(8,
                                                                       datatype,
                                                                       false);
                        break;

                case COLOR_TYPE_RGB:
                        ColorSpace rgb =
                                ColorSpace.getInstance(ColorSpace.CS_sRGB);
                        int[] bandOffsets = new int[3];
                        bandOffsets[0] = 0;
                        bandOffsets[1] = 1;
                        bandOffsets[2] = 2;
                        imageType =
                                ImageTypeSpecifier.createInterleaved(rgb,
                                                                     bandOffsets,
                                                                     datatype,
                                                                     false,
                                                                     false);
                        break;                          
                }
                l.add(imageType);
                return l.iterator();
        }

イメージ・ヘッダーの解析 これまでに紹介したメソッドのいくつかでは、readHeaderメソッドを利用していました。これらのメソッドは、イメージの幅、高さ、およびレイアウトを判別するのに必要なだけの情報を入力ストリームから読み込む役割を果たしています。readHeaderは、何回も呼び出しても安全なように定義されています(ただし、マルチスレッドのアクセスは考慮に入れていない)。
        public void readHeader() {
                if (gotHeader) {
                        return;
                }
                gotHeader = true;

                if (stream == null) {
                        throw new IllegalStateException("No input stream");
                }

                // Read `myformat\n' from the stream
                byte[] signature = new byte[9];
                try {
                        stream.readFully(signature);
                } catch (IOException e) {
                        throw new IIOException("Error reading signature", e);
                }
                if (signature[0] != (byte)'m' || ...) { // etc.
                        throw new IIOException("Bad file signature!");
                }
                // Read width, height, color type, newline
                try {
                        this.width = stream.readInt();
                        this.height = stream.readInt();
                        this.colorType = stream.readUnsignedByte();
                        stream.readUnsignedByte(); // skip newline character
                } catch (IOException e) {
                        throw new IIOException("Error reading header", e)
                }
        }
イメージの実際の読込みは、次のようなreadメソッドによって処理されます。
        public BufferedImage read(int imageIndex, ImageReadParam param)
                throws IIOException {
                readMetadata(); // Stream is positioned at start of image data

ImageReadParamの処理 readメソッドの最初のセクションでは、提供されたImageReadParamオブジェクトを使用して、ソース・イメージのどの領域を読み込むのか、どんな種類のサブサンプリングを適用するのか、バンドの選択と再配置、およびデスティネーションでのオフセットを判別します。
          
        // Compute initial source region, clip against destination later
        Rectangle sourceRegion = getSourceRegion(param, width, height);
                
        // Set everything to default values
        int sourceXSubsampling = 1;
        int sourceYSubsampling = 1;
        int[] sourceBands = null;
        int[] destinationBands = null;
        Point destinationOffset = new Point(0, 0);

        // Get values from the ImageReadParam, if any
        if (param != null) {
                sourceXSubsampling = param.getSourceXSubsampling();
                sourceYSubsampling = param.getSourceYSubsampling();
                sourceBands = param.getSourceBands();
                destinationBands = param.getDestinationBands();
                destinationOffset = param.getDestinationOffset();
        }

この時点までに、読み取る対象の領域、サブサンプリング、バンドの選択およびデスティネーションのオフセットが初期化されます。次のステップは、適切なデスティネーション・イメージを作成します。ImageReader.getDestinationメソッドは、ImageReadParam.setDestinationを使用して以前に指定されたイメージがあればそのイメージを返し、そうでない場合は、提供されるImageTypeSpecifier (この場合はgetImageTypes(0)を呼び出すことで取得される)を使用して適切なデスティネーション・イメージを作成します。次の例を参照してください。
        // Get the specified detination image or create a new one
        BufferedImage dst = getDestination(param,
                                           getImageTypes(0),
                                           width, height);
        // Enure band settings from param are compatible with images
        int inputBands = (colorType == COLOR_TYPE_RGB) ? 3 : 1;
        checkReadParamBandSettings(param, inputBands,
                                   dst.getSampleModel().getNumBands());

記述しなければならないコードの量を減らすため、1行分のデータを保持するRasterを作成し、ピクセルはそのRasterから実際のイメージにコピーします。この方法により、バンドの選択やピクセルのフォーマット設定の詳細は、その追加のコピー操作の際に処理されます。
        int[] bandOffsets = new int[inputBands];
        for (int i = 0; i < inputBands; i++) {
                bandOffsets[i] = i;
        }
        int bytesPerRow = width*inputBands;
        DataBufferByte rowDB = new DataBufferByte(bytesPerRow);
        WritableRaster rowRas =
                Raster.createInterleavedRaster(rowDB,
                                               width, 1, bytesPerRow,
                                               inputBands, bandOffsets,
                                               new Point(0, 0));
        byte[] rowBuf = rowDB.getData();

        // Create an int[] that can a single pixel
        int[] pixel = rowRas.getPixel(0, 0, (int[])null);

ここで、バイト配列rowBufを用意しました。この配列は、入力データから情報を入れることができ、RasterオブジェクトrowRasterに入れるピクセル・データのソースにもなります。デスティネーション・イメージのタイルを(1つ)抽出し、その範囲を決定します。その後、ソースとデスティネーションの両方について子ラスターを作成します。子ラスターでは、以前にImageReadParamから抽出した設定に基づいてバンドを選択し、並べます。次の例を参照してください。
          
        WritableRaster imRas = dst.getWritableTile(0, 0);
        int dstMinX = imRas.getMinX();
        int dstMaxX = dstMinX + imRas.getWidth() - 1;
        int dstMinY = imRas.getMinY();
        int dstMaxY = dstMinY + imRas.getHeight() - 1;

        // Create a child raster exposing only the desired source bands
        if (sourceBands != null) {
                rowRas = rowRas.createWritableChild(0, 0,
                                                    width, 1,
                                                    0, 0,
                                                    sourceBands);
        }

        // Create a child raster exposing only the desired dest bands
        if (destinationBands != null) {
                imRas = imRas.createWritableChild(0, 0,
                                                  imRas.getWidth(),
                                                  imRas.getHeight(),
                                                  0, 0,
                                                  destinationBands);
        }
ピクセル・データの読み込み これで、イメージからのピクセル・データの読込みを開始する用意が調いました。このあとの処理では、行全体を読み込んで、サブサンプリングとデスティネーションのクリッピングを実行します。水平方向のクリッピングは、サブサンプリングを考慮に入れる必要があるため、複雑になります。ここでは、ピクセルごとのクリッピングを実行しますが、より高度な読込みプラグインなら、水平方向のクリッピングを1回で実行できるものもあります。
        for (int srcY = 0; srcY < height; srcY++) {
                // Read the row
                try {
                        stream.readFully(rowBuf);
                } catch (IOException e) {
                        throw new IIOException("Error reading line " + srcY, e);
                }
                        
                // Reject rows that lie outside the source region,
                // or which aren't part of the subsampling
                if ((srcY < sourceRegion.y) ||
                    (srcY >= sourceRegion.y + sourceRegion.height) ||
                    (((srcY - sourceRegion.y) %
                      sourceYSubsampling) != 0)) {
                        continue;
                }

                // Determine where the row will go in the destination
                int dstY = destinationOffset.y +
                        (srcY - sourceRegion.y)/sourceYSubsampling;
                if (dstY < dstMinY) {
                        continue; // The row is above imRas
                }
                if (dstY > dstMaxY) {
                        break; // We're done with the image
                }

                // Copy each (subsampled) source pixel into imRas
                for (int srcX = sourceRegion.x;
                     srcX < sourceRegion.x + sourceRegion.width;
                     srcX++) {
                        if (((srcX - sourceRegion.x) % sourceXSubsampling) != 0) {
                                continue;
                        }
                        int dstX = destinationOffset.x +
                                (srcX - sourceRegion.x)/sourceXSubsampling;
                        if (dstX < dstMinX) {
                                continue;  // The pixel is to the left of imRas
                        }
                        if (dstX > dstMaxX) {
                                break; // We're done with the row
                        }

                        // Copy the pixel, sub-banding is done automatically
                        rowRas.getPixel(srcX, 0, pixel);
                        imRas.setPixel(dstX, dstY, pixel);
                }
        }
        return dst;
パフォーマンスを向上させるため、sourceXSubsamplingが1のケースを別々に切り離すことができます。複数のピクセルを一度にコピーすることが可能だからです。次の例を参照してください。
        // Create an int[] that can hold a row's worth of pixels
        int[] pixels = rowRas.getPixels(0, 0, width, 1, (int[])null);

        // Clip against the left and right edges of the destination image
        int srcMinX =
                Math.max(sourceRegion.x,
                     dstMinX - destinationOffset.x + sourceRegion.x);
      int srcMaxX =
                Math.min(sourceRegion.x + sourceRegion.width - 1,
                         dstMaxX - destinationOffset.x + sourceRegion.x);
        int dstX = destinationOffset.x + (srcMinX - sourceRegion.x);
        int w = srcMaxX - srcMinX + 1;
        rowRas.getPixels(srcMinX, 0, w, 1, pixels);
        imRas.setPixels(dstX, dstY, w, 1, pixels);
                    
読込みプラグインが実装する必要のある機能は、ほかにもいくつかあります。リスナーに読込みの進捗を通知する機能や、読込みプロセスを別のスレッドから中止させる機能などです。 リスナー 読込みプラグインに付加できるリスナーは、IIOReadProgressListener、IIOReadUpdateListener、およびIIOReadWarningListenerの3種類です。ImageReaderスーパー・クラスに実装されている各種のaddメソッドおよびremoveメソッドを利用して、それぞれの種類のリスナーを任意の数だけ読込みプラグインに付加できます。ImageReaderには、付加された特定の種類のリスナーすべてに情報を通知する、各種のprocessメソッドも含まれています。たとえば、イメージの読込みを開始したら、processImageStarted(imageIndex)メソッドを呼び出して、付加されたすべてのIIOReadProgressListenerにそのイベントを通知する必要があります。

読取りプラグインは、通常、プラグインのreadメソッドの最初と最後で、processImageStartedおよびprocessImageCompleteメソッドをそれぞれ呼び出さなければなりません。processImageProgressメソッドは、少なくとも2 - 3行の走査線を読み取るたびに、推定の読取り完了率とともに呼び出す必要があります。この完了率は、1つのイメージの読取り中に減少することがあってはなりません。読取りプラグインがサムネイルをサポートしている場合は、サムネイルについての対応するprogressメソッドも呼び出す必要があります。IIOReadProgressListenerのprocessSequenceStartedおよびprocessSequenceCompleteの各メソッドを呼び出す必要があるのは、そのプラグインで、スーパー・クラスのreadAllの実装をオーバーライドした場合のみです。

入力データを複数パスで処理するような、さらに高度な読込みプラグインでは、どのピクセルがそれまでに読み込まれたかについてもっと詳細な情報を受け取れるIIOReadUpdateListenersをサポートすることもできます。アプリケーションはその情報を利用して、たとえば、画面上のイメージを選択的に更新したり、イメージ・データをストリーム方式でエンコードし直したりできます。

読込みプロセスの中止 1つのスレッドがイメージの読込みを実行しているときに、その読込みプラグインのabortメソッドを別のメソッドから非同期に呼び出すことができます。読込みを実行しているスレッドは、読込みプラグインのステータスをabortRequestedメソッドを使って定期的にポーリングし、デコードの中止を途中で試みる必要があります。部分的にデコードされたイメージが返されてきますが、読込みプラグインは、そのイメージの内容について保証する必要はありません。たとえば、視覚的になんの意味もない圧縮されたデータまたは暗号化されたデータがDataBufferに入っていてもかまいません。 IIOReadProgressListenerの例 IIOReadProgressListenerの典型的な呼出しは、たとえば次のようになります。

public BufferedImage read(int imageIndex, ImageReadParam param)
        throws IOException {
        // Clear any previous abort request
        boolean aborted = false;
        clearAbortRequested();

        // Inform IIOReadProgressListeners of the start of the image
        processImageStarted(imageIndex);

        // Compute xMin, yMin, xSkip, ySkip from the ImageReadParam
        // ...

        // Create a suitable image
        BufferedImage theImage = new BufferedImage(...);

        // Compute factors for use in reporting percentages
        int pixelsPerRow = (width - xMin + xSkip - 1)/xSkip;
        int rows = (height - yMin + ySkip - 1)/ySkip;
        long pixelsDecoded = 0L;
        long totalPixels = rows*pixelsPerRow;

        for (int y = yMin; y < height; y += yskip) {
                // Decode a (subsampled) scanline of the image
                // ...

                // Update the percentage estimate
                // This may be done only every few rows if desired
                pixelsDecoded += pixelsPerRow;
                processImageProgress(100.0F*pixelsDecoded/totalPixels);

                // Check for an asynchronous abort request
                if (abortRequested()) {
                        aborted = true;
                        break;
                }
        }

        // Handle the end of decoding
        if (aborted) {
                processImageAborted();
        } else {
                processImageComplete(imageIndex);
        }

        // If the read was aborted, we still return a partially decoded image
        return theImage;
}
メタデータ 次に考えるMyFormatImageReaderのメソッド群は、メタデータを処理するものです。いま考えている架空のファイル形式でエンコードできるのは単一のイメージのみなので、「ストリーム」メタデータの概念は無視し、「イメージ」メタデータだけを使用できます。次の例を参照してください。
        MyFormatMetadata metadata = null; // class defined below

        public IIOMetadata getStreamMetadata()
                throws IIOException {
                return null;
        }

        public IIOMetadata getImageMetadata(int imageIndex)
                throws IIOException {
                if (imageIndex != 0) {
                        throw new IndexOutOfBoundsException("imageIndex != 0!");
                }
                readMetadata();
                return metadata;
        }

実際の処理は、ファイル形式に固有のメソッドreadMetadataによって実行されます。この例のファイル形式の場合は、このメソッドにより、メタデータ・オブジェクトのキーワード/値のペアが設定されます。
        public void readMetadata() throws IIOException {
                if (metadata != null) {
                        return;
                }
                readHeader();
                this.metadata = new MyFormatMetadata();
                try {
                        while (true) {
                                String keyword = stream.readUTF();
                                stream.readUnsignedByte();
                                if (keyword.equals("END")) {
                                        break;
                                }
                                String value = stream.readUTF();
                                stream.readUnsignedByte();

                                metadata.keywords.add(keyword);
                                metadata.values.add(value);
                        } catch (IIOException e) {
                                throw new IIOException("Exception reading metadata",
                                                       e);
                        }
                }
        }

MyFormatMetadata 最後に、メタデータを抽出したり編集したりするための各種インタフェースを定義しなければなりません。この例では、IIOMetadataクラスを拡張して、この例のファイル形式でメタデータとして設定できるキーワード/値のペアを格納できるようにした、MyFormatMetadataというクラスを定義します。
package com.mycompany.imageio;

import org.w3c.dom.*;
import javax.xml.parsers.*; // Package name may change in JDK 1.4

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataNode;

public class MyFormatMetadata extends IIOMetadata {

        static final boolean standardMetadataFormatSupported = false;
        static final String nativeMetadataFormatName =
                "com.mycompany.imageio.MyFormatMetadata_1.0";
        static final String nativeMetadataFormatClassName =
                "com.mycompany.imageio.MyFormatMetadata";
        static final String[] extraMetadataFormatNames = null;
        static final String[] extraMetadataFormatClassNames = null;
    
        // Keyword/value pairs
        List keywords = new ArrayList();
        List values = new ArrayList();
下記の最初のメソッド群は、IIOMetadataのほとんどの実装に共通です。
        public MyFormatMetadata() {
                super(standardMetadataFormatSupported,
                      nativeMetadataFormatName,
                      nativeMetadataFormatClassName,
                      extraMetadataFormatNames,
                      extraMetadataFormatClassNames);
        }

        public IIOMetadataFormat getMetadataFormat(String formatName) {
                if (!formatName.equals(nativeMetadataFormatName)) {
                        throw new IllegalArgumentException("Bad format name!");
                }
                return MyFormatMetadataFormat.getDefaultInstance();
        }

読込みプラグインの場合にもっとも重要なメソッドは、次に例を示すgetAsTreeです。
        public Node getAsTree(String formatName) {
                if (!formatName.equals(nativeMetadataFormatName)) {
                        throw new IllegalArgumentException("Bad format name!");
                }

                // Create a root node
                IIOMetadataNode root =
                        new IIOMetadataNode(nativeMetadataFormatName);

                // Add a child to the root node for each keyword/value pair
                Iterator keywordIter = keywords.iterator();
                Iterator valueIter = values.iterator();
                while (keywordIter.hasNext()) {
                        IIOMetadataNode node =
                                new IIOMetadataNode("KeywordValuePair");
                        node.setAttribute("keyword", (String)keywordIter.next());
                        node.setAttribute("value", (String)valueIter.next());
                        root.appendChild(node);
                }

                return root;
        }
書込みプラグインの場合、メタデータの値を編集する機能を実現するには、下記のisReadOnlyreset、およびmergeTreeの各メソッドを実装します。
        public boolean isReadOnly() {
            return false;
        }

        public void reset() {
            this.keywords = new ArrayList();
            this.values = new ArrayList();
        }

        public void mergeTree(String formatName, Node root)
                throws IIOInvalidTreeException {
                if (!formatName.equals(nativeMetadataFormatName)) {
                        throw new IllegalArgumentException("Bad format name!");
                }

                Node node = root;
                if (!node.getNodeName().equals(nativeMetadataFormatName)) {
                        fatal(node, "Root must be " + nativeMetadataFormatName);
                }
                node = node.getFirstChild();
                while (node != null) {
                        if (!node.getNodeName().equals("KeywordValuePair")) {
                                fatal(node, "Node name not KeywordValuePair!");
                        }
                        NamedNodeMap attributes = node.getAttributes();
                        Node keywordNode = attributes.getNamedItem("keyword");
                        Node valueNode = attributes.getNamedItem("value");
                        if (keywordNode == null || valueNode == null) {
                                fatal(node, "Keyword or value missing!");
                        }

                        // Store keyword and value
                        keywords.add((String)keywordNode.getNodeValue());
                        values.add((String)valueNode.getNodeValue());

                        // Move to the next sibling
                        node = node.getNextSibling();
                }
        }

        private void fatal(Node node, String reason)
                throws IIOInvalidTreeException {
                throw new IIOInvalidTreeException(reason, node);
        }
}
MyFormatMetadataFormat メタデータのツリー構造は、IIOMetadataFormatインタフェースを使用して記述できます。実装クラスであるIIOMetadataFormatImplは、要素、要素の属性および要素間の親子関係についての情報の「データベース」を維持管理します。次の例を参照してください。
package com.mycompany.imageio;

import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataFormatImpl;

public class MyFormatMetadataFormat extends IIOMetadataFormatImpl {

        // Create a single instance of this class (singleton pattern)
        private static MyFormatMetadataFormat defaultInstance =
                new MyFormatMetadataFormat();

        // Make constructor private to enforce the singleton pattern
        private MyFormatMetadataFormat() {
                // Set the name of the root node
                // The root node has a single child node type that may repeat
                super("com.mycompany.imageio.MyFormatMetadata_1.0",
                      CHILD_POLICY_REPEAT);

                // Set up the "KeywordValuePair" node, which has no children
                addElement("KeywordValuePair",
                           "com.mycompany.imageio.MyFormatMetadata_1.0",
                           CHILD_POLICY_EMPTY);

                // Set up attribute "keyword" which is a String that is required
                // and has no default value
                addAttribute("KeywordValuePair", "keyword", DATATYPE_STRING,
                             true, null);
                // Set up attribute "value" which is a String that is required
                // and has no default value
                addAttribute("KeywordValuePair", "value", DATATYPE_STRING,
                             true, null);
        }

        // Check for legal element name
        public boolean canNodeAppear(String elementName,
                                     ImageTypeSpecifier imageType) {
                return elementName.equals("KeywordValuePair");
        }

        // Return the singleton instance
        public static MyFormatMetadataFormat getDefaultInstance() {
                return defaultInstance;
        }
}


目次||

Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.