帆软使用的第三方框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

718 lines
29 KiB

/*
* Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* -Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* -Redistribution in binary form must reproduct the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that Software is not designed,licensed or intended for use in
* the design, construction, operation or maintenance of any nuclear facility.
*/
package com.fr.third.JAI;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.ComponentColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* An abstract class allowing the creation of image decoders and
* encoders. Instances of <code>ImageCodec</code> may be registered.
* Once a codec has been registered, the format name associated with
* it may be used as the <code>name</code> parameter in the
* <code>createImageEncoder()</code> and <code>createImageDecoder()</code>
* methods.
*
* <p> Additionally, subclasses of <code>ImageCodec</code>
* are able to perform recognition of their particular format,
* wither by inspection of a fixed-length file header or by
* arbitrary access to the source data stream.
*
* <p> Format recognition is performed by two variants of the
* <code>isFormatRecognized()</code> method. Which variant should be
* called is determined by the output of the codec's
* <codec>getNumHeaderBytes()</code> method, which returns 0 if
* arbitrary access to the stream is required, and otherwise returns
* the number of header bytes required to recognize the format.
* Each subclass of <code>ImageCodec</code> needs to implement only
* one of the two variants.
*
* <p><b> This class is not a committed part of the JAI API. It may
* be removed or changed in future releases of JAI.</b>
*/
public abstract class ImageCodec {
private static Hashtable codecs = new Hashtable();
/** Allow only subclasses to instantiate this class. */
protected ImageCodec() {}
/**
* Load the JPEG and PNM codecs.
*/
static {
registerCodec(new BMPCodec());
registerCodec(new GIFCodec());
registerCodec(new FPXCodec());
registerCodec(new JPEGCodec());
registerCodec(new PNGCodec());
registerCodec(new PNMCodec());
registerCodec(new TIFFCodec());
}
/**
* Returns the <code>ImageCodec</code> associated with the given
* name. <code>null</code> is returned if no codec is registered
* with the given name. Case is not significant.
*
* @param name The name associated with the codec.
* @return The associated <code>ImageCodec</code>, or <code>null</code>.
*/
public static ImageCodec getCodec(String name) {
return (ImageCodec)codecs.get(name.toLowerCase());
}
/**
* Associates an <code>ImageCodec</code> with its format name, as
* determined by its <code>getFormatName()</code> method. Case is
* not significant. Any codec previously associated with the name
* is discarded.
*
* @param codec The <code>ImageCodec</code> object to be registered.
*/
public static void registerCodec(ImageCodec codec) {
codecs.put(codec.getFormatName().toLowerCase(), codec);
}
/**
* Unregisters the <code>ImageCodec</code> object currently
* responsible for handling the named format. Case is not
* significant.
*
* @param name The name associated with the codec to be removed.
*/
public static void unregisterCodec(String name) {
codecs.remove(name.toLowerCase());
}
/**
* Returns an <code>Enumeration</code> of all regstered
* <code>ImageCodec</code> objects.
*/
public static Enumeration getCodecs() {
return codecs.elements();
}
/**
* Returns an <code>ImageEncoder</code> object suitable for
* encoding to the supplied <code>OutputStream</code>, using the
* supplied <code>ImageEncoderParam</code> object.
*
* @param name The name associated with the codec.
* @param dst An <code>OutputStream</code> to write to.
* @param param An instance of <code>ImageEncoderParam</code> suitable
* for use with the named codec, or <code>null</code>.
* @return An instance of <code>ImageEncoder</code>, or <code>null</code>.
*/
public static ImageEncoder createImageEncoder(String name,
OutputStream dst,
ImageEncodeParam param) {
ImageCodec codec = getCodec(name);
if (codec == null) {
return null;
}
return codec.createImageEncoder(dst, param);
}
/**
* Returns an <code>ImageDecoder</code> object suitable for
* decoding from the supplied <code>InputStream</code>, using the
* supplied <code>ImageDecodeParam</code> object.
*
* @param name The name associated with the codec.
* @param src An <code>InputStream</code> to read from.
* @param param An instance of <code>ImageDecodeParam</code> suitable
* for use with the named codec, or <code>null</code>.
* @return An instance of <code>ImageDecoder</code>, or <code>null</code>.
*/
public static ImageDecoder createImageDecoder(String name,
InputStream src,
ImageDecodeParam param) {
ImageCodec codec = getCodec(name);
if (codec == null) {
return null;
}
return codec.createImageDecoder(src, param);
}
/**
* Returns an <code>ImageDecoder</code> object suitable for
* decoding from the supplied <code>File</code>, using the
* supplied <code>ImageDecodeParam</code> object.
*
* @param name The name associated with the codec.
* @param src A <code>File</code> to read from.
* @param param An instance of <code>ImageDecodeParam</code> suitable
* for use with the named codec, or <code>null</code>.
* @return An instance of <code>ImageDecoder</code>, or <code>null</code>.
*/
public static ImageDecoder createImageDecoder(String name,
File src,
ImageDecodeParam param)
throws IOException {
ImageCodec codec = getCodec(name);
if (codec == null) {
return null;
}
return codec.createImageDecoder(src, param);
}
/**
* Returns an <code>ImageDecoder</code> object suitable for
* decoding from the supplied <code>SeekableStream</code>, using the
* supplied <code>ImageDecodeParam</code> object.
*
* @param name The name associated with the codec.
* @param src A <code>SeekableStream</code> to read from.
* @param param An instance of <code>ImageDecodeParam</code> suitable
* for use with the named codec, or <code>null</code>.
* @return An instance of <code>ImageDecoder</code>, or <code>null</code>.
*/
public static ImageDecoder createImageDecoder(String name,
SeekableStream src,
ImageDecodeParam param) {
ImageCodec codec = getCodec(name);
if (codec == null) {
return null;
}
return codec.createImageDecoder(src, param);
}
private static String[] vectorToStrings(Vector nameVec) {
int count = nameVec.size();
String[] names = new String[count];
for (int i = 0; i < count; i++) {
names[i] = (String)nameVec.elementAt(i);
}
return names;
}
/**
* Returns an array of <code>String</code>s indicating the names
* of registered <code>ImageCodec</code>s that may be appropriate
* for reading the given <code>SeekableStream</code>.
*
* <p> If the <code>src</code> <code>SeekableStream</code> does
* not support seeking backwards (that is, its
* <code>canSeekBackwards()</code> method returns
* <code>false</code>) then only <code>FormatRecognizer</code>s
* that require only a fixed-length header will be checked.
*
* <p> If the <code>src</code> stream does not support seeking
* backwards, it must support marking, as determined by its
* <code>markSupported()</code> method.
*
* @param src A <code>SeekableStream</code> which optionally supports
* seeking backwards.
* @return An array of <code>String</code>s.
*
* @throws IllegalArgumentException if <code>src</code> supports
* neither seeking backwards nor marking.
*/
public static String[] getDecoderNames(SeekableStream src) {
if (!src.canSeekBackwards() && !src.markSupported()) {
throw new IllegalArgumentException(JaiI18N.getString("ImageCodec2"));
}
Enumeration enum1 = codecs.elements();
Vector nameVec = new Vector();
String opName = null;
while (enum1.hasMoreElements()) {
ImageCodec codec = (ImageCodec)enum1.nextElement();
int bytesNeeded = codec.getNumHeaderBytes();
if ((bytesNeeded == 0) && !src.canSeekBackwards()) {
continue;
}
try {
if (bytesNeeded > 0) {
src.mark(bytesNeeded);
byte[] header = new byte[bytesNeeded];
src.readFully(header);
src.reset();
if (codec.isFormatRecognized(header)) {
nameVec.add(codec.getFormatName());
}
} else {
long pointer = src.getFilePointer();
src.seek(0L);
if (codec.isFormatRecognized(src)) {
nameVec.add(codec.getFormatName());
}
src.seek(pointer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
return vectorToStrings(nameVec);
}
/**
* Returns an array of <code>String</code>s indicating the names
* of registered <code>ImageCodec</code>s that may be appropriate
* for writing the given <code>RenderedImage</code>, using the
* optional <code>ImageEncodeParam</code>, which may be
* <code>null</code>.
*
* @param im A <code>RenderedImage</code> to be encodec.
* @param param An <code>ImageEncodeParam</code>, or null.
* @return An array of <code>String</code>s.
*/
public static String[] getEncoderNames(RenderedImage im,
ImageEncodeParam param) {
Enumeration enum1= codecs.elements();
Vector nameVec = new Vector();
String opName = null;
while (enum1.hasMoreElements()) {
ImageCodec codec = (ImageCodec)enum1.nextElement();
if (codec.canEncodeImage(im, param)) {
nameVec.add(codec.getFormatName());
}
}
return vectorToStrings(nameVec);
}
/**
* Returns the name of this image format.
*
* @return A <code>String</code> containing the name of the
* image format supported by this codec.
*/
public abstract String getFormatName();
/**
* Returns the number of bytes of header needed to recognize the
* format, or 0 if an arbitrary number of bytes may be needed.
* The default implementation returns 0.
*
* <p> The return value must be a constant for all instances of
* each particular subclass of <code>ImageCodec</code>.
*
* <p> Although it is legal to always return 0, in some cases
* processing may be more efficient if the number of bytes needed
* is known in advance.
*/
public int getNumHeaderBytes() {
return 0;
}
/**
* Returns <code>true</code> if the format is recognized in the
* initial portion of a stream. The header will be passed in as a
* <code>byte</code> array of length <code>getNumHeaderBytes()</code>.
* This method should be called only if <code>getNumHeaderBytes()</code>
* returns a value greater than 0.
*
* <p> The default implementation throws an exception to indicate
* that it should never be called.
*
* @param header An array of <code>byte</code>s containing the input
* stream header.
* @return <code>true</code> if the format is recognized.
*/
public boolean isFormatRecognized(byte[] header) {
throw new RuntimeException(JaiI18N.getString("ImageCodec0"));
}
/**
* Returns <code>true</code> if the format is recognized in the
* input data stream. This method should be called only if
* <code>getNumHeaderBytesNeeded()</code> returns 0.
*
* <p> The source <code>SeekableStream</code> is guaranteed to
* support seeking backwards, and should be seeked to 0 prior
* to calling this method.
*
* <p> The default implementation throws an exception to indicate
* that it should never be called.
*
* @param src A <code>SeekableStream</code> containing the input
* data.
* @return <code>true</code> if the format is recognized.
*/
public boolean isFormatRecognized(SeekableStream src) throws IOException {
throw new RuntimeException(JaiI18N.getString("ImageCodec1"));
}
/**
* Returns a <code>Class</code> object indicating the proper
* subclass of <code>ImageEncodeParam</code> to be used with this
* <code>ImageCodec</code>. If encoding is not supported by this
* codec, <code>null</code> is returned. If encoding is
* supported, but a parameter object is not used during encoding,
* Object.class is returned to signal this fact.
*/
protected abstract Class getEncodeParamClass();
/**
* Returns a <code>Class</code> object indicating the proper
* subclass of <code>ImageDecodeParam</code> to be used with this
* <code>ImageCodec</code>. If encoding is not supported by this
* codec, <code>null</code> is returned. If decoding is
* supported, but a parameter object is not used during decoding,
* Object.class is returned to signal this fact.
*/
protected abstract Class getDecodeParamClass();
/**
* In a concrete subclass of <code>ImageCodec</code>, returns an
* implementation of the <code>ImageEncoder</code> interface
* appropriate for that codec.
*
* @param dst An <code>OutputStream</code> to write to.
* @param param An instance of <code>ImageEncoderParam</code>
* suitable for use with the <code>ImageCodec</code>
* subclass, or <code>null</code>.
* @return An instance of <code>ImageEncoder</code>.
*/
protected abstract ImageEncoder createImageEncoder(OutputStream dst,
ImageEncodeParam param);
/**
* Returns <code>true</code> if the given image and encoder param
* object are suitable for encoding by this <code>ImageCodec</code>.
* For example, some codecs may only deal with images with a certain
* number of bands; an attempt to encode an image with an unsupported
* number of bands will fail.
*
* @param im a RenderedImage whose ability to be encoded is to be
* determined.
* @param param a suitable <code>ImageEncodeParam</code> object,
* or <code>null</code>.
*/
public abstract boolean canEncodeImage(RenderedImage im,
ImageEncodeParam param);
/**
* Returns an implementation of the <code>ImageDecoder</code>
* interface appropriate for that codec. Subclasses of
* <code>ImageCodec</code> may override this method if they wish
* to accept data directly from an <code>InputStream</code>;
* otherwise, this method will convert the source into a
* backwards-seekable <code>SeekableStream</code> and call the
* appropriate version of <code>createImageDecoder</code> for that
* data type.
*
* <p> Instances of <code>ImageCodec</code> that do not require
* the ability to seek backwards in their source
* <code>SeekableStream</code> should override this method in
* order to avoid the default call to
* <code>SeekableStream.wrapInputStream(src, true)</code>.
*
* @param dst An <code>InputStream</code> to read from.
* @param param An instance of <code>ImageDecodeParam</code>
* suitable for use with the <code>ImageCodec</code>
* subclass, or <code>null</code>.
* @return An instance of <code>ImageDecoder</code>.
*/
protected ImageDecoder createImageDecoder(InputStream src,
ImageDecodeParam param) {
SeekableStream stream = SeekableStream.wrapInputStream(src, true);
return createImageDecoder(stream, param);
}
/**
* Returns an implementation of the <code>ImageDecoder</code>
* interface appropriate for that codec. Subclasses of
* <code>ImageCodec</code> may override this method if they wish
* to accept data directly from a <code>File</code>;
* otherwise, this method will convert the source into a
* <code>SeekableStream</code> and call the appropriate
* version of <code>createImageDecoder</code> for that data type.
*
* @param dst A <code>File</code> to read from.
* @param param An instance of <code>ImageDecodeParam</code>
* suitable for use with the <code>ImageCodec</code>
* subclass, or <code>null</code>.
* @return An instance of <code>ImageDecoder</code>.
*/
protected ImageDecoder createImageDecoder(File src,
ImageDecodeParam param)
throws IOException {
return createImageDecoder(new FileSeekableStream(src), param);
}
/**
* In a concrete subclass of <code>ImageCodec</code>, returns an
* implementation of the <code>ImageDecoder</code> interface
* appropriate for that codec.
*
* @param dst A <code>SeekableStream</code> to read from.
* @param param An instance of <code>ImageDecodeParam</code>
* suitable for use with the <code>ImageCodec</code>
* subclass, or <code>null</code>.
* @return An instance of <code>ImageDecoder</code>.
*/
protected abstract ImageDecoder createImageDecoder(SeekableStream src,
ImageDecodeParam param);
// ColorModel utility functions
private static final byte[][] grayIndexCmaps = {
null,
// 1 bit
{ (byte)0x00, (byte)0xff },
// 2 bits
{ (byte)0x00, (byte)0x55, (byte)0xaa, (byte)0xff },
null,
// 4 bits
{ (byte)0x00, (byte)0x11, (byte)0x22, (byte)0x33,
(byte)0x44, (byte)0x55, (byte)0x66, (byte)0x77,
(byte)0x88, (byte)0x99, (byte)0xaa, (byte)0xbb,
(byte)0xcc, (byte)0xdd, (byte)0xee, (byte)0xff }
};
/**
* A convenience methods to create an instance of
* <code>IndexColorModel</code> suitable for the given 1-banded
* <code>SampleModel</code>.
*
* @param sm a 1-banded <code>SampleModel</code>.
* @param blackIsZero <code>true</code> if the gray ramp should
* go from black to white, <code>false</code>otherwise.
*/
public static ColorModel createGrayIndexColorModel(SampleModel sm,
boolean blackIsZero) {
if (sm.getNumBands() != 1) {
throw new IllegalArgumentException();
}
int sampleSize = sm.getSampleSize(0);
byte[] cmap = null;
if (sampleSize < 8) {
cmap = grayIndexCmaps[sampleSize];
if (!blackIsZero) {
int length = cmap.length;
byte[] newCmap = new byte[length];
for (int i = 0; i < length; i++) {
newCmap[i] = cmap[length - i - 1];
}
cmap = newCmap;
}
} else {
cmap = new byte[256];
if (blackIsZero) {
for (int i = 0; i < 256; i++) {
cmap[i] = (byte)i;
}
} else {
for (int i = 0; i < 256; i++) {
cmap[i] = (byte)(255 - i);
}
}
}
return new IndexColorModel(sampleSize, cmap.length,
cmap, cmap, cmap);
}
private static final int[] GrayBits8 = { 8 };
private static final ComponentColorModel colorModelGray8 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
GrayBits8, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
private static final int[] GrayAlphaBits8 = { 8, 8 };
private static final ComponentColorModel colorModelGrayAlpha8 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
GrayAlphaBits8, true, false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
private static final int[] GrayBits16 = { 16 };
private static final ComponentColorModel colorModelGray16 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
GrayBits16, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_USHORT);
private static final int[] GrayAlphaBits16 = { 16, 16 };
private static final ComponentColorModel colorModelGrayAlpha16 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
GrayAlphaBits16, true, false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_USHORT);
private static final int[] GrayBits32 = { 32 };
private static final ComponentColorModel colorModelGray32 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
GrayBits32, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_INT);
private static final int[] GrayAlphaBits32 = { 32, 32 };
private static final ComponentColorModel colorModelGrayAlpha32 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
GrayAlphaBits32, true, false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_INT);
private static final int[] RGBBits8 = { 8, 8, 8 };
private static final ComponentColorModel colorModelRGB8 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
RGBBits8, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
private static final int[] RGBABits8 = { 8, 8, 8, 8 };
private static final ComponentColorModel colorModelRGBA8 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
RGBABits8, true, false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
private static final int[] RGBBits16 = { 16, 16, 16 };
private static final ComponentColorModel colorModelRGB16 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
RGBBits16, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_USHORT);
private static final int[] RGBABits16 = { 16, 16, 16, 16 };
private static final ComponentColorModel colorModelRGBA16 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
RGBABits16, true, false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_USHORT);
private static final int[] RGBBits32 = { 32, 32, 32 };
private static final ComponentColorModel colorModelRGB32 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
RGBBits32, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_INT);
private static final int[] RGBABits32 = { 32, 32, 32, 32 };
private static final ComponentColorModel colorModelRGBA32 =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
RGBABits32, true, false,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_INT);
/**
* A convenience method to create an instance of
* <code>ComponentColorModel</code> suitable for use with the
* given <code>SampleModel</code>. The <code>SampleModel</code>
* should have a data type of <code>DataBuffer.TYPE_BYTE</code>,
* <code>TYPE_USHORT</code>, or <code>TYPE_INT</code> and between
* 1 and 4 bands. Depending on the number of bands of the
* <code>SampleModel</code>, either a gray, gray+alpha, rgb, or
* rgb+alpha <code>ColorModel</code> is returned.
*/
public static ColorModel createComponentColorModel(SampleModel sm) {
int type = sm.getDataType();
int bands = sm.getNumBands();
ComponentColorModel cm = null;
if (type == DataBuffer.TYPE_BYTE) {
switch (bands) {
case 1:
cm = colorModelGray8;
break;
case 2:
cm = colorModelGrayAlpha8;
break;
case 3:
cm = colorModelRGB8;
break;
case 4:
cm = colorModelRGBA8;
break;
}
} else if (type == DataBuffer.TYPE_USHORT) {
switch (bands) {
case 1:
cm = colorModelGray16;
break;
case 2:
cm = colorModelGrayAlpha16;
break;
case 3:
cm = colorModelRGB16;
break;
case 4:
cm = colorModelRGBA16;
break;
}
} else if (type == DataBuffer.TYPE_INT) {
switch (bands) {
case 1:
cm = colorModelGray32;
break;
case 2:
cm = colorModelGrayAlpha32;
break;
case 3:
cm = colorModelRGB32;
break;
case 4:
cm = colorModelRGBA32;
break;
}
} else if (type == DataBuffer.TYPE_FLOAT &&
bands >= 1 && bands <= 4) {
ColorSpace cs = bands <= 2 ?
ColorSpace.getInstance(ColorSpace.CS_GRAY) :
ColorSpace.getInstance(ColorSpace.CS_sRGB);
boolean hasAlpha = bands % 2 == 0;
cm = new FloatDoubleColorModel(cs, hasAlpha, false,
hasAlpha ?
Transparency.TRANSLUCENT :
Transparency.OPAQUE,
DataBuffer.TYPE_FLOAT);
}
return cm;
}
}