Java Imageio Guide
Java Imageio Guide
f t
Dra
et a
B
April 2001
All rights reserved. Copyright in this document is owned by Sun Microsystems, Inc.
Sun Microsystems, Inc. (SUN) hereby grants to you at no charge a nonexclusive, nontransferable,
worldwide, limited license (without the right to sublicense) under Sun’s intellectual property rights
in the Java Image I/O API Specification (Specification) to use the Specification for internal evalua-
tion purposes only. Other than this limited license, you acquire no right, title, or interest in or to the
Specification and you shall have no right to use the Specification for productive or commercial use.
The Specification is the confidential and proprietary information of Sun Microsystems, Inc. (Confi-
dential Information). You may not disclose such Confidential Information to any third part and
shall use it only in accordance with the terms of this license.
Use, duplication, or disclosure by the U.S. government is subject to restrictions of FAR 52.227-
14(g)(2)(6/87) and FAR 52.227-19(6/87), or DFAR 252.227- 7015(b)(6/95) and DFAR 227.7202-
1(a).
Sun, the Sun Logo, Sun Microsystems, Jini, JavaBeans, JDK, Java Solaris, NEO, Netra, NFS,
ONC, ONC+, OpenWindows, PC-NFS, EmbeddedJava, PersonalJava, SNM, SunNet Manager,
Solaris sunburst design, Solstice, SunCore, SolarNet, SunWeb, Sun Workstation, The Network Is
The Computer, ToolTalk, Ultra, Ultracomputing, Ultraserver, Where The Network Is Going, Sun
WorkShop, XView, Java WorkShop, and the Java Coffee Cup logo are trademarks or registered
trademarks of Sun Microsystems, Inc. in the United States and other countries.
Contents
1. Introduction 5
1.1 The JavaTM Image I/O API 5
1.2 About This Document 5
1.3 Platforms 6
1.4 Target Audience 6
1.5 Acknowledgements 6
2. Goals 7
2.1 API Goals 7
2.1.1 Client-Side Application Goals 8
2.1.2 Server Use Cases 9
2.2 Non-Goals 10
Contents iii
3.4 The ImageWriter Class 15
3.4.1 Writing Multiple Images 16
3.5 Handling Metadata 16
3.5.1 The IIOMetadataFormat Interface 18
3.6 Transcoding 19
3.7 Listening for Events 20
3.7.1 The IIOReadProgressListener Interface 21
3.7.2 The IIOReadUpdateListener Interface 21
3.7.3 The IIOReadWarningListener Interface 22
3.7.4 The IIOWriteProgressListener and IIOWriteWarningListener
Interfaces 22
3.8 Handling Errors using IIOException 22
Introduction
The API provides a framework for the addition of format-specific plug-ins. Plug-ins
for several common formats will be included with the standard Java SDK; other
plug-ins will be available from Sun and from third parties. A voting process has
been established where Java developers may indicate their interest in particular
formats. See:
http://developer.java.sun.com/developer/bugParade/bugs/4339476.html
1-5
1.3 Platforms
The API will be a standard part of the “Merlin” release of the JavaTM Platform
Standard Edition (J2SE version 1.4). Sun will provide an implementation of J2SE for
the Solaris, Linux, and Microsoft Windows 95/98/NT/ME (Win32) platforms. The
availibility of a particular J2SE release on other platforms is determined by the
platform vendor.
1.5 Acknowledgements
The authors would like to thank the following members of the expert group and
others who provided valuable assistance with the API design:
Jeannette Hung and Ihtisham Kabir provided excellent front-line management and
made it possible for the API and its implementation to be devloped with a minimum
of red tape. Aastha Bhardwaj, Brian Burkhalter, Jerry Evans, Ivan Wong, and John
Zimmerman provided invaluable technical assistance.
Goals
As a general rule of thumb, whenever a tradeoff between ease of use for application
developers and plug-in developers was identified, the application developer was
given a higher priority. Since relatively few users of the API are expected to write
plug-ins, and even those developers who do write plug-ins will typically write no
more than a handful in order to support formats of immediate use to them, it makes
sense to push complexity into the plug-ins rather than into applications.
2-7
2.1.1 Client-Side Application Goals
Pluggability
An application written to use the Image I/O API should be able to automatically
take advantage of new plug-ins without any rewriting or recompilation. This
requires that plug-ins follow, as much as possible, a set of format-neutral interfaces.
However, every image format has unique properties and capabilities, which plug-ins
must be able to expose to applications. This is accomplished by allowing plug-ins to
extend a number of interfaces within the API. Applications that are unaware of the
plug-in specific extensions may continue to make use of the normal plug-in
capabilities, while aware applications may use the extended interfaces.
Both generic and plug-in specific access to metadata (non-image data) is supported
by allowing plug-ins to provide access to metadata in multiple formats. These may
include plug-in specific formats, a plug-in neutral format defined by the API, and
industry standard formats.
All of the bytecode (.class) files associated with a plug-in may be combined into a
JAR file, which may be installed permanently into a JavaTM Runtime Environment,
or loaded dynamically using the application CLASSPATH mechanism.
Access to Metadata
The metadata stored alongside an image may be as important as the image data. The
JavaTM Image I/O API provides thorough yet flexible access to metadata. Since
metadata may take many forms, and contain very specialized information, it is
difficult to provide direct access to such metadata in a general-purpose API. Instead,
the API requires plug-ins to cast their metadata into the form of an XML Document
structure, possibly enhanced with Object references in addition to textual data. Once
this has been done, the metadata may be accessed and edited using standard XML
DOM (Document Object Model) interfaces. The syntax of the Documents will vary
from plug-in to plug-in, but the structure may be traversed, displayed, and edited
without specific knowledge of the plug-in being used.
Image Customization
Web images are typically one-size-fits-all, with the same image data being delivered
regardless of the display capabilities of the receiver. The increasing use of wireless
and handheld devices will require images that are customized for their limited
bandwidth and display capabilities. Desktop computers have seen increases in their
display resolution, causing many web images to appear too small. The lack of
scalable images also causes problems for visually impaired users. Server-side image
customization can be used to provide optimal images for all users, with the image
resolution and color characteristics being chosen based on user preferences.
2.2 Non-Goals
Thread Safety
A particular instance of an ImageReader or ImageWriter is not required to support
re-entrant (simultaneous) calls to its methods (with the exception of the abort
method that requests current read or writes to halt). However, it must be possible for
multiple instances of the same plug-in class to operate simultaneously. For the sake
of brevity, we will only discuss reader plug-ins below.
Supporting full re-entrancy would require that the reader bundle all of its state
information (e.g., the current input source) into a separate state object, which would
allow methods in progress to continue to work with the settings that were in effect at
the time they began, while allowing a separate thread to modify the state to be used
by the next operation.
Rather than forcing each ImageReader to keep track of its state in this way, it is
simpler to require the application to instantiate multiple instances of the same
ImageReader class if it wishes to perform multithreaded processing. This means that
the state of an ImageReader must be maintained using non-static instance variables
only, which should not be a burden for plug-in developers.
The format of the image will be auto-detected by the API based on the contents of
the file. Most image files contain a “magic number” in their first few bytes that
identifies the file format. For formats that do not have a magic number, auto-
detection may fail and somewhat more sophisticated application code will be
needed.
Additional formats may be handled by installing JAR files containing plug-ins; the
details are described in the next chapter. Once a plug-in has been installed, a new
format will be understood automatically without any changes to the application
code.
BufferedImage bi;
File f = new File(“c:\images\myimage.png”);
ImageIO.write(im, “png”, f);
3-11
The list of supported formats may be obtained by calling
ImageIO.getWriterFormatNames.
3.3 ImageReader
Rather than using the ImageIO class to perform the entire decoding operation, an
application may use the ImageIO class to obtain an ImageReader object that may be
used to perform the read:
Readers may also be retrieved based on file contents, file suffix, or MIME type. The
mechanism for locating and instantiating the reader makes use of the
javax.imageio.spi.ImageReaderSpi class, which allows information about a reader
plug-in to be retrieved without actually instantiating the plug-in. The notion of
“service provider interfaces” (SPIs) is described in detail in the following chapter.
Once a reader has been obtained, it must be supplied with an input source. Most
readers are able to read from an ImageInputStream, which is a special input source
that is defined by the Image I/O API. A special input source is used in order to
make it simple for reader and writers to work with both file and streaming I/O.
Once a source has been obtained, it may be attached to the reader by calling
reader.setInput(iis, true);
Once the reader has its input source set, we can use it to obtain information about
the image without necessarily causing image data to be read into memory. For
example, calling reader.getImageWidth(0) allows us to obtain the width of the first
image stored in the file. A well-written plug-in will attempt to decode only as much
of the file as is necessary to determine the image width, without reading any pixels.
3.3.1 ImageReadParam
More control may be obtained by supplying the read method with an additional
parameter of type ImageReadParam. An ImageReadParam allows the application to
specify a destination image in which the decoded image data should be stored,
allowing better control over memory use. It also allows a region of interest to be
specified, as well as subsampling factors that may be used to obtain a scaled-down
version of the image.
When a source region is set, the reader plug-in will attempt to decode only the
desired region, to the extent that the file format allows partial decoding. In any case,
no pixels outside the region will appear in the output. This capability makes it
possible to work with extremely large images in a limited amount of memory.
For example, to decode only the upper-left quadrant of the image, the application
first obtains an ImageReadParam that is suitable for use with the reader:
import java.awt.Rectangle;
int imageIndex = 0;
int half_width = reader.getImageWidth(imageIndex)/2;
int half_height = reader.getImageHeight(imageIndex)/2;
Rectangle rect = new Rectangle(0, 0, half_width, half_height);
param.setSourceRegion(rect);
The lower-right quadrant of the image may then be read into the same
BufferedImage that was created to hold the upper-left quadrant, overwriting the
previous pixel data:
param.setDestination(bi);
rect = new Rectangle(half_width, half_height, half_width, half_height);
param.setSourceRegion(rect);
BufferedImage bi2 = reader.read(0, param);
if (bi == bi2) {
System.out.println(“The same BufferedImage was used!”);
} else {
System.out.println(“This can’t happen!”);
}
As another example, to read every third pixel of the image, resulting in an image
one-ninth the size of the original, subsampling factors may be set in the
ImageReadParam:
param = reader.getDefaultImageParam();
param.setSourceSubsampling(3, 3, 0, 0);
BufferedImage bi3 = reader.read(0, param);
3.3.2 IIOParamController
A plug-in may optionally supply an IIOParamController object that may be used to
set up an IIOReadParam (or IIOWriteParam) using a graphical user interface (GUI), or
any other interface. A reader plug-in may attach an IIOParamController to any
ImageReadParam objects that it creates:
When the controller’s activate method is called, it displays the GUI and handles
user events such as slider movements and button presses. Typically the interface will
contain an “OK” or “Apply” button, which when pressed will cause the activate
method to return. The controller is responsible for calling methods on its associated
ImageReadParam to update its state, either in response to each GUI event, or all at
once prior to returning from activate.
Even if the number of images is not known by the application, it is still possible to
call read(imageIndex); if the index is too large, the method will throw an
IndexOutOfBoundsException. Thus, the application can request images with
increasing indices until it receives an exception.
reader.getNumThumbnails(imageIndex);
int thumbailIndex = 0;
BufferedImage bi;
bi = reader.readThumbnail(imageIndex, thumbnailIndex);
BufferedImage bi;
writer.write(bi);
The ImageWriter class contains a write method that creates a new file from an
IIOImage, and a writeInsert method that adds an IIOImage to an existing file . By
calling the two in sequence a multi-image file may be written:
Metadata may contain complex, hierarchical structures. The Java XML Document
Object Model (DOM) API is used to represent these structures, allowing developers
to leverage their knowledge of these interfaces.
In order to allow access to the metadata without the need for format-specific
application code, IIOMetadata objects expose their internal information in the form
of an XML DOM structure, which is essentially a tree of nodes of various types that
may contain a set of attributes (String values accessed by name), and which may
reference a set of child nodes.
A single plug-in may support multiple document formats, which are distinguished
by a format name. Typically, at least two formats will be supported by a given plug-
in. The first is a common, plug-in neutral format called com.sun.imageio_1.0, which
is defined in the class comment for the IIOMetadata interface. The second will be a
highly plug-in specific format that exposes all of the internal structure of the
IIOMetadata object in the form of a DOM. The latter format is referred to as the
native format of the plug-in; its name may be determined by calling the
getNativeMetadataFormatName method of the IIOMetadata object returned by the
reader (advanced users may call a method of the same name on the ImageReaderSpi
object used to instantiate the reader. The latter approach is useful for selecting a
plug-in based on its support for a particular format). The names of all of the
supported document formats may be determined similarly by calling
getMetadataFormatNames.
The contents of an IIOMetadata object may be accessed in the form of a tree of XML
Node objects by calling its getAsTree method. This method takes a String argument
which is the name of one of the document formats supported by the plug-in. This
document may then be manipulated as a standard XML DOM tree.
As an example, to print the contents of an XML DOM tree, the following code may
be used:
Executing displayMetadata on the metadata from the standard PNG test image
http://www.schaik.com/pngsuite/ccwn2c08.png yields the output:
<com.sun.imageio.png_1.0>
<IHDR width="32" height="32" bitDepth="8" colorType="RGB"
compressionMethod="deflate" filterMethod="adaptive" interlaceMethod="none"/>
<cHRM whitePointX="31270" whitePointY="32900" redX="64000" redY="33000"
greenX="30000" greenY="60000" blueX="15000" blueY="6000"/>
<gAMA value="100000"/>
</com.sun.imageio.png_1.0>
We see that the image contains IHDR, cHRM, and gAMA chunks. The interpretation of
the attribute values requires an understanding of the PNG format; however, it is still
possible for an application that does not understand PNG internals to display the
values and allow them to be edited interactively.
A node may not contain any textual data, but may contain a reference to an arbitrary
Object. The IIOMetadataFormat indicates the class type and optionally the legal
enumerated values or a range of values for the Object. Arrays are supported as well.
A DTD does not allow any attribute data types other than character strings; an XML
Schema allows extremely complex data types to be built up from simpler ones.
IIOMetadataFormat occupies a middle ground; it allows attributes to be constrained
to belong to one of a predefined set of simple data types, including integers,
floating-point decimals, and dates. Lists of these data types are also allowed.
3.6 Transcoding
Transcoding refers to reading an image file with a reader plug-in, and writing out the
image using a writer plug-in, especially, if the writer plug-in cannot directly
understand the native metadata format of the reader. In such a case, there will
always be some loss of information, even if both plug-ins deal with the same file
format. A transcoder plug-in is responsible for converting the stream and image
metadata that have been created by a reader plug-in into a better form for use by a
writer plug-in. The API does not mandate the presence of any transcoder plug-ins,
but they may be installed and registered just like readers and writers.
A set of reader and writer plug-ins may be designed to interpret each other’s
metadata without the need for a separate transcoder. The relationships between
plug-ins can be determined using the ImageReaderSpi.getImageWriterSpiNames
However, when working with plug-ins that have not been designed to interoperate,
a transcoder plug-in may be provided by a developer who understands the
metadata formats of a given pair of reader and writer plug-ins, even though each
was written without knowledge of the other.
Given a particular pair of reader and writer plug-ins, the set of appropriate
transcoder plug-ins may be located as follows:
ImageReader reader;
ImageWriter writer;
public MyReadProgressListener() {}
The imageStarted method will be called at the start of the read. During the read, the
imageProgress method will be called multiple times, each time with a different,
increasing value for its percentageDone parameter. When the read is about to
complete, the imageComplete method will be called.
Other methods exist to indicate the start and end of a sequence of image reads
performed by the ImageReader.readAll method. Additionally, it is possible for an
ongoing read to be aborted using the ImageReader.abort method; in this case, the
listener’s readAborted method will be called.
ImageReaders may specify a set of Locales for which they can provide localized
warning messages. The set of available locales can be obtained from the reader’s
getAvailableLocales method. The desired locale should then be set by calling the
reader’s setLocale method prior to attaching the IIOReadWarningListener. Each
listener will receive messages in the Locale that was in effect at the time it was
attached to the reader.
Thus, application code that attempts to provide graceful handling of errors will look
something like:
reader.setInput(stream);
try {
reader.read(0, param);
} catch (IIOException iioe2) {
System.out.println(“An error occurred during reading: “ +
iioe2.getMessage());
Throwable t = iioe2.getCause();
if ((t != null) && (t instanceof IOException)) {
System.out.println(“Caused by IOException: “ +
t.getMessage());
}
}
In order for plug-ins to be added to a running JavaTM 2 Virtual Machine1, they must
be compiled into Java bytecode files (class files). These class files will contain the
code for subclasses of various classes defined by the API. For example, a plug-in that
provides the capability to read images will include a new subclass of the abstract
javax.imageio.ImageReader class defined by the API. The usual reversed Internet
domain name convention may be used to guarantee uniqueness of class names.
The “stand-in” object is lightweight enough that it can be loaded and a single
instance instantiated every time the API is used within a given invocation of the Java
virtual machine. This pattern, in which a small class is used to provide information
about an available service, is referred to as a “service provider interface.”
1. As used in this guide, the terms “Java Virtual Machine” or “JVM” mean a virtual machine for the Java
platform.
4-25
4.2 Embedding Plug-ins in JAR Files
Since a plug-in consists of several classes, the JAR file mechanism is used to allow
them to be combined into a single file. In addition to class files, JAR files may
contain additional files used to describe their contents. In particular, a JAR file may
contain a META-INF/services directory that is used to list any service providers that
are contained in the file. For each service provider interface that is implemented by a
class stored in the JAR file, a file whose name is the fully-qualified class name of the
service provider interface is placed within the services directory. The file should
contain the fully-qualified class names of the implementation classes present in the
JAR file, one per line. For example, if the JAR file contains a service provider class
named com.mycompany.mypackage.MyImageReaderSpi, which implements the
javax.imageio.spi.ImageReaderSpi interface, there should be a file named META-
INF/services/javax.imageio.spi.ImageReaderSpi containing the line
com.mycompany.mypackage.MyImageReaderSpi.
The Image I/O API will automatically examine any JAR files that are found on the
class path, and identify those that contain Image I/O plug-ins. For each plug-in
found, a single instance of its service provider class will be instantiated and stored in
a run-time registry class, javax.imageio.spi.IIORegistry.
JAR files on the application class path (i.e., the path set using the CLASSPATH
variable), or elsewhere (e.g., available via a network URL) may be loaded by the
application. They are not loaded by default in order to minimize startup time.
Most applications should not need to deal directly with the registry. Instead, they
may make use of convenience methods contained within the
javax.imageio.ImageIO class that search for appropriate plug-ins automatically.
The format itself is defined to begin with the characters ‘myformat\n’, followed by
two four-byte integers representing the width, height, and a single byte indicating
the color type of the image, which may be either gray or RGB. Next, after a newline
character, metadata values may stored as alternating lines containing a keyword and
a value, terminated by the special keyword ‘END’. The string values are stored using
UTF8 encoding followed by a newline. Finally, the image samples are stored in left-
to-right, top-to-bottom order as either byte grayscale values, or three bytes
representing red, green, and blue.
MyFormatImageReaderSpi
The MyFormatImageReaderSpi class provides information about the plug-in,
including the vendor name, plug-in version string and description, format name, file
suffixes associated with the format, MIME types associated with the format, input
source classes that the plug-in can handle, and the ImageWriterSpis of plug-ins that
are able to interoperate specially with the reader. It also must provide an
implementation of the canDecodeInput method, which is used to locate plug-ins
based on the contents of a source image file.
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 MyFormatImageReaderSpi() {
super(vendorName, version,
names, suffixes, MIMETypes,
readerClassName,
STANDARD_INPUT_TYPE, // Accept ImageInputStreams
writerSpiNames,
supportsStandardStreamMetadataFormat,
nativeStreamMetadataFormatName,
nativeStreamMetadataFormatClassName,
extraStreamMetadataFormatNames,
extraStreamMetadataFormatClassNames,
supportsStandardImageMetadataFormat,
nativeImageMetadataFormatName,
extraImageMetadataFormatNames,
extraImageMetadataFormatClassNames);
}
Most plug-ins need read only from ImageInputStream sources, since it is possible to
“wrap” most other types of input with an appropriate ImageInputStream. However,
it is possible for a plug-in to work directly with other Objects, for example an
Object that provides an interface to a digital camera or scanner. This interface need
not provide a “stream” view of the device at all. Rather, a plug-in that is aware of the
interface may use it to drive the device directly.
MyFormatImageReader
The heart of a reader plug-in is its extension of the ImageReader class. This class is
responsible for responding to queries about the images actually stored in an input
file or stream, as well as the actual reading of images, thumbnails, and metadata. For
simplicity, we will ignore thumbnail images in this example.
package com.mycompany.imageio;
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();
}
if (stream == null) {
throw new IllegalStateException("No input stream");
}
At this point, the region of interest, subsampling, band selection, and destination
offset have been initialized. The next step is to create a suitable destination image.
The ImageReader.getDestination method will return any image that was specified
using ImageReadParam.setDestination, or else will create a suitable destination
image using a supplied ImageTypeSpecifier, in this case determined by calling
getImageTypes(0):
To reduce the amount of code we have to write, we create a Raster to hold a row’s
worth of data, and copy the pixels from that Raster into the actual image. In this
way, band selection and the details of pixel formatting are taken care of, at the
expense of an additional copy.
Now we have a byte array, rowBuf, which can be filled in from the input data, and
which is also the source of pixel data for the Raster rowRaster. We extract the
(single) tile of the destination image, and determine its extent. Then we create child
rasters of both the source and destination that select and order their bands according
to the settings previously extracted from the ImageReadParam:
// 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);
There are several additional features that readers should implement, namely
informing listeners of the progress of the read, and allowing the read process to be
aborted from another thread.
Listeners
There are three types of listeners that may be attached to a reader:
IIOReadProgressListener, IIOReadUpdateListener, and IIOReadWarningListener.
Any number of each type may be attached to a reader by means of various add and
remove methods that are implemented in the ImageReader superclass. ImageReader
also contains various process methods that broadcast information to all of the
attached listeners of a given type. For example, when the image read begins, the
method processImageStarted(imageIndex) should be called to inform all attached
IIOReadProgressListeners of the event.
More advanced readers that process incoming data in multiple passes may choose to
support IIOReadUpdateListeners, which receive more detauled information about
which pixels have been read so far. Applications may use this information to
perform selective updates of an on-screen image, for example, or to re-encode image
data in a streaming fashion.
IIOReadProgressListener Example
A typical set of IIOReadProgressListener calls might look like this:
Metadata
The next set of methods in MyFormatImageReader deal with metadata. Because our
hypothetical format only encodes a single image, we may ignore the concept of
“stream” metadata, and use “image” metadata only:
The actual work is done by a format-specific method readMetadata, which for this
format fills in the keyword/value pairs of the metadata object,
metadata.keywords.add(keyword);
metadata.values.add(value);
} catch (IIOException e) {
throw new IIOException(“Exception reading metadata”,
e);
}
}
}
MyFormatMetadata
Finally, the various interfaces for extracting and editing metadata must be defined.
We define a class called MyFormatMetadata that extends the IIOMetadata class, and
additionally can store the keyword/value pairs that are allowed in the file format:
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;
// Keyword/value pairs
List keywords = new ArrayList();
List values = new ArrayList();
public MyFormatMetadata() {
super(standardMetadataFormatSupported,
nativeMetadataFormatName,
nativeMetadataFormatClassName,
extraMetadataFormatNames,
extraMetadataFormatClassNames);
return root;
}
For writer plug-ins, the ability to edit metadata values is obtained by implementing
the isReadOnly, reset, and mergeTree methods:
MyFormatMetadataFormat
The tree structure of the metadata may be described using the IIOMetadataFormat
interface. An implementation class, IIOMetadataFormatImpl, takes care of
maintaining the “database” of information about elements, their attributes, and the
parent-child relationships between them:
package com.mycompany.imageio;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataFormatImpl;
MyFormatImageWriterSpi
The MyFormatImageWriterSpi call plays a similar role to the
MyFormatImageReaderSpi class discussed in the previous section. However, instead
of being responsible for determining whether a given stream can be read, it must
deterine whether an image in memory can be written. Rather than inspecting the
image itself, an ImageTypeSpecifier is used so that writers may be selected before
an actual image is available.
package com.mycompany.imageio;
public MyFormatImageWriterSpi() {
super(vendorName, version,
names, suffixes, MIMETypes,
writerClassName,
STANDARD_OUTPUT_TYPE, // Write to ImageOutputStreams
readerSpiNames,
supportsStandardStreamMetadataFormat,
nativeStreamMetadataFormatName,
nativeStreamMetadataFormatClassName,
extraStreamMetadataFormatNames,
extraStreamMetadataFormatClassNames,
supportsStandardImageMetadataFormat,
nativeImageMetadataFormatName,
nativeImageMetadataFormatClassName,
extraImageMetadataFormatNames,
extraImageMetadataFormatClassNames);
}
MyFormatImageWriter
package com.mycompany.imageio;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
The format only handles image metadata. The convertImageMetadata method does
very little; it could be defined to interpret the metadata classes used by other plug-
ins.
public IIOMetadata
getDefaultImageMetadata(ImageTypeSpecifier imageType,
ImageWriteParam param) {
return new MyFormatMetadata();
}
The actual writing of the image requires first applying the source region, source
bands, and subsampling factors from the ImageWriteParam. The source region and
source bands may be handled by creating a child Raster. For simplicity, we extract a
single Raster from the source image. If the source image is tiled, we can save
memory by extracting smaller Rasters as needed.
Rectangle sourceRegion =
new Rectangle(0, 0, im.getWidth(), im.getHeight());
int sourceXSubsampling = 1;
int sourceYSubsampling = 1;
int[] sourceBands = null;
if (param != null) {
sourceRegion =
sourceRegion.intersection(param.getSourceRegion());
sourceXSubsampling = param.getSourceXSubsampling();
sourceYSubsampling = param.getSourceYSubsampling();
sourceBands = param.getSourceBands();
Once the image dimensions and color type of the image have been ascertained, the
plug-in is ready to write the file header:
try {
byte[] signature = {
(byte)’m’, (byte)’y’, (byte)’f’, (byte)’o’,
(byte)’r’, (byte)’m’, (byte)’a’, (byte)’t’
};
// Output header information
stream.write(signature);
stream.write(‘\n’);
stream.writeInt(width);
stream.writeInt(height);
stream.writeByte(colorType);
stream.write(‘\n’);
Next, the plug-in extracts the image metadata from the write method’s IIOImage
argument, and attempts to convert it into a MyFormatMetadata object by calling
convertImageMetadata. If the result is non-null, the keywords and values are
extracted from the metadata and written to the output:
stream.writeUTF(keyword);
stream.write(‘\n’);
stream.writeUTF(value);
stream.write(‘\n’);
}
}
Finally, the plug-in is ready to begin writing the pixel data. The image Raster is
copied into an int array, one row at a time using the getPixels method. Then these
values are subsampled using the horizontal subsampling factor, and copied into a
byte array, which is written to the output with a single write call. The source row is
then incremented by the vertical subsampling factor until the end of the source
region is reached, and the output stream is flushed:
The ImageTranscoder may use the standard interfaces to unpack the incoming
metadata, or it may make use of interfaces that are specific to the actual object at
hand. For example, it could access the keywords and values instance variables of the
MyFormatMetadata class defined above; these were made public, but not
documented, precisely in order to allow a transcoder plug-in developer to access
them without having to go through a DOM representation.