/*
Java Media APIs: Cross-Platform Imaging, Media and Visualization
Alejandro Terrazas
Sams, Published November 2002,
ISBN 0672320940
*/
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.util.Locale;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
import org.w3c.dom.Node;
/**
* Simple, functional ImageWriterSpi used to understand how information
* regarding format name, suffices and mime types get passed to ImageIO static
* methods
*/
public class ch5ImageWriterSpi extends ImageWriterSpi {
static final String[] suffixes = { "ch5", "CH5" };
static final String[] names = { "ch5" };
static final String[] MIMETypes = { "image/ch5" };
static final String version = "1.00";
static final String writerClassName = "ch5.imageio.plugins.ch5ImageWriter";
static final String vendorName = "Company Name";
static final String[] readerSpiNames = { "ch5.imagio.plugins.ch5ImageReaderSpi" };
/*
* static final String nativeStreamMetadataFormatName =
* "ch5.imageio.ch5stream_1.0"; static final String[]
* streamMetadataFormatNames = {nativeStreamMetadataFormatName}; static
* final String nativeImageMetadataFormatName = "ch5.imageio.ch5image_1.0";
* static final String[] imageMetadataFormatNames =
* {nativeImageMetadataFormatName};
*/
static final String nativeStreamMetadataFormatName = "ch5.imageio.ch5stream_1.00";
static final String nativeStreamMetadataFormatClassName = "ch5.imageio.ch5stream";
static final String[] extraStreamMetadataFormatNames = { null };
static final String[] extraStreamMetadataFormatClassNames = { null };
static final String nativeImageMetadataFormatName = "ch5.imageio.ch5image_1.00";
static final String nativeImageMetadataFormatClassName = "ch5.imageio.ch5image";
static final String[] extraImageMetadataFormatNames = { null };
static final String[] extraImageMetadataFormatClassNames = { null };
public ch5ImageWriterSpi() {
super(vendorName, version, names, suffixes, MIMETypes, writerClassName,
STANDARD_OUTPUT_TYPE, readerSpiNames, false,
nativeStreamMetadataFormatName,
nativeStreamMetadataFormatClassName,
extraStreamMetadataFormatNames,
extraStreamMetadataFormatClassNames, false,
nativeImageMetadataFormatName,
nativeImageMetadataFormatClassName,
extraImageMetadataFormatNames,
extraImageMetadataFormatClassNames);
}
public String getDescription(Locale locale) {
return "Demo ch5 image writer, version " + version;
}
public ImageWriter createWriterInstance(Object extension) {
return new ch5ImageWriter(this);
}
/**
* This method gets called when an application wants to see if the
* corresponding ImageWriter can encode an image with a ColorModel and
* SampleModel specified by the ImageTypeSpecifier
*/
public boolean canEncodeImage(ImageTypeSpecifier its) {
if (its.getBufferedImageType() == BufferedImage.TYPE_BYTE_GRAY)
return true;
else
return false;
}
}
class ch5v1ImageWriter extends ImageWriter {
public ch5v1ImageWriter(ImageWriterSpi originatingProvider) {
super(originatingProvider);
streamMetadataWritten = false;
}
/**
* this method returns null for now. We will revisit it at the end of this
* chapter after metadata has been discussed.
*/
public IIOMetadata convertImageMetadata(IIOMetadata metadata,
ImageTypeSpecifier its, ImageWriteParam param) {
return null;
}
/**
* this method returns null for now. We will revisit it at the end of this
* chapter after metadata has been discussed.
*/
public IIOMetadata convertStreamMetadata(IIOMetadata metadata,
ImageWriteParam param) {
return null;
}
/**
* this method returns null for now. We will revisit it at the end of this
* chapter after metadata has been discussed.
*/
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier its,
ImageWriteParam param) {
return null;
}
/**
* this method returns null for now. We will revisit it at the end of this
* chapter after metadata has been discussed.
*/
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
return null;
}
/**
* write out the output image specified by index imageIndex using the
* parameters specified by the ImageWriteParam object param
*/
public void write(IIOMetadata metadata, IIOImage iioimage,
ImageWriteParam param) {
Node root = null;
Node dimensionsElementNode = null;
if (iioimage.getRenderedImage() != null)
raster = iioimage.getRenderedImage().getData();
else
raster = iioimage.getRaster();
/*
* since this format allows you to write multiple images, the
* streamMetadataWritten variable makes sure the stream metadata is
* written only once. Not using metadata in this version, so using
* default value of 1 image per output stream.
*/
if (streamMetadataWritten == false) {
try {
ios.writeBytes("5\n");
ios.writeBytes("1");
ios.flush();
} catch (IOException ioe) {
System.err.println("IOException " + ioe.getMessage());
}
streamMetadataWritten = true;
}
int sourceWidth = raster.getWidth();
int sourceHeight = raster.getHeight();
int destinationWidth = -1;
int destinationHeight = -1;
int sourceRegionWidth = -1;
int sourceRegionHeight = -1;
int sourceRegionXOffset = -1;
int sourceRegionYOffset = -1;
int xSubsamplingFactor = -1;
int ySubsamplingFactor = -1;
if (param == null)
param = getDefaultWriteParam();
/*
* get Rectangle object which will be used to clip the source image's
* dimensions.
*/
Rectangle sourceRegion = param.getSourceRegion();
if (sourceRegion != null) {
sourceRegionWidth = (int) sourceRegion.getWidth();
sourceRegionHeight = (int) sourceRegion.getHeight();
sourceRegionXOffset = (int) sourceRegion.getX();
sourceRegionYOffset = (int) sourceRegion.getY();
/*
* correct for overextended source regions
*/
if (sourceRegionXOffset + sourceRegionWidth > sourceWidth)
destinationWidth = sourceWidth - sourceRegionXOffset;
else
destinationWidth = sourceRegionWidth;
if (sourceRegionYOffset + sourceRegionHeight > sourceHeight)
destinationHeight = sourceHeight - sourceRegionYOffset;
else
destinationHeight = sourceRegionHeight;
} else {
destinationWidth = sourceWidth;
destinationHeight = sourceHeight;
sourceRegionXOffset = sourceRegionYOffset = 0;
}
/*
* get subsampling factors
*/
xSubsamplingFactor = param.getSourceXSubsampling();
ySubsamplingFactor = param.getSourceYSubsampling();
destinationWidth = (destinationWidth - 1) / xSubsamplingFactor + 1;
destinationHeight = (destinationHeight - 1) / ySubsamplingFactor + 1;
byte[] sourceBuffer;
byte[] destinationBuffer = new byte[destinationWidth];
try {
ios.writeBytes(new String("\n"));
ios.writeBytes(new String(destinationWidth + "\n"));
ios.writeBytes(new String(destinationHeight + "\n"));
int jj;
int index;
for (int j = 0; j < sourceWidth; j++) {
sourceBuffer = (byte[]) raster.getDataElements(0, j,
sourceWidth, 1, null);
jj = j - sourceRegionYOffset;
if (jj % ySubsamplingFactor == 0) {
jj /= ySubsamplingFactor;
if ((jj >= 0) && (jj < destinationHeight)) {
for (int i = 0; i < destinationWidth; i++) {
index = sourceRegionXOffset + i
* xSubsamplingFactor;
destinationBuffer[i] = sourceBuffer[index];
}
ios.write(destinationBuffer, 0, destinationWidth);
ios.flush();
}
}
}
} catch (IOException e) {
System.err.println("IOException: " + e.getMessage());
}
}
public void setOutput(Object output) {
super.setOutput(output);
if (output == null)
throw new IllegalArgumentException("output is null");
if (!(output instanceof ImageOutputStream))
throw new IllegalArgumentException(
"output not an ImageOutputStream");
ios = (ImageOutputStream) output;
streamMetadataWritten = false;
}
private ImageOutputStream ios;
private boolean streamMetadataWritten;
private Raster raster;
}
class ch5ImageWriter extends ImageWriter {
public ch5ImageWriter(ImageWriterSpi originatingProvider) {
super(originatingProvider);
streamMetadataWritten = false;
}
/**
* this method is used to convert an ImageReader's image metadata which is
* in a particular format into image metadata that can be used for this
* ImageWriter. Primarily this is used for transcoding (format conversion).
* This ImageWriter does not support such conversions
*/
public IIOMetadata convertImageMetadata(IIOMetadata metadata,
ImageTypeSpecifier specifier, ImageWriteParam param) {
return null;
}
/**
* this method is used to convert an ImageReader's stream metadata which is
* in a particular format into stream metadata that can be used for this
* ImageWriter. Primarily this is used for transcoding (format conversion).
* This ImageWriter does not support such conversions
*/
public IIOMetadata convertStreamMetadata(IIOMetadata metadata,
ImageWriteParam param) {
return null;
}
/**
* provide default values for the image metadata
*/
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier specifier,
ImageWriteParam param) {
ch5ImageMetadata imagemd = new ch5ImageMetadata();
int width = raster.getWidth();
int height = raster.getHeight();
imagemd.initialize(width, height); // default image size
return imagemd;
}
/**
* provide default values for the stream metadata
*/
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
ch5StreamMetadata streammd = new ch5StreamMetadata();
streammd.initialize(1); // default number of images
return streammd;
}
/**
* write out the output image specified by index imageIndex using the
* parameters specified by the ImageWriteParam object param
*/
public void write(IIOMetadata metadata, IIOImage iioimage,
ImageWriteParam param) {
Node root = null;
Node dimensionsElementNode = null;
if (iioimage.getRenderedImage() != null)
raster = iioimage.getRenderedImage().getData();
else
raster = iioimage.getRaster();
/*
* since this format allows you to write multiple images, the
* streamMetadataWritten variable makes sure the stream metadata is
* written only once
*/
if (streamMetadataWritten == false) {
if (metadata == null)
metadata = getDefaultStreamMetadata(param);
root = metadata.getAsTree("ch5.imageio.ch5stream_1.00");
dimensionsElementNode = root.getFirstChild();
Node numberImagesAttributeNode = dimensionsElementNode
.getAttributes().getNamedItem("numberImages");
String numberImages = numberImagesAttributeNode.getNodeValue();
try {
ios.writeBytes("5\n");
ios.writeBytes(numberImages);
ios.flush();
} catch (IOException ioe) {
System.err.println("IOException " + ioe.getMessage());
}
streamMetadataWritten = true;
}
String widthString;
String heightString;
IIOMetadata imageMetadata = (ch5ImageMetadata) iioimage.getMetadata();
/*
* don't really need image metadata object here since raster knows
* necessary information
*/
if (imageMetadata == null)
imageMetadata = getDefaultImageMetadata(null, param);
root = imageMetadata.getAsTree("ch5.imageio.ch5image_1.00");
dimensionsElementNode = root.getFirstChild();
Node widthAttributeNode = dimensionsElementNode.getAttributes()
.getNamedItem("imageWidth");
widthString = widthAttributeNode.getNodeValue();
Node heightAttributeNode = dimensionsElementNode.getAttributes()
.getNamedItem("imageHeight");
heightString = heightAttributeNode.getNodeValue();
int sourceWidth = Integer.parseInt(widthString);
int sourceHeight = Integer.parseInt(heightString);
int destinationWidth = -1;
int destinationHeight = -1;
int sourceRegionWidth = -1;
int sourceRegionHeight = -1;
int sourceRegionXOffset = -1;
int sourceRegionYOffset = -1;
int xSubsamplingFactor = -1;
int ySubsamplingFactor = -1;
if (param == null)
param = getDefaultWriteParam();
/*
* get Rectangle object which will be used to clip the source image's
* dimensions.
*/
Rectangle sourceRegion = param.getSourceRegion();
if (sourceRegion != null) {
sourceRegionWidth = (int) sourceRegion.getWidth();
sourceRegionHeight = (int) sourceRegion.getHeight();
sourceRegionXOffset = (int) sourceRegion.getX();
sourceRegionYOffset = (int) sourceRegion.getY();
/*
* correct for overextended source regions
*/
if (sourceRegionXOffset + sourceRegionWidth > sourceWidth)
destinationWidth = sourceWidth - sourceRegionXOffset;
else
destinationWidth = sourceRegionWidth;
if (sourceRegionYOffset + sourceRegionHeight > sourceHeight)
destinationHeight = sourceHeight - sourceRegionYOffset;
else
destinationHeight = sourceRegionHeight;
} else {
destinationWidth = sourceWidth;
destinationHeight = sourceHeight;
sourceRegionXOffset = sourceRegionYOffset = 0;
}
/*
* get subsampling factors
*/
xSubsamplingFactor = param.getSourceXSubsampling();
ySubsamplingFactor = param.getSourceYSubsampling();
destinationWidth = (destinationWidth - 1) / xSubsamplingFactor + 1;
destinationHeight = (destinationHeight - 1) / ySubsamplingFactor + 1;
byte[] sourceBuffer;
byte[] destinationBuffer = new byte[destinationWidth];
try {
ios.writeBytes(new String("\n"));
ios.writeBytes(new String(destinationWidth + "\n"));
ios.writeBytes(new String(destinationHeight + "\n"));
int jj;
int index;
for (int j = 0; j < sourceWidth; j++) {
sourceBuffer = (byte[]) raster.getDataElements(0, j,
sourceWidth, 1, null);
jj = j - sourceRegionYOffset;
if (jj % ySubsamplingFactor == 0) {
jj /= ySubsamplingFactor;
if ((jj >= 0) && (jj < destinationHeight)) {
for (int i = 0; i < destinationWidth; i++) {
index = sourceRegionXOffset + i
* xSubsamplingFactor;
destinationBuffer[i] = sourceBuffer[index];
}
ios.write(destinationBuffer, 0, destinationWidth);
ios.flush();
}
}
}
} catch (IOException e) {
System.err.println("IOException: " + e.getMessage());
}
}
public void setOutput(Object output) {
super.setOutput(output);
if (output == null)
throw new IllegalArgumentException("output is null");
if (!(output instanceof ImageOutputStream))
throw new IllegalArgumentException(
"output not an ImageOutputStream");
ios = (ImageOutputStream) output;
streamMetadataWritten = false;
}
private ImageOutputStream ios;
private boolean streamMetadataWritten;
private Raster raster;
}
/**
* ch5ImageMetadata.java -- holds image metadata for the ch5 format.
* The internal tree for holding this metadata is read only
*/
class ch5ImageMetadata extends IIOMetadata {
static final String
nativeMetadataFormatName = "ch5.imageio.ch5image_1.00";
static final String
nativeMetadataFormatClassName = "ch5.imageio.ch5image";
static final String[] extraMetadataFormatNames = null;
static final String[] extraMetadataFormatClassNames = null;
static final boolean standardMetadataFormatSupported = false;
public int imageWidth;
public int imageHeight;
public ch5ImageMetadata() {
super(standardMetadataFormatSupported,
nativeMetadataFormatName,
nativeMetadataFormatClassName,
extraMetadataFormatNames,
extraMetadataFormatClassNames
);
imageWidth = -1;
imageHeight = -1;
}
public boolean isReadOnly() {
return true;
}
/**
* IIOMetadataFormat objects are meant to describe the structure of
* metadata returned from the getAsTree method. In this case,
* no such description is available
*/
public IIOMetadataFormat getMetadataFormat(String formatName) {
if (formatName.equals(nativeMetadataFormatName)) {
return null;
} else {
throw new IllegalArgumentException("Unrecognized format!");
}
}
/**
* returns the image metadata in a tree corresponding to the
* provided formatName
*/
public Node getAsTree(String formatName) {
if (formatName.equals(nativeMetadataFormatName)) {
return getNativeTree();
} else {
throw new IllegalArgumentException("Unrecognized format!");
}
}
/**
* returns the image metadata in a tree using the following format
* <!ELEMENT ch5.imageio.ch5image_1.00 (imageDimensions)>
* <!ATTLIST imageDimensions
* imageWidth CDATA #REQUIRED
* imageHeight CDATA #REQUIRED
*/
private Node getNativeTree() {
IIOMetadataNode root =
new IIOMetadataNode(nativeMetadataFormatName);
IIOMetadataNode node = new IIOMetadataNode("imageDimensions");
node.setAttribute("imageWidth", Integer.toString(imageWidth));
node.setAttribute("imageHeight", Integer.toString(imageHeight));
root.appendChild(node);
return root;
}
public void setFromTree(String formatName, Node root) {
throw new IllegalStateException("Metadata is read-only!");
}
public void mergeTree(String formatName, Node root) {
throw new IllegalStateException("Metadata is read-only!");
}
public void reset() {
throw new IllegalStateException("Metadata is read-only!");
}
/**
* initialize the image metadata elements width and height
*/
public void initialize(int width, int height) {
imageWidth = width;
imageHeight = height;
}
}
/**
* ch5StreamMetadata.java -- holds stream metadata for the ch5 format. The
* internal tree for holding this metadata is read only
*/
class ch5StreamMetadata extends IIOMetadata {
static final String nativeMetadataFormatName = "ch5.imageio.ch5stream_1.00";
static final String nativeMetadataFormatClassName = "ch5.imageio.ch5stream";
static final String[] extraMetadataFormatNames = null;
static final String[] extraMetadataFormatClassNames = null;
static final boolean standardMetadataFormatSupported = false;
public int numberImages;
public ch5StreamMetadata() {
super(standardMetadataFormatSupported, nativeMetadataFormatName,
nativeMetadataFormatClassName, extraMetadataFormatNames,
extraMetadataFormatClassNames);
numberImages = -1;
}
public boolean isReadOnly() {
return true;
}
/**
* IIOMetadataFormat objects are meant to describe the structure of metadata
* returned from the getAsTree method. In this case, no such description is
* available
*/
public IIOMetadataFormat getMetadataFormat(String formatName) {
if (formatName.equals(nativeMetadataFormatName)) {
return null;
} else {
throw new IllegalArgumentException("Unrecognized format!");
}
}
/**
* returns the stream metadata in a tree corresponding to the provided
* formatName
*/
public Node getAsTree(String formatName) {
if (formatName.equals(nativeMetadataFormatName)) {
return getNativeTree();
} else {
throw new IllegalArgumentException("Unrecognized format!");
}
}
/**
* returns the stream metadata in a tree using the following format
* <!ELEMENT ch5.imageio.ch5stream_1.00 (imageDimensions)> <!ATTLIST
* imageDimensions numberImages CDATA #REQUIRED
*/
private Node getNativeTree() {
IIOMetadataNode node; // scratch node
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
// Image descriptor
node = new IIOMetadataNode("imageDimensions");
node.setAttribute("numberImages", Integer.toString(numberImages));
root.appendChild(node);
return root;
}
public void setFromTree(String formatName, Node root) {
throw new IllegalStateException("Metadata is read-only!");
}
public void mergeTree(String formatName, Node root) {
throw new IllegalStateException("Metadata is read-only!");
}
public void reset() {
throw new IllegalStateException("Metadata is read-only!");
}
/**
* initialize the stream metadata element numberImages
*/
public void initialize(int numberImages) {
this.numberImages = numberImages;
}
}
|