|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.imageio.plugins.jpeg; |
|
|
|
import javax.imageio.IIOException; |
|
import javax.imageio.ImageWriter; |
|
import javax.imageio.ImageWriteParam; |
|
import javax.imageio.IIOImage; |
|
import javax.imageio.ImageTypeSpecifier; |
|
import javax.imageio.metadata.IIOMetadata; |
|
import javax.imageio.metadata.IIOMetadataFormatImpl; |
|
import javax.imageio.metadata.IIOInvalidTreeException; |
|
import javax.imageio.spi.ImageWriterSpi; |
|
import javax.imageio.stream.ImageOutputStream; |
|
import javax.imageio.plugins.jpeg.JPEGImageWriteParam; |
|
import javax.imageio.plugins.jpeg.JPEGQTable; |
|
import javax.imageio.plugins.jpeg.JPEGHuffmanTable; |
|
|
|
import org.w3c.dom.Node; |
|
|
|
import java.awt.image.Raster; |
|
import java.awt.image.WritableRaster; |
|
import java.awt.image.DataBufferByte; |
|
import java.awt.image.ColorModel; |
|
import java.awt.image.IndexColorModel; |
|
import java.awt.image.ColorConvertOp; |
|
import java.awt.image.RenderedImage; |
|
import java.awt.image.BufferedImage; |
|
import java.awt.color.ColorSpace; |
|
import java.awt.color.ICC_ColorSpace; |
|
import java.awt.color.ICC_Profile; |
|
import java.awt.Dimension; |
|
import java.awt.Rectangle; |
|
import java.awt.Transparency; |
|
|
|
import java.io.IOException; |
|
|
|
import java.util.List; |
|
import java.util.ArrayList; |
|
import java.util.Iterator; |
|
|
|
import sun.java2d.Disposer; |
|
import sun.java2d.DisposerRecord; |
|
|
|
public class JPEGImageWriter extends ImageWriter { |
|
|
|
///////// Private variables |
|
|
|
private boolean debug = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private long structPointer = 0; |
|
|
|
|
|
|
|
private ImageOutputStream ios = null; |
|
|
|
|
|
private Raster srcRas = null; |
|
|
|
|
|
private WritableRaster raster = null; |
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean indexed = false; |
|
private IndexColorModel indexCM = null; |
|
|
|
private boolean convertTosRGB = false; |
|
private WritableRaster converted = null; |
|
|
|
private boolean isAlphaPremultiplied = false; |
|
private ColorModel srcCM = null; |
|
|
|
|
|
|
|
*/ |
|
private List thumbnails = null; |
|
|
|
|
|
|
|
*/ |
|
private ICC_Profile iccProfile = null; |
|
|
|
private int sourceXOffset = 0; |
|
private int sourceYOffset = 0; |
|
private int sourceWidth = 0; |
|
private int [] srcBands = null; |
|
private int sourceHeight = 0; |
|
|
|
|
|
private int currentImage = 0; |
|
|
|
private ColorConvertOp convertOp = null; |
|
|
|
private JPEGQTable [] streamQTables = null; |
|
private JPEGHuffmanTable[] streamDCHuffmanTables = null; |
|
private JPEGHuffmanTable[] streamACHuffmanTables = null; |
|
|
|
// Parameters for writing metadata |
|
private boolean ignoreJFIF = false; |
|
private boolean forceJFIF = false; |
|
private boolean ignoreAdobe = false; |
|
private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; |
|
private boolean writeDefaultJFIF = false; |
|
private boolean writeAdobe = false; |
|
private JPEGMetadata metadata = null; |
|
|
|
private boolean sequencePrepared = false; |
|
|
|
private int numScans = 0; |
|
|
|
|
|
private Object disposerReferent = new Object(); |
|
|
|
|
|
private DisposerRecord disposerRecord; |
|
|
|
///////// End of Private variables |
|
|
|
///////// Protected variables |
|
|
|
protected static final int WARNING_DEST_IGNORED = 0; |
|
protected static final int WARNING_STREAM_METADATA_IGNORED = 1; |
|
protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2; |
|
protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3; |
|
protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4; |
|
protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5; |
|
protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6; |
|
protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7; |
|
protected static final int WARNING_NO_BANDS_ON_INDEXED = 8; |
|
protected static final int WARNING_ILLEGAL_THUMBNAIL = 9; |
|
protected static final int WARNING_IGNORING_THUMBS = 10; |
|
protected static final int WARNING_FORCING_JFIF = 11; |
|
protected static final int WARNING_THUMB_CLIPPED = 12; |
|
protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13; |
|
protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14; |
|
protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15; |
|
|
|
private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED; |
|
|
|
///////// End of Protected variables |
|
|
|
///////// static initializer |
|
|
|
static { |
|
java.security.AccessController.doPrivileged( |
|
new java.security.PrivilegedAction<Void>() { |
|
public Void run() { |
|
System.loadLibrary("jpeg"); |
|
return null; |
|
} |
|
}); |
|
initWriterIDs(JPEGQTable.class, |
|
JPEGHuffmanTable.class); |
|
} |
|
|
|
//////// Public API |
|
|
|
public JPEGImageWriter(ImageWriterSpi originator) { |
|
super(originator); |
|
structPointer = initJPEGImageWriter(); |
|
disposerRecord = new JPEGWriterDisposerRecord(structPointer); |
|
Disposer.addRecord(disposerReferent, disposerRecord); |
|
} |
|
|
|
public void setOutput(Object output) { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
super.setOutput(output); |
|
resetInternalState(); |
|
ios = (ImageOutputStream) output; |
|
|
|
setDest(structPointer); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
public ImageWriteParam getDefaultWriteParam() { |
|
return new JPEGImageWriteParam(null); |
|
} |
|
|
|
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { |
|
setThreadLock(); |
|
try { |
|
return new JPEGMetadata(param, this); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
public IIOMetadata |
|
getDefaultImageMetadata(ImageTypeSpecifier imageType, |
|
ImageWriteParam param) { |
|
setThreadLock(); |
|
try { |
|
return new JPEGMetadata(imageType, param, this); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
public IIOMetadata convertStreamMetadata(IIOMetadata inData, |
|
ImageWriteParam param) { |
|
// There isn't much we can do. If it's one of ours, then |
|
// return it. Otherwise just return null. We use it only |
|
// for tables, so we can't get a default and modify it, |
|
|
|
if (inData instanceof JPEGMetadata) { |
|
JPEGMetadata jpegData = (JPEGMetadata) inData; |
|
if (jpegData.isStream) { |
|
return inData; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
public IIOMetadata |
|
convertImageMetadata(IIOMetadata inData, |
|
ImageTypeSpecifier imageType, |
|
ImageWriteParam param) { |
|
setThreadLock(); |
|
try { |
|
return convertImageMetadataOnThread(inData, imageType, param); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
private IIOMetadata |
|
convertImageMetadataOnThread(IIOMetadata inData, |
|
ImageTypeSpecifier imageType, |
|
ImageWriteParam param) { |
|
|
|
if (inData instanceof JPEGMetadata) { |
|
JPEGMetadata jpegData = (JPEGMetadata) inData; |
|
if (!jpegData.isStream) { |
|
return inData; |
|
} else { |
|
// Can't convert stream metadata to image metadata |
|
|
|
return null; |
|
} |
|
} |
|
// If it's not one of ours, create a default and set it from |
|
|
|
if (inData.isStandardMetadataFormatSupported()) { |
|
String formatName = |
|
IIOMetadataFormatImpl.standardMetadataFormatName; |
|
Node tree = inData.getAsTree(formatName); |
|
if (tree != null) { |
|
JPEGMetadata jpegData = new JPEGMetadata(imageType, |
|
param, |
|
this); |
|
try { |
|
jpegData.setFromTree(formatName, tree); |
|
} catch (IIOInvalidTreeException e) { |
|
// Other plug-in generates bogus standard tree |
|
|
|
return null; |
|
} |
|
|
|
return jpegData; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
public int getNumThumbnailsSupported(ImageTypeSpecifier imageType, |
|
ImageWriteParam param, |
|
IIOMetadata streamMetadata, |
|
IIOMetadata imageMetadata) { |
|
if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { |
|
return Integer.MAX_VALUE; |
|
} |
|
return 0; |
|
} |
|
|
|
static final Dimension [] preferredThumbSizes = {new Dimension(1, 1), |
|
new Dimension(255, 255)}; |
|
|
|
public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType, |
|
ImageWriteParam param, |
|
IIOMetadata streamMetadata, |
|
IIOMetadata imageMetadata) { |
|
if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { |
|
return (Dimension [])preferredThumbSizes.clone(); |
|
} |
|
return null; |
|
} |
|
|
|
private boolean jfifOK(ImageTypeSpecifier imageType, |
|
ImageWriteParam param, |
|
IIOMetadata streamMetadata, |
|
IIOMetadata imageMetadata) { |
|
|
|
if ((imageType != null) && |
|
(!JPEG.isJFIFcompliant(imageType, true))) { |
|
return false; |
|
} |
|
if (imageMetadata != null) { |
|
JPEGMetadata metadata = null; |
|
if (imageMetadata instanceof JPEGMetadata) { |
|
metadata = (JPEGMetadata) imageMetadata; |
|
} else { |
|
metadata = (JPEGMetadata)convertImageMetadata(imageMetadata, |
|
imageType, |
|
param); |
|
} |
|
|
|
if (metadata.findMarkerSegment |
|
(JFIFMarkerSegment.class, true) == null){ |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
public boolean canWriteRasters() { |
|
return true; |
|
} |
|
|
|
public void write(IIOMetadata streamMetadata, |
|
IIOImage image, |
|
ImageWriteParam param) throws IOException { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
writeOnThread(streamMetadata, image, param); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
private void writeOnThread(IIOMetadata streamMetadata, |
|
IIOImage image, |
|
ImageWriteParam param) throws IOException { |
|
|
|
if (ios == null) { |
|
throw new IllegalStateException("Output has not been set!"); |
|
} |
|
|
|
if (image == null) { |
|
throw new IllegalArgumentException("image is null!"); |
|
} |
|
|
|
|
|
if (streamMetadata != null) { |
|
warningOccurred(WARNING_STREAM_METADATA_IGNORED); |
|
} |
|
|
|
|
|
boolean rasterOnly = image.hasRaster(); |
|
|
|
RenderedImage rimage = null; |
|
if (rasterOnly) { |
|
srcRas = image.getRaster(); |
|
} else { |
|
rimage = image.getRenderedImage(); |
|
if (rimage instanceof BufferedImage) { |
|
|
|
srcRas = ((BufferedImage)rimage).getRaster(); |
|
} else if (rimage.getNumXTiles() == 1 && |
|
rimage.getNumYTiles() == 1) |
|
{ |
|
|
|
srcRas = rimage.getTile(rimage.getMinTileX(), |
|
rimage.getMinTileY()); |
|
|
|
// Ensure the Raster has dimensions of the image, |
|
|
|
if (srcRas.getWidth() != rimage.getWidth() || |
|
srcRas.getHeight() != rimage.getHeight()) |
|
{ |
|
srcRas = srcRas.createChild(srcRas.getMinX(), |
|
srcRas.getMinY(), |
|
rimage.getWidth(), |
|
rimage.getHeight(), |
|
srcRas.getMinX(), |
|
srcRas.getMinY(), |
|
null); |
|
} |
|
} else { |
|
|
|
srcRas = rimage.getData(); |
|
} |
|
} |
|
|
|
// Now determine if we are using a band subset |
|
|
|
|
|
int numSrcBands = srcRas.getNumBands(); |
|
indexed = false; |
|
indexCM = null; |
|
ColorModel cm = null; |
|
ColorSpace cs = null; |
|
isAlphaPremultiplied = false; |
|
srcCM = null; |
|
if (!rasterOnly) { |
|
cm = rimage.getColorModel(); |
|
if (cm != null) { |
|
cs = cm.getColorSpace(); |
|
if (cm instanceof IndexColorModel) { |
|
indexed = true; |
|
indexCM = (IndexColorModel) cm; |
|
numSrcBands = cm.getNumComponents(); |
|
} |
|
if (cm.isAlphaPremultiplied()) { |
|
isAlphaPremultiplied = true; |
|
srcCM = cm; |
|
} |
|
} |
|
} |
|
|
|
srcBands = JPEG.bandOffsets[numSrcBands-1]; |
|
int numBandsUsed = numSrcBands; |
|
// Consult the param to determine if we're writing a subset |
|
|
|
if (param != null) { |
|
int[] sBands = param.getSourceBands(); |
|
if (sBands != null) { |
|
if (indexed) { |
|
warningOccurred(WARNING_NO_BANDS_ON_INDEXED); |
|
} else { |
|
srcBands = sBands; |
|
numBandsUsed = srcBands.length; |
|
if (numBandsUsed > numSrcBands) { |
|
throw new IIOException |
|
("ImageWriteParam specifies too many source bands"); |
|
} |
|
} |
|
} |
|
} |
|
|
|
boolean usingBandSubset = (numBandsUsed != numSrcBands); |
|
boolean fullImage = ((!rasterOnly) && (!usingBandSubset)); |
|
|
|
int [] bandSizes = null; |
|
if (!indexed) { |
|
bandSizes = srcRas.getSampleModel().getSampleSize(); |
|
|
|
if (usingBandSubset) { |
|
int [] temp = new int [numBandsUsed]; |
|
for (int i = 0; i < numBandsUsed; i++) { |
|
temp[i] = bandSizes[srcBands[i]]; |
|
} |
|
bandSizes = temp; |
|
} |
|
} else { |
|
int [] tempSize = srcRas.getSampleModel().getSampleSize(); |
|
bandSizes = new int [numSrcBands]; |
|
for (int i = 0; i < numSrcBands; i++) { |
|
bandSizes[i] = tempSize[0]; |
|
} |
|
} |
|
|
|
for (int i = 0; i < bandSizes.length; i++) { |
|
// 4450894 part 1: The IJG libraries are compiled so they only |
|
// handle <= 8-bit samples. We now check the band sizes and throw |
|
// an exception for images, such as USHORT_GRAY, with > 8 bits |
|
|
|
if (bandSizes[i] <= 0 || bandSizes[i] > 8) { |
|
throw new IIOException("Illegal band size: should be 0 < size <= 8"); |
|
} |
|
// 4450894 part 2: We expand IndexColorModel images to full 24- |
|
// or 32-bit in grabPixels() for each scanline. For indexed |
|
// images such as BYTE_BINARY, we need to ensure that we update |
|
// bandSizes to account for the scaling from 1-bit band sizes |
|
|
|
if (indexed) { |
|
bandSizes[i] = 8; |
|
} |
|
} |
|
|
|
if (debug) { |
|
System.out.println("numSrcBands is " + numSrcBands); |
|
System.out.println("numBandsUsed is " + numBandsUsed); |
|
System.out.println("usingBandSubset is " + usingBandSubset); |
|
System.out.println("fullImage is " + fullImage); |
|
System.out.print("Band sizes:"); |
|
for (int i = 0; i< bandSizes.length; i++) { |
|
System.out.print(" " + bandSizes[i]); |
|
} |
|
System.out.println(); |
|
} |
|
|
|
|
|
ImageTypeSpecifier destType = null; |
|
if (param != null) { |
|
destType = param.getDestinationType(); |
|
|
|
if ((fullImage) && (destType != null)) { |
|
warningOccurred(WARNING_DEST_IGNORED); |
|
destType = null; |
|
} |
|
} |
|
|
|
// Examine the param |
|
|
|
sourceXOffset = srcRas.getMinX(); |
|
sourceYOffset = srcRas.getMinY(); |
|
int imageWidth = srcRas.getWidth(); |
|
int imageHeight = srcRas.getHeight(); |
|
sourceWidth = imageWidth; |
|
sourceHeight = imageHeight; |
|
int periodX = 1; |
|
int periodY = 1; |
|
int gridX = 0; |
|
int gridY = 0; |
|
JPEGQTable [] qTables = null; |
|
JPEGHuffmanTable[] DCHuffmanTables = null; |
|
JPEGHuffmanTable[] ACHuffmanTables = null; |
|
boolean optimizeHuffman = false; |
|
JPEGImageWriteParam jparam = null; |
|
int progressiveMode = ImageWriteParam.MODE_DISABLED; |
|
|
|
if (param != null) { |
|
|
|
Rectangle sourceRegion = param.getSourceRegion(); |
|
if (sourceRegion != null) { |
|
Rectangle imageBounds = new Rectangle(sourceXOffset, |
|
sourceYOffset, |
|
sourceWidth, |
|
sourceHeight); |
|
sourceRegion = sourceRegion.intersection(imageBounds); |
|
sourceXOffset = sourceRegion.x; |
|
sourceYOffset = sourceRegion.y; |
|
sourceWidth = sourceRegion.width; |
|
sourceHeight = sourceRegion.height; |
|
} |
|
|
|
if (sourceWidth + sourceXOffset > imageWidth) { |
|
sourceWidth = imageWidth - sourceXOffset; |
|
} |
|
if (sourceHeight + sourceYOffset > imageHeight) { |
|
sourceHeight = imageHeight - sourceYOffset; |
|
} |
|
|
|
periodX = param.getSourceXSubsampling(); |
|
periodY = param.getSourceYSubsampling(); |
|
gridX = param.getSubsamplingXOffset(); |
|
gridY = param.getSubsamplingYOffset(); |
|
|
|
switch(param.getCompressionMode()) { |
|
case ImageWriteParam.MODE_DISABLED: |
|
throw new IIOException("JPEG compression cannot be disabled"); |
|
case ImageWriteParam.MODE_EXPLICIT: |
|
float quality = param.getCompressionQuality(); |
|
quality = JPEG.convertToLinearQuality(quality); |
|
qTables = new JPEGQTable[2]; |
|
qTables[0] = JPEGQTable.K1Luminance.getScaledInstance |
|
(quality, true); |
|
qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance |
|
(quality, true); |
|
break; |
|
case ImageWriteParam.MODE_DEFAULT: |
|
qTables = new JPEGQTable[2]; |
|
qTables[0] = JPEGQTable.K1Div2Luminance; |
|
qTables[1] = JPEGQTable.K2Div2Chrominance; |
|
break; |
|
// We'll handle the metadata case later |
|
} |
|
|
|
progressiveMode = param.getProgressiveMode(); |
|
|
|
if (param instanceof JPEGImageWriteParam) { |
|
jparam = (JPEGImageWriteParam)param; |
|
optimizeHuffman = jparam.getOptimizeHuffmanTables(); |
|
} |
|
} |
|
|
|
|
|
IIOMetadata mdata = image.getMetadata(); |
|
if (mdata != null) { |
|
if (mdata instanceof JPEGMetadata) { |
|
metadata = (JPEGMetadata) mdata; |
|
if (debug) { |
|
System.out.println |
|
("We have metadata, and it's JPEG metadata"); |
|
} |
|
} else { |
|
if (!rasterOnly) { |
|
ImageTypeSpecifier type = destType; |
|
if (type == null) { |
|
type = new ImageTypeSpecifier(rimage); |
|
} |
|
metadata = (JPEGMetadata) convertImageMetadata(mdata, |
|
type, |
|
param); |
|
} else { |
|
warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER); |
|
} |
|
} |
|
} |
|
|
|
// First set a default state |
|
|
|
ignoreJFIF = false; |
|
ignoreAdobe = false; |
|
newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; |
|
writeDefaultJFIF = false; |
|
writeAdobe = false; |
|
|
|
|
|
int inCsType = JPEG.JCS_UNKNOWN; |
|
int outCsType = JPEG.JCS_UNKNOWN; |
|
|
|
JFIFMarkerSegment jfif = null; |
|
AdobeMarkerSegment adobe = null; |
|
SOFMarkerSegment sof = null; |
|
|
|
if (metadata != null) { |
|
jfif = (JFIFMarkerSegment) metadata.findMarkerSegment |
|
(JFIFMarkerSegment.class, true); |
|
adobe = (AdobeMarkerSegment) metadata.findMarkerSegment |
|
(AdobeMarkerSegment.class, true); |
|
sof = (SOFMarkerSegment) metadata.findMarkerSegment |
|
(SOFMarkerSegment.class, true); |
|
} |
|
|
|
iccProfile = null; |
|
convertTosRGB = false; |
|
converted = null; |
|
|
|
if (destType != null) { |
|
if (numBandsUsed != destType.getNumBands()) { |
|
throw new IIOException |
|
("Number of source bands != number of destination bands"); |
|
} |
|
cs = destType.getColorModel().getColorSpace(); |
|
|
|
if (metadata != null) { |
|
checkSOFBands(sof, numBandsUsed); |
|
|
|
checkJFIF(jfif, destType, false); |
|
|
|
if ((jfif != null) && (ignoreJFIF == false)) { |
|
if (JPEG.isNonStandardICC(cs)) { |
|
iccProfile = ((ICC_ColorSpace) cs).getProfile(); |
|
} |
|
} |
|
checkAdobe(adobe, destType, false); |
|
|
|
} else { // no metadata, but there is a dest type |
|
|
|
if (JPEG.isJFIFcompliant(destType, false)) { |
|
writeDefaultJFIF = true; |
|
|
|
if (JPEG.isNonStandardICC(cs)) { |
|
iccProfile = ((ICC_ColorSpace) cs).getProfile(); |
|
} |
|
} else { |
|
int transform = JPEG.transformForType(destType, false); |
|
if (transform != JPEG.ADOBE_IMPOSSIBLE) { |
|
writeAdobe = true; |
|
newAdobeTransform = transform; |
|
} |
|
} |
|
|
|
metadata = new JPEGMetadata(destType, null, this); |
|
} |
|
inCsType = getSrcCSType(destType); |
|
outCsType = getDefaultDestCSType(destType); |
|
} else { |
|
if (metadata == null) { |
|
if (fullImage) { // no dest, no metadata, full image |
|
|
|
metadata = new JPEGMetadata(new ImageTypeSpecifier(rimage), |
|
param, this); |
|
if (metadata.findMarkerSegment |
|
(JFIFMarkerSegment.class, true) != null) { |
|
cs = rimage.getColorModel().getColorSpace(); |
|
if (JPEG.isNonStandardICC(cs)) { |
|
iccProfile = ((ICC_ColorSpace) cs).getProfile(); |
|
} |
|
} |
|
|
|
inCsType = getSrcCSType(rimage); |
|
outCsType = getDefaultDestCSType(rimage); |
|
} |
|
// else no dest, no metadata, not an image, |
|
// so no special headers, no color conversion |
|
} else { |
|
checkSOFBands(sof, numBandsUsed); |
|
if (fullImage) { // no dest, metadata, image |
|
// Check that the metadata and the image match |
|
|
|
ImageTypeSpecifier inputType = |
|
new ImageTypeSpecifier(rimage); |
|
|
|
inCsType = getSrcCSType(rimage); |
|
|
|
if (cm != null) { |
|
boolean alpha = cm.hasAlpha(); |
|
switch (cs.getType()) { |
|
case ColorSpace.TYPE_GRAY: |
|
if (!alpha) { |
|
outCsType = JPEG.JCS_GRAYSCALE; |
|
} else { |
|
if (jfif != null) { |
|
ignoreJFIF = true; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_JFIF_MISMATCH); |
|
} |
|
// out colorspace remains unknown |
|
} |
|
if ((adobe != null) |
|
&& (adobe.transform != JPEG.ADOBE_UNKNOWN)) { |
|
newAdobeTransform = JPEG.ADOBE_UNKNOWN; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_ADOBE_MISMATCH); |
|
} |
|
break; |
|
case ColorSpace.TYPE_RGB: |
|
if (!alpha) { |
|
if (jfif != null) { |
|
outCsType = JPEG.JCS_YCbCr; |
|
if (JPEG.isNonStandardICC(cs) |
|
|| ((cs instanceof ICC_ColorSpace) |
|
&& (jfif.iccSegment != null))) { |
|
iccProfile = |
|
((ICC_ColorSpace) cs).getProfile(); |
|
} |
|
} else if (adobe != null) { |
|
switch (adobe.transform) { |
|
case JPEG.ADOBE_UNKNOWN: |
|
outCsType = JPEG.JCS_RGB; |
|
break; |
|
case JPEG.ADOBE_YCC: |
|
outCsType = JPEG.JCS_YCbCr; |
|
break; |
|
default: |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_ADOBE_MISMATCH); |
|
newAdobeTransform = JPEG.ADOBE_UNKNOWN; |
|
outCsType = JPEG.JCS_RGB; |
|
break; |
|
} |
|
} else { |
|
|
|
int outCS = sof.getIDencodedCSType(); |
|
// if they don't resolve it, |
|
|
|
if (outCS != JPEG.JCS_UNKNOWN) { |
|
outCsType = outCS; |
|
} else { |
|
boolean subsampled = |
|
isSubsampled(sof.componentSpecs); |
|
if (subsampled) { |
|
outCsType = JPEG.JCS_YCbCr; |
|
} else { |
|
outCsType = JPEG.JCS_RGB; |
|
} |
|
} |
|
} |
|
} else { |
|
if (jfif != null) { |
|
ignoreJFIF = true; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_JFIF_MISMATCH); |
|
} |
|
if (adobe != null) { |
|
if (adobe.transform |
|
!= JPEG.ADOBE_UNKNOWN) { |
|
newAdobeTransform = JPEG.ADOBE_UNKNOWN; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_ADOBE_MISMATCH); |
|
} |
|
outCsType = JPEG.JCS_RGBA; |
|
} else { |
|
|
|
int outCS = sof.getIDencodedCSType(); |
|
// if they don't resolve it, |
|
|
|
if (outCS != JPEG.JCS_UNKNOWN) { |
|
outCsType = outCS; |
|
} else { |
|
boolean subsampled = |
|
isSubsampled(sof.componentSpecs); |
|
outCsType = subsampled ? |
|
JPEG.JCS_YCbCrA : JPEG.JCS_RGBA; |
|
} |
|
} |
|
} |
|
break; |
|
case ColorSpace.TYPE_3CLR: |
|
if (cs == JPEG.JCS.getYCC()) { |
|
if (!alpha) { |
|
if (jfif != null) { |
|
convertTosRGB = true; |
|
convertOp = |
|
new ColorConvertOp(cs, |
|
JPEG.JCS.sRGB, |
|
null); |
|
outCsType = JPEG.JCS_YCbCr; |
|
} else if (adobe != null) { |
|
if (adobe.transform |
|
!= JPEG.ADOBE_YCC) { |
|
newAdobeTransform = JPEG.ADOBE_YCC; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_ADOBE_MISMATCH); |
|
} |
|
outCsType = JPEG.JCS_YCC; |
|
} else { |
|
outCsType = JPEG.JCS_YCC; |
|
} |
|
} else { |
|
if (jfif != null) { |
|
ignoreJFIF = true; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_JFIF_MISMATCH); |
|
} else if (adobe != null) { |
|
if (adobe.transform |
|
!= JPEG.ADOBE_UNKNOWN) { |
|
newAdobeTransform |
|
= JPEG.ADOBE_UNKNOWN; |
|
warningOccurred |
|
(WARNING_IMAGE_METADATA_ADOBE_MISMATCH); |
|
} |
|
} |
|
outCsType = JPEG.JCS_YCCA; |
|
} |
|
} |
|
} |
|
} |
|
} // else no dest, metadata, not an image. Defaults ok |
|
} |
|
} |
|
|
|
boolean metadataProgressive = false; |
|
int [] scans = null; |
|
|
|
if (metadata != null) { |
|
if (sof == null) { |
|
sof = (SOFMarkerSegment) metadata.findMarkerSegment |
|
(SOFMarkerSegment.class, true); |
|
} |
|
if ((sof != null) && (sof.tag == JPEG.SOF2)) { |
|
metadataProgressive = true; |
|
if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) { |
|
scans = collectScans(metadata, sof); |
|
} else { |
|
numScans = 0; |
|
} |
|
} |
|
if (jfif == null) { |
|
jfif = (JFIFMarkerSegment) metadata.findMarkerSegment |
|
(JFIFMarkerSegment.class, true); |
|
} |
|
} |
|
|
|
thumbnails = image.getThumbnails(); |
|
int numThumbs = image.getNumThumbnails(); |
|
forceJFIF = false; |
|
// determine if thumbnails can be written |
|
// If we are going to add a default JFIF marker segment, |
|
|
|
if (!writeDefaultJFIF) { |
|
|
|
if (metadata == null) { |
|
thumbnails = null; |
|
if (numThumbs != 0) { |
|
warningOccurred(WARNING_IGNORING_THUMBS); |
|
} |
|
} else { |
|
// There is metadata |
|
// If we are writing a raster or subbands, |
|
|
|
if (fullImage == false) { |
|
if (jfif == null) { |
|
thumbnails = null; |
|
if (numThumbs != 0) { |
|
warningOccurred(WARNING_IGNORING_THUMBS); |
|
} |
|
} |
|
} else { |
|
if (jfif == null) { // Not JFIF |
|
|
|
if ((outCsType == JPEG.JCS_GRAYSCALE) |
|
|| (outCsType == JPEG.JCS_YCbCr)) { |
|
if (numThumbs != 0) { |
|
forceJFIF = true; |
|
warningOccurred(WARNING_FORCING_JFIF); |
|
} |
|
} else { |
|
thumbnails = null; |
|
if (numThumbs != 0) { |
|
warningOccurred(WARNING_IGNORING_THUMBS); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Set up a boolean to indicate whether we need to call back to |
|
|
|
boolean haveMetadata = |
|
((metadata != null) || writeDefaultJFIF || writeAdobe); |
|
|
|
// Now that we have dealt with metadata, finalize our tables set up |
|
|
|
|
|
boolean writeDQT = true; |
|
boolean writeDHT = true; |
|
|
|
|
|
DQTMarkerSegment dqt = null; |
|
DHTMarkerSegment dht = null; |
|
|
|
int restartInterval = 0; |
|
|
|
if (metadata != null) { |
|
dqt = (DQTMarkerSegment) metadata.findMarkerSegment |
|
(DQTMarkerSegment.class, true); |
|
dht = (DHTMarkerSegment) metadata.findMarkerSegment |
|
(DHTMarkerSegment.class, true); |
|
DRIMarkerSegment dri = |
|
(DRIMarkerSegment) metadata.findMarkerSegment |
|
(DRIMarkerSegment.class, true); |
|
if (dri != null) { |
|
restartInterval = dri.restartInterval; |
|
} |
|
|
|
if (dqt == null) { |
|
writeDQT = false; |
|
} |
|
if (dht == null) { |
|
writeDHT = false; |
|
} |
|
} |
|
|
|
// Whether we write tables or not, we need to figure out which ones |
|
|
|
if (qTables == null) { |
|
if (dqt != null) { |
|
qTables = collectQTablesFromMetadata(metadata); |
|
} else if (streamQTables != null) { |
|
qTables = streamQTables; |
|
} else if ((jparam != null) && (jparam.areTablesSet())) { |
|
qTables = jparam.getQTables(); |
|
} else { |
|
qTables = JPEG.getDefaultQTables(); |
|
} |
|
|
|
} |
|
|
|
|
|
if (optimizeHuffman == false) { |
|
|
|
if ((dht != null) && (metadataProgressive == false)) { |
|
DCHuffmanTables = collectHTablesFromMetadata(metadata, true); |
|
ACHuffmanTables = collectHTablesFromMetadata(metadata, false); |
|
} else if (streamDCHuffmanTables != null) { |
|
DCHuffmanTables = streamDCHuffmanTables; |
|
ACHuffmanTables = streamACHuffmanTables; |
|
} else if ((jparam != null) && (jparam.areTablesSet())) { |
|
DCHuffmanTables = jparam.getDCHuffmanTables(); |
|
ACHuffmanTables = jparam.getACHuffmanTables(); |
|
} else { |
|
DCHuffmanTables = JPEG.getDefaultHuffmanTables(true); |
|
ACHuffmanTables = JPEG.getDefaultHuffmanTables(false); |
|
} |
|
} |
|
|
|
|
|
int [] componentIds = new int[numBandsUsed]; |
|
int [] HsamplingFactors = new int[numBandsUsed]; |
|
int [] VsamplingFactors = new int[numBandsUsed]; |
|
int [] QtableSelectors = new int[numBandsUsed]; |
|
for (int i = 0; i < numBandsUsed; i++) { |
|
componentIds[i] = i+1; |
|
HsamplingFactors[i] = 1; |
|
VsamplingFactors[i] = 1; |
|
QtableSelectors[i] = 0; |
|
} |
|
|
|
|
|
if (sof != null) { |
|
for (int i = 0; i < numBandsUsed; i++) { |
|
if (forceJFIF == false) { |
|
componentIds[i] = sof.componentSpecs[i].componentId; |
|
} |
|
HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor; |
|
VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor; |
|
QtableSelectors[i] = sof.componentSpecs[i].QtableSelector; |
|
} |
|
} |
|
|
|
sourceXOffset += gridX; |
|
sourceWidth -= gridX; |
|
sourceYOffset += gridY; |
|
sourceHeight -= gridY; |
|
|
|
int destWidth = (sourceWidth + periodX - 1)/periodX; |
|
int destHeight = (sourceHeight + periodY - 1)/periodY; |
|
|
|
|
|
int lineSize = sourceWidth*numBandsUsed; |
|
|
|
DataBufferByte buffer = new DataBufferByte(lineSize); |
|
|
|
|
|
int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1]; |
|
|
|
raster = Raster.createInterleavedRaster(buffer, |
|
sourceWidth, 1, |
|
lineSize, |
|
numBandsUsed, |
|
bandOffs, |
|
null); |
|
|
|
// Call the writer, who will call back for every scanline |
|
|
|
clearAbortRequest(); |
|
cbLock.lock(); |
|
try { |
|
processImageStarted(currentImage); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
|
|
boolean aborted = false; |
|
|
|
if (debug) { |
|
System.out.println("inCsType: " + inCsType); |
|
System.out.println("outCsType: " + outCsType); |
|
} |
|
|
|
// Note that getData disables acceleration on buffer, but it is |
|
// just a 1-line intermediate data transfer buffer that does not |
|
|
|
aborted = writeImage(structPointer, |
|
buffer.getData(), |
|
inCsType, outCsType, |
|
numBandsUsed, |
|
bandSizes, |
|
sourceWidth, |
|
destWidth, destHeight, |
|
periodX, periodY, |
|
qTables, |
|
writeDQT, |
|
DCHuffmanTables, |
|
ACHuffmanTables, |
|
writeDHT, |
|
optimizeHuffman, |
|
(progressiveMode |
|
!= ImageWriteParam.MODE_DISABLED), |
|
numScans, |
|
scans, |
|
componentIds, |
|
HsamplingFactors, |
|
VsamplingFactors, |
|
QtableSelectors, |
|
haveMetadata, |
|
restartInterval); |
|
|
|
cbLock.lock(); |
|
try { |
|
if (aborted) { |
|
processWriteAborted(); |
|
} else { |
|
processImageComplete(); |
|
} |
|
|
|
ios.flush(); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
currentImage++; |
|
} |
|
|
|
@Override |
|
public boolean canWriteSequence() { |
|
return true; |
|
} |
|
|
|
public void prepareWriteSequence(IIOMetadata streamMetadata) |
|
throws IOException { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
prepareWriteSequenceOnThread(streamMetadata); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata) |
|
throws IOException { |
|
if (ios == null) { |
|
throw new IllegalStateException("Output has not been set!"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (streamMetadata != null) { |
|
if (streamMetadata instanceof JPEGMetadata) { |
|
// write a complete tables-only image at the beginning of |
|
|
|
JPEGMetadata jmeta = (JPEGMetadata) streamMetadata; |
|
if (jmeta.isStream == false) { |
|
throw new IllegalArgumentException |
|
("Invalid stream metadata object."); |
|
} |
|
// Check that we are |
|
// at the beginning of the stream, or can go there, and haven't |
|
|
|
if (currentImage != 0) { |
|
throw new IIOException |
|
("JPEG Stream metadata must precede all images"); |
|
} |
|
if (sequencePrepared == true) { |
|
throw new IIOException("Stream metadata already written!"); |
|
} |
|
|
|
// Set the tables |
|
|
|
streamQTables = collectQTablesFromMetadata(jmeta); |
|
if (debug) { |
|
System.out.println("after collecting from stream metadata, " |
|
+ "streamQTables.length is " |
|
+ streamQTables.length); |
|
} |
|
if (streamQTables == null) { |
|
streamQTables = JPEG.getDefaultQTables(); |
|
} |
|
streamDCHuffmanTables = |
|
collectHTablesFromMetadata(jmeta, true); |
|
if (streamDCHuffmanTables == null) { |
|
streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true); |
|
} |
|
streamACHuffmanTables = |
|
collectHTablesFromMetadata(jmeta, false); |
|
if (streamACHuffmanTables == null) { |
|
streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false); |
|
} |
|
|
|
|
|
writeTables(structPointer, |
|
streamQTables, |
|
streamDCHuffmanTables, |
|
streamACHuffmanTables); |
|
} else { |
|
throw new IIOException("Stream metadata must be JPEG metadata"); |
|
} |
|
} |
|
sequencePrepared = true; |
|
} |
|
|
|
public void writeToSequence(IIOImage image, ImageWriteParam param) |
|
throws IOException { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
if (sequencePrepared == false) { |
|
throw new IllegalStateException("sequencePrepared not called!"); |
|
} |
|
|
|
write(null, image, param); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
public void endWriteSequence() throws IOException { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
if (sequencePrepared == false) { |
|
throw new IllegalStateException("sequencePrepared not called!"); |
|
} |
|
sequencePrepared = false; |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
public synchronized void abort() { |
|
setThreadLock(); |
|
try { |
|
|
|
|
|
|
|
*/ |
|
super.abort(); |
|
abortWrite(structPointer); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
@Override |
|
protected synchronized void clearAbortRequest() { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
if (abortRequested()) { |
|
super.clearAbortRequest(); |
|
|
|
resetWriter(structPointer); |
|
|
|
setDest(structPointer); |
|
} |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
private void resetInternalState() { |
|
|
|
resetWriter(structPointer); |
|
|
|
|
|
srcRas = null; |
|
raster = null; |
|
convertTosRGB = false; |
|
currentImage = 0; |
|
numScans = 0; |
|
metadata = null; |
|
} |
|
|
|
public void reset() { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
super.reset(); |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
public void dispose() { |
|
setThreadLock(); |
|
try { |
|
cbLock.check(); |
|
|
|
if (structPointer != 0) { |
|
disposerRecord.dispose(); |
|
structPointer = 0; |
|
} |
|
} finally { |
|
clearThreadLock(); |
|
} |
|
} |
|
|
|
////////// End of public API |
|
|
|
///////// Package-access API |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void warningOccurred(int code) { |
|
cbLock.lock(); |
|
try { |
|
if ((code < 0) || (code > MAX_WARNING)){ |
|
throw new InternalError("Invalid warning index"); |
|
} |
|
processWarningOccurred |
|
(currentImage, |
|
"com.sun.imageio.plugins.jpeg.JPEGImageWriterResources", |
|
Integer.toString(code)); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void warningWithMessage(String msg) { |
|
cbLock.lock(); |
|
try { |
|
processWarningOccurred(currentImage, msg); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
|
|
void thumbnailStarted(int thumbnailIndex) { |
|
cbLock.lock(); |
|
try { |
|
processThumbnailStarted(currentImage, thumbnailIndex); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
void thumbnailProgress(float percentageDone) { |
|
cbLock.lock(); |
|
try { |
|
processThumbnailProgress(percentageDone); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
void thumbnailComplete() { |
|
cbLock.lock(); |
|
try { |
|
processThumbnailComplete(); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
|
|
///////// End of Package-access API |
|
|
|
///////// Private methods |
|
|
|
///////// Metadata handling |
|
|
|
private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed) |
|
throws IIOException { |
|
|
|
if (sof != null) { |
|
if (sof.componentSpecs.length != numBandsUsed) { |
|
throw new IIOException |
|
("Metadata components != number of destination bands"); |
|
} |
|
} |
|
} |
|
|
|
private void checkJFIF(JFIFMarkerSegment jfif, |
|
ImageTypeSpecifier type, |
|
boolean input) { |
|
if (jfif != null) { |
|
if (!JPEG.isJFIFcompliant(type, input)) { |
|
ignoreJFIF = true; |
|
warningOccurred(input |
|
? WARNING_IMAGE_METADATA_JFIF_MISMATCH |
|
: WARNING_DEST_METADATA_JFIF_MISMATCH); |
|
} |
|
} |
|
} |
|
|
|
private void checkAdobe(AdobeMarkerSegment adobe, |
|
ImageTypeSpecifier type, |
|
boolean input) { |
|
if (adobe != null) { |
|
int rightTransform = JPEG.transformForType(type, input); |
|
if (adobe.transform != rightTransform) { |
|
warningOccurred(input |
|
? WARNING_IMAGE_METADATA_ADOBE_MISMATCH |
|
: WARNING_DEST_METADATA_ADOBE_MISMATCH); |
|
if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) { |
|
ignoreAdobe = true; |
|
} else { |
|
newAdobeTransform = rightTransform; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private int [] collectScans(JPEGMetadata metadata, |
|
SOFMarkerSegment sof) { |
|
List segments = new ArrayList(); |
|
int SCAN_SIZE = 9; |
|
int MAX_COMPS_PER_SCAN = 4; |
|
for (Iterator iter = metadata.markerSequence.iterator(); |
|
iter.hasNext();) { |
|
MarkerSegment seg = (MarkerSegment) iter.next(); |
|
if (seg instanceof SOSMarkerSegment) { |
|
segments.add(seg); |
|
} |
|
} |
|
int [] retval = null; |
|
numScans = 0; |
|
if (!segments.isEmpty()) { |
|
numScans = segments.size(); |
|
retval = new int [numScans*SCAN_SIZE]; |
|
int index = 0; |
|
for (int i = 0; i < numScans; i++) { |
|
SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i); |
|
retval[index++] = sos.componentSpecs.length; |
|
for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) { |
|
if (j < sos.componentSpecs.length) { |
|
int compSel = sos.componentSpecs[j].componentSelector; |
|
for (int k = 0; k < sof.componentSpecs.length; k++) { |
|
if (compSel == sof.componentSpecs[k].componentId) { |
|
retval[index++] = k; |
|
break; |
|
} |
|
} |
|
} else { |
|
retval[index++] = 0; |
|
} |
|
} |
|
retval[index++] = sos.startSpectralSelection; |
|
retval[index++] = sos.endSpectralSelection; |
|
retval[index++] = sos.approxHigh; |
|
retval[index++] = sos.approxLow; |
|
} |
|
} |
|
return retval; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private JPEGQTable [] collectQTablesFromMetadata |
|
(JPEGMetadata metadata) { |
|
ArrayList tables = new ArrayList(); |
|
Iterator iter = metadata.markerSequence.iterator(); |
|
while (iter.hasNext()) { |
|
MarkerSegment seg = (MarkerSegment) iter.next(); |
|
if (seg instanceof DQTMarkerSegment) { |
|
DQTMarkerSegment dqt = |
|
(DQTMarkerSegment) seg; |
|
tables.addAll(dqt.tables); |
|
} |
|
} |
|
JPEGQTable [] retval = null; |
|
if (tables.size() != 0) { |
|
retval = new JPEGQTable[tables.size()]; |
|
for (int i = 0; i < retval.length; i++) { |
|
retval[i] = |
|
new JPEGQTable(((DQTMarkerSegment.Qtable)tables.get(i)).data); |
|
} |
|
} |
|
return retval; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private JPEGHuffmanTable[] collectHTablesFromMetadata |
|
(JPEGMetadata metadata, boolean wantDC) throws IIOException { |
|
ArrayList tables = new ArrayList(); |
|
Iterator iter = metadata.markerSequence.iterator(); |
|
while (iter.hasNext()) { |
|
MarkerSegment seg = (MarkerSegment) iter.next(); |
|
if (seg instanceof DHTMarkerSegment) { |
|
DHTMarkerSegment dht = |
|
(DHTMarkerSegment) seg; |
|
for (int i = 0; i < dht.tables.size(); i++) { |
|
DHTMarkerSegment.Htable htable = |
|
(DHTMarkerSegment.Htable) dht.tables.get(i); |
|
if (htable.tableClass == (wantDC ? 0 : 1)) { |
|
tables.add(htable); |
|
} |
|
} |
|
} |
|
} |
|
JPEGHuffmanTable [] retval = null; |
|
if (tables.size() != 0) { |
|
DHTMarkerSegment.Htable [] htables = |
|
new DHTMarkerSegment.Htable[tables.size()]; |
|
tables.toArray(htables); |
|
retval = new JPEGHuffmanTable[tables.size()]; |
|
for (int i = 0; i < retval.length; i++) { |
|
retval[i] = null; |
|
for (int j = 0; j < tables.size(); j++) { |
|
if (htables[j].tableID == i) { |
|
if (retval[i] != null) { |
|
throw new IIOException("Metadata has duplicate Htables!"); |
|
} |
|
retval[i] = new JPEGHuffmanTable(htables[j].numCodes, |
|
htables[j].values); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return retval; |
|
} |
|
|
|
/////////// End of metadata handling |
|
|
|
////////////// ColorSpace conversion |
|
|
|
private int getSrcCSType(ImageTypeSpecifier type) { |
|
return getSrcCSType(type.getColorModel()); |
|
} |
|
|
|
private int getSrcCSType(RenderedImage rimage) { |
|
return getSrcCSType(rimage.getColorModel()); |
|
} |
|
|
|
private int getSrcCSType(ColorModel cm) { |
|
int retval = JPEG.JCS_UNKNOWN; |
|
if (cm != null) { |
|
boolean alpha = cm.hasAlpha(); |
|
ColorSpace cs = cm.getColorSpace(); |
|
switch (cs.getType()) { |
|
case ColorSpace.TYPE_GRAY: |
|
retval = JPEG.JCS_GRAYSCALE; |
|
break; |
|
case ColorSpace.TYPE_RGB: |
|
if (alpha) { |
|
retval = JPEG.JCS_RGBA; |
|
} else { |
|
retval = JPEG.JCS_RGB; |
|
} |
|
break; |
|
case ColorSpace.TYPE_YCbCr: |
|
if (alpha) { |
|
retval = JPEG.JCS_YCbCrA; |
|
} else { |
|
retval = JPEG.JCS_YCbCr; |
|
} |
|
break; |
|
case ColorSpace.TYPE_3CLR: |
|
if (cs == JPEG.JCS.getYCC()) { |
|
if (alpha) { |
|
retval = JPEG.JCS_YCCA; |
|
} else { |
|
retval = JPEG.JCS_YCC; |
|
} |
|
} |
|
case ColorSpace.TYPE_CMYK: |
|
retval = JPEG.JCS_CMYK; |
|
break; |
|
} |
|
} |
|
return retval; |
|
} |
|
|
|
private int getDestCSType(ImageTypeSpecifier destType) { |
|
ColorModel cm = destType.getColorModel(); |
|
boolean alpha = cm.hasAlpha(); |
|
ColorSpace cs = cm.getColorSpace(); |
|
int retval = JPEG.JCS_UNKNOWN; |
|
switch (cs.getType()) { |
|
case ColorSpace.TYPE_GRAY: |
|
retval = JPEG.JCS_GRAYSCALE; |
|
break; |
|
case ColorSpace.TYPE_RGB: |
|
if (alpha) { |
|
retval = JPEG.JCS_RGBA; |
|
} else { |
|
retval = JPEG.JCS_RGB; |
|
} |
|
break; |
|
case ColorSpace.TYPE_YCbCr: |
|
if (alpha) { |
|
retval = JPEG.JCS_YCbCrA; |
|
} else { |
|
retval = JPEG.JCS_YCbCr; |
|
} |
|
break; |
|
case ColorSpace.TYPE_3CLR: |
|
if (cs == JPEG.JCS.getYCC()) { |
|
if (alpha) { |
|
retval = JPEG.JCS_YCCA; |
|
} else { |
|
retval = JPEG.JCS_YCC; |
|
} |
|
} |
|
case ColorSpace.TYPE_CMYK: |
|
retval = JPEG.JCS_CMYK; |
|
break; |
|
} |
|
return retval; |
|
} |
|
|
|
private int getDefaultDestCSType(ImageTypeSpecifier type) { |
|
return getDefaultDestCSType(type.getColorModel()); |
|
} |
|
|
|
private int getDefaultDestCSType(RenderedImage rimage) { |
|
return getDefaultDestCSType(rimage.getColorModel()); |
|
} |
|
|
|
private int getDefaultDestCSType(ColorModel cm) { |
|
int retval = JPEG.JCS_UNKNOWN; |
|
if (cm != null) { |
|
boolean alpha = cm.hasAlpha(); |
|
ColorSpace cs = cm.getColorSpace(); |
|
switch (cs.getType()) { |
|
case ColorSpace.TYPE_GRAY: |
|
retval = JPEG.JCS_GRAYSCALE; |
|
break; |
|
case ColorSpace.TYPE_RGB: |
|
if (alpha) { |
|
retval = JPEG.JCS_YCbCrA; |
|
} else { |
|
retval = JPEG.JCS_YCbCr; |
|
} |
|
break; |
|
case ColorSpace.TYPE_YCbCr: |
|
if (alpha) { |
|
retval = JPEG.JCS_YCbCrA; |
|
} else { |
|
retval = JPEG.JCS_YCbCr; |
|
} |
|
break; |
|
case ColorSpace.TYPE_3CLR: |
|
if (cs == JPEG.JCS.getYCC()) { |
|
if (alpha) { |
|
retval = JPEG.JCS_YCCA; |
|
} else { |
|
retval = JPEG.JCS_YCC; |
|
} |
|
} |
|
case ColorSpace.TYPE_CMYK: |
|
retval = JPEG.JCS_YCCK; |
|
break; |
|
} |
|
} |
|
return retval; |
|
} |
|
|
|
private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) { |
|
int hsamp0 = specs[0].HsamplingFactor; |
|
int vsamp0 = specs[0].VsamplingFactor; |
|
for (int i = 1; i < specs.length; i++) { |
|
if ((specs[i].HsamplingFactor != hsamp0) || |
|
(specs[i].HsamplingFactor != hsamp0)) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
////////////// End of ColorSpace conversion |
|
|
|
////////////// Native methods and callbacks |
|
|
|
|
|
private static native void initWriterIDs(Class qTableClass, |
|
Class huffClass); |
|
|
|
|
|
private native long initJPEGImageWriter(); |
|
|
|
|
|
private native void setDest(long structPointer); |
|
|
|
|
|
|
|
*/ |
|
private native boolean writeImage(long structPointer, |
|
byte [] data, |
|
int inCsType, int outCsType, |
|
int numBands, |
|
int [] bandSizes, |
|
int srcWidth, |
|
int destWidth, int destHeight, |
|
int stepX, int stepY, |
|
JPEGQTable [] qtables, |
|
boolean writeDQT, |
|
JPEGHuffmanTable[] DCHuffmanTables, |
|
JPEGHuffmanTable[] ACHuffmanTables, |
|
boolean writeDHT, |
|
boolean optimizeHuffman, |
|
boolean progressive, |
|
int numScans, |
|
int [] scans, |
|
int [] componentIds, |
|
int [] HsamplingFactors, |
|
int [] VsamplingFactors, |
|
int [] QtableSelectors, |
|
boolean haveMetadata, |
|
int restartInterval); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void writeMetadata() throws IOException { |
|
if (metadata == null) { |
|
if (writeDefaultJFIF) { |
|
JFIFMarkerSegment.writeDefaultJFIF(ios, |
|
thumbnails, |
|
iccProfile, |
|
this); |
|
} |
|
if (writeAdobe) { |
|
AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform); |
|
} |
|
} else { |
|
metadata.writeToStream(ios, |
|
ignoreJFIF, |
|
forceJFIF, |
|
thumbnails, |
|
iccProfile, |
|
ignoreAdobe, |
|
newAdobeTransform, |
|
this); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private native void writeTables(long structPointer, |
|
JPEGQTable [] qtables, |
|
JPEGHuffmanTable[] DCHuffmanTables, |
|
JPEGHuffmanTable[] ACHuffmanTables); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void grabPixels(int y) { |
|
|
|
Raster sourceLine = null; |
|
if (indexed) { |
|
sourceLine = srcRas.createChild(sourceXOffset, |
|
sourceYOffset+y, |
|
sourceWidth, 1, |
|
0, 0, |
|
new int [] {0}); |
|
// If the image has BITMASK transparency, we need to make sure |
|
// it gets converted to 32-bit ARGB, because the JPEG encoder |
|
|
|
boolean forceARGB = |
|
(indexCM.getTransparency() != Transparency.OPAQUE); |
|
BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine, |
|
forceARGB); |
|
sourceLine = temp.getRaster(); |
|
} else { |
|
sourceLine = srcRas.createChild(sourceXOffset, |
|
sourceYOffset+y, |
|
sourceWidth, 1, |
|
0, 0, |
|
srcBands); |
|
} |
|
if (convertTosRGB) { |
|
if (debug) { |
|
System.out.println("Converting to sRGB"); |
|
} |
|
// The first time through, converted is null, so |
|
// a new raster is allocated. It is then reused |
|
|
|
converted = convertOp.filter(sourceLine, converted); |
|
sourceLine = converted; |
|
} |
|
if (isAlphaPremultiplied) { |
|
WritableRaster wr = sourceLine.createCompatibleWritableRaster(); |
|
int[] data = null; |
|
data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(), |
|
sourceLine.getWidth(), sourceLine.getHeight(), |
|
data); |
|
wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(), |
|
sourceLine.getWidth(), sourceLine.getHeight(), |
|
data); |
|
srcCM.coerceData(wr, false); |
|
sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(), |
|
wr.getWidth(), wr.getHeight(), |
|
0, 0, |
|
srcBands); |
|
} |
|
raster.setRect(sourceLine); |
|
if ((y > 7) && (y%8 == 0)) { |
|
cbLock.lock(); |
|
try { |
|
processImageProgress((float) y / (float) sourceHeight * 100.0F); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
} |
|
|
|
|
|
private native void abortWrite(long structPointer); |
|
|
|
|
|
private native void resetWriter(long structPointer); |
|
|
|
|
|
private static native void disposeWriter(long structPointer); |
|
|
|
private static class JPEGWriterDisposerRecord implements DisposerRecord { |
|
private long pData; |
|
|
|
public JPEGWriterDisposerRecord(long pData) { |
|
this.pData = pData; |
|
} |
|
|
|
public synchronized void dispose() { |
|
if (pData != 0) { |
|
disposeWriter(pData); |
|
pData = 0; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void writeOutputData(byte[] data, int offset, int len) |
|
throws IOException |
|
{ |
|
cbLock.lock(); |
|
try { |
|
ios.write(data, offset, len); |
|
} finally { |
|
cbLock.unlock(); |
|
} |
|
} |
|
|
|
private Thread theThread = null; |
|
private int theLockCount = 0; |
|
|
|
private synchronized void setThreadLock() { |
|
Thread currThread = Thread.currentThread(); |
|
if (theThread != null) { |
|
if (theThread != currThread) { |
|
// it looks like that this reader instance is used |
|
|
|
throw new IllegalStateException("Attempt to use instance of " + |
|
this + " locked on thread " + |
|
theThread + " from thread " + |
|
currThread); |
|
} else { |
|
theLockCount ++; |
|
} |
|
} else { |
|
theThread = currThread; |
|
theLockCount = 1; |
|
} |
|
} |
|
|
|
private synchronized void clearThreadLock() { |
|
Thread currThread = Thread.currentThread(); |
|
if (theThread == null || theThread != currThread) { |
|
throw new IllegalStateException("Attempt to clear thread lock form wrong thread. " + |
|
"Locked thread: " + theThread + |
|
"; current thread: " + currThread); |
|
} |
|
theLockCount --; |
|
if (theLockCount == 0) { |
|
theThread = null; |
|
} |
|
} |
|
|
|
private CallBackLock cbLock = new CallBackLock(); |
|
|
|
private static class CallBackLock { |
|
|
|
private State lockState; |
|
|
|
CallBackLock() { |
|
lockState = State.Unlocked; |
|
} |
|
|
|
void check() { |
|
if (lockState != State.Unlocked) { |
|
throw new IllegalStateException("Access to the writer is not allowed"); |
|
} |
|
} |
|
|
|
private void lock() { |
|
lockState = State.Locked; |
|
} |
|
|
|
private void unlock() { |
|
lockState = State.Unlocked; |
|
} |
|
|
|
private static enum State { |
|
Unlocked, |
|
Locked |
|
} |
|
} |
|
} |