| 目次 | 前へ | 次へ |
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 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 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;
}
}