Spec-Zone .ru
спецификации, руководства, описания, API
|
СОДЕРЖАНИЕ | ПРЕДЫДУЩИЙ | NEXT |
ImageReaderSpi
подкласс, и ImageReader
подкласс. Дополнительно, это может содержать реализации IIOMetadata
интерфейс, представляющий поток и метаданные изображения, и IIOMetadataFormat
объект, описывающий структуру метаданных. В следующих разделах мы изобразим схематически реализацию простого плагина читателя для гипотетического формата по имени "MyFormat". Это будет состоять из классов MyFormatImageReaderSpi
, MyFormatImageReader
, и MyFormatMetadata
.
Сам формат определяется, чтобы начаться с символов `myformat\n', сопровождается двумя четырехбайтовыми целыми числами, представляющими width, height, и единственный байт, указывающий на цветной тип изображения, которое может быть или серым или RGB. Затем, после символа новой строки, значения метаданных могут сохраненный как переменные строки, содержащие ключевое слово и значение, завершенное специальным ключевым словом `КОНЕЦ'. Строковые значения сохранены, используя кодирование UTF8, сопровождаемое новой строкой. Наконец, выборки изображения сохранены в слева направо, порядок от начала до конца или как полутоновые значения байта, или как три байта, представляющие красный, зеленый, и синий.
MyFormatImageReaderSpi
MyFormatImageReaderSpi
class предоставляет информацию о плагине, включая имя поставщика, сменную строку версии и описание, имя формата, суффиксы файла, связанные с форматом, типы MIME, связанные с форматом, входные исходные классы, которые плагин может обработать, и ImageWriterSpi
s плагинов, которые в состоянии взаимодействовать особенно с читателем. Это также должно обеспечить реализацию canDecodeInput
метод, который используется, чтобы определить местоположение плагинов, основанных на содержании файла исходного изображения. ImageReaderSpi
class обеспечивает реализации большинства своих методов. Эти методы, главным образом, возвращают значение различных защищенных переменных экземпляра, который 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
s, например Object
это обеспечивает интерфейс для цифрового фотоаппарата или сканера. Этот интерфейс не должен обеспечить "потоковое" представление устройства вообще. Скорее плагин, который знает об интерфейсе, может использовать его, чтобы управлять устройством непосредственно. Плагин дает объявление, которые вводят классы, которые он может обработать через getInputTypes
метод, который возвращает массив Class
объекты. Реализация getInputTypes
обеспечивается в суперклассе, который возвращает значение inputTypes
переменная экземпляра, которая поочередно устанавливается седьмым параметром конструктору суперкласса. Значение, используемое в примере выше, STANDARD_INPUT_TYPE
, сокращение для массива, содержащего единственный элемент javax.imageio.stream.ImageInputStream.class
, указание, что плагин принимает только ImageInputStream
s.
canDecodeInput
метод ответственен за определение двух вещей: во-первых, ли входной параметр является экземпляром class, который плагин может понять, и во-вторых, кажется ли содержание файла, находится в формате, обработанном плагином. Это должно оставить свой ввод в том же самом состоянии, как это было, когда в этом передали. Для ImageInputStream
входной источник, метка и методы сброса могут использоваться. Например, так как файлы в "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
class. Этот class ответственен за отвечание на запросы об изображениях, фактически сохраненных во входном файле или потоке, так же как фактическом чтении изображений, миниатюр, и метаданных. Для простоты мы проигнорируем изображения миниатюры в этом примере. Эскиз некоторых из методов гипотетического MyFormatImageReader class показывают ниже:
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
class используется, чтобы содержать a SampleModel
и ColorModel
указание на юридическое расположение изображения. getImageTypes
метод возвращается Iterator
из ImageTypeSpecifier
s: 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(); }Анализируя Заголовок Изображения Несколько из методов выше зависят от a
readHeader
метод, который ответственен за чтение достаточного количества входного потока, чтобы определить width, height, и расположение изображения. 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, который первый раздел метода обеспокоен использованием предоставленного объекта 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());Чтобы уменьшить объем кода, мы должны записать, мы создаем a
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
. Мы извлекаем (единственную) мозаику целевого изображения, и определяем его степень. Затем мы создаем дочерние растры и источника и места назначения, которые выбирают и упорядочивают их полосы согласно настройкам, ранее извлеченным из 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); }Читая Пиксельные данные Теперь мы готовы начать пиксельные данные чтения с изображения. Мы считаем целые строки, и выполним подвыборку и место назначения, отсекающее, поскольку мы продолжаем. Горизонтальное отсечение усложняется потребностью принять подвыборку во внимание. Здесь мы выполняем отсечение на пиксель; более сложный читатель мог выполнить горизонталь, отсекающую однажды:
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. Любое число каждого типа может быть присоединено к читателю посредством различного, добавляют и удаляют методы, которые реализуются в суперклассе ImageReader. ImageReader также содержит различные методы процесса, которые широковещательно передают информацию всем присоединенным слушателям данного типа. Например, когда чтение изображения начинается, метод processImageStarted (imageIndex) нужно вызвать, чтобы сообщить, что все присоединили IIOReadProgressListeners события.
Плагин читателя обычно ответственен за вызов processImageStarted и processImageComplete вначале и конца его метода чтения, соответственно. processImageProgress нужно вызвать, по крайней мере, каждыми несколькими строками развертки с оценкой завершения процента чтения. Важно, чтобы этот процент никогда не уменьшился во время чтения единственного изображения. Если читатель поддерживает миниатюры, corresponsing методы продвижения миниатюры нужно вызвать также. processSequenceStarted и processSequenceComplete методы IIOReadProgressListener только нужно вызвать, если плагин переопределяет реализацию суперкласса readAll.
Более усовершенствованные читатели, которые обрабатывают входящие данные в многократных передачах, могут хотеть поддерживать IIOReadUpdateListeners, которые получают больше detauled информации, о которой пиксели до сих пор читались. Приложения могут использовать эту информацию, чтобы выполнить выборочные обновления экранного изображения, например, или повторно закодировать данные изображения способом потоковой передачи.
Прерывая Процесс считывания, В то время как один поток выполняет чтение изображения, другой поток может вызвать метод аварийного прекращения работы читателя асинхронно. Поток чтения должен, периодически опрашивать состояние читателя используя 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
Наконец, различные интерфейсы для извлечения и редактирования метаданных должны быть определены. Мы определяем вызванный class MyFormatMetadata
это расширяется IIOMetadata
class, и дополнительно может сохранить пар ключевого слова/значения, которые позволяются в формате файла: 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. Реализация class, 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; } }