目次 | 前へ | 次へ |
ImageReaderSpi
のサブクラスと、ImageReader
のサブクラスで構成されます。さらにオプションとして、ストリームメタデータおよびイメージメタデータを表現する IIOMetadata
インタフェースの実装や、メタデータの構造を記述する IIOMetadataFormat
オブジェクトを含めることもできます。
この後の説明では、「MyFormat」という架空のファイル形式用の単純な読み込みプラグインを実装する過程を概観します。このプラグインは、MyFormatImageReaderSpi
、MyFormatImageReader
、および 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
メソッドは、ImageTypeSpecifier
の Iterator
を返します。
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 dataImageReadParam の処理 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 J2SDK 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; }書き込みプラグインの場合、メタデータの値を編集する機能を実現するには、下記の
isReadOnly
、reset
、および 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; } }