mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
TMI-92: Color conversion between embedded ICC profile and sRGB now correctly applied for common case.
This commit is contained in:
parent
025021442f
commit
ecfcea98df
@ -293,7 +293,6 @@ public final class ColorSpaces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
return ICC_Profile.getInstance(stream);
|
return ICC_Profile.getInstance(stream);
|
||||||
}
|
}
|
||||||
catch (IOException ignore) {
|
catch (IOException ignore) {
|
||||||
|
@ -68,6 +68,9 @@ import java.util.List;
|
|||||||
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
|
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
|
||||||
// Done: Allow reading the extra alpha channels (index after composite data)
|
// Done: Allow reading the extra alpha channels (index after composite data)
|
||||||
public final class PSDImageReader extends ImageReaderBase {
|
public final class PSDImageReader extends ImageReaderBase {
|
||||||
|
|
||||||
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.psd.debug"));
|
||||||
|
|
||||||
private PSDHeader header;
|
private PSDHeader header;
|
||||||
private ICC_ColorSpace colorSpace;
|
private ICC_ColorSpace colorSpace;
|
||||||
private PSDMetadata metadata;
|
private PSDMetadata metadata;
|
||||||
@ -264,9 +267,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
switch (header.mode) {
|
switch (header.mode) {
|
||||||
case PSD.COLOR_MODE_RGB:
|
case PSD.COLOR_MODE_RGB:
|
||||||
// Prefer interleaved versions as they are much faster to display
|
// Prefer interleaved versions as they are much faster to display
|
||||||
// if (header.channels == 3 && header.bits == 8) {
|
|
||||||
if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 8) {
|
if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 8) {
|
||||||
// TODO: ColorConvertOp to CS_sRGB
|
|
||||||
// TODO: Integer raster
|
// TODO: Integer raster
|
||||||
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_RGB));
|
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_RGB));
|
||||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||||
@ -276,9 +277,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else if (header.channels >= 4 && header.bits == 8) {
|
|
||||||
else if (rawType.getNumBands() >= 4 && rawType.getBitsPerBand(0) == 8) {
|
else if (rawType.getNumBands() >= 4 && rawType.getBitsPerBand(0) == 8) {
|
||||||
// TODO: ColorConvertOp to CS_sRGB
|
|
||||||
// TODO: Integer raster
|
// TODO: Integer raster
|
||||||
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_ARGB));
|
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_ARGB));
|
||||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
||||||
@ -288,35 +287,28 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else if (header.channels == 3 && header.bits == 16) {
|
|
||||||
else if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 16) {
|
else if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 16) {
|
||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
|
||||||
}
|
}
|
||||||
// else if (header.channels >= 4 && header.bits == 16) {
|
|
||||||
else if (rawType.getNumBands() >= 4 && rawType.getBitsPerBand(0) == 16) {
|
else if (rawType.getNumBands() >= 4 && rawType.getBitsPerBand(0) == 16) {
|
||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PSD.COLOR_MODE_CMYK:
|
case PSD.COLOR_MODE_CMYK:
|
||||||
// Prefer interleaved versions as they are much faster to display
|
// Prefer interleaved versions as they are much faster to display
|
||||||
// TODO: ColorConvertOp to CS_sRGB
|
|
||||||
// TODO: We should convert these to their RGB equivalents while reading for the common-case,
|
// TODO: We should convert these to their RGB equivalents while reading for the common-case,
|
||||||
// as Java2D is extremely slow displaying custom images.
|
// as Java2D is extremely slow displaying custom images.
|
||||||
// Converting to RGB is also correct behaviour, according to the docs.
|
// Converting to RGB is also correct behaviour, according to the docs.
|
||||||
// Doing this, will require rewriting the image reading, as the raw image data is channelled, not interleaved :-/
|
// Doing this, will require rewriting the image reading, as the raw image data is channelled, not interleaved :-/
|
||||||
// if (header.channels == 4 && header.bits == 8) {
|
|
||||||
if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 8) {
|
if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 8) {
|
||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
|
||||||
}
|
}
|
||||||
// else if (header.channels == 5 && header.bits == 8) {
|
|
||||||
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 8) {
|
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 8) {
|
||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
||||||
}
|
}
|
||||||
// else if (header.channels == 4 && header.bits == 16) {
|
|
||||||
else if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 16) {
|
else if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 16) {
|
||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
|
||||||
}
|
}
|
||||||
// else if (header.channels == 5 && header.bits == 16) {
|
|
||||||
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 16) {
|
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 16) {
|
||||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
|
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
|
||||||
}
|
}
|
||||||
@ -375,25 +367,17 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// TODO: Create temp raster in native format w * 1
|
// TODO: Create temp raster in native format w * 1
|
||||||
// Read (sub-sampled) row into temp raster (skip other rows)
|
// Read (sub-sampled) row into temp raster (skip other rows)
|
||||||
// If color model (color space) is not RGB, do color convert op
|
|
||||||
// Otherwise, copy "through" ColorModel?
|
|
||||||
// Copy pixels from temp raster
|
// Copy pixels from temp raster
|
||||||
// If possible, leave the destination image "untouched" (accelerated)
|
// If possible, leave the destination image "untouched" (accelerated)
|
||||||
// See Jim Grahams comments:
|
// See Jim Grahams comments:
|
||||||
// http://forums.java.net/jive/message.jspa?messageID=295758#295758
|
// http://forums.java.net/jive/message.jspa?messageID=295758#295758
|
||||||
|
|
||||||
// TODO: Doing a per line color convert will be expensive, as data is channelled...
|
|
||||||
// Will need to either convert entire image, or skip back/forth between channels...
|
|
||||||
|
|
||||||
// TODO: Banding...
|
// TODO: Banding...
|
||||||
|
|
||||||
ImageTypeSpecifier spec = getRawImageType(imageIndex);
|
ImageTypeSpecifier spec = getRawImageType(imageIndex);
|
||||||
BufferedImage temp = spec.createBufferedImage(getWidth(imageIndex), 1);
|
BufferedImage temp = spec.createBufferedImage(getWidth(imageIndex), 1);
|
||||||
temp.getRaster();
|
temp.getRaster();
|
||||||
|
|
||||||
if (...)
|
|
||||||
ColorConvertOp convert = new ColorConvertOp(...);
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
final int xSub;
|
final int xSub;
|
||||||
@ -470,7 +454,6 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
final int[] pByteCounts, final int pCompression) throws IOException {
|
final int[] pByteCounts, final int pCompression) throws IOException {
|
||||||
|
|
||||||
final WritableRaster raster = pImage.getRaster();
|
final WritableRaster raster = pImage.getRaster();
|
||||||
// TODO: Conversion if destination cm is not compatible
|
|
||||||
final ColorModel destCM = pImage.getColorModel();
|
final ColorModel destCM = pImage.getColorModel();
|
||||||
|
|
||||||
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
||||||
@ -513,6 +496,39 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
|
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
|
||||||
decomposeAlpha(destCM, raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands());
|
decomposeAlpha(destCM, raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: ColorSpace uses Object.equals(), so we rely on using same instances!
|
||||||
|
if (!pSourceCM.getColorSpace().equals(pImage.getColorModel().getColorSpace())) {
|
||||||
|
convertToDestinationCS(pSourceCM, pImage.getColorModel(), raster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void convertToDestinationCS(final ColorModel sourceCM, ColorModel destinationCM, final WritableRaster raster) {
|
||||||
|
long start = DEBUG ? System.currentTimeMillis() : 0;
|
||||||
|
|
||||||
|
// Color conversion from embedded color space, to destination color space
|
||||||
|
WritableRaster alphaMaskedRaster = destinationCM.hasAlpha()
|
||||||
|
? raster.createWritableChild(0, 0, raster.getWidth(), raster.getHeight(),
|
||||||
|
raster.getMinX(), raster.getMinY(),
|
||||||
|
createBandList(sourceCM.getColorSpace().getNumComponents()))
|
||||||
|
: raster;
|
||||||
|
|
||||||
|
new ColorConvertOp(sourceCM.getColorSpace(), destinationCM.getColorSpace(), null)
|
||||||
|
.filter(alphaMaskedRaster, alphaMaskedRaster);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Color conversion " + (System.currentTimeMillis() - start) + "ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] createBandList(final int numBands) {
|
||||||
|
int[] bands = new int[numBands];
|
||||||
|
|
||||||
|
for (int i = 0; i < numBands; i++) {
|
||||||
|
bands[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processImageProgressForChannel(int channel, int channelCount, int y, int height) {
|
private void processImageProgressForChannel(int channel, int channelCount, int y, int height) {
|
||||||
@ -542,6 +558,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
|
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
|
||||||
if (pRLECompressed) {
|
if (pRLECompressed) {
|
||||||
DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
|
DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (int x = 0; x < pChannelWidth; x++) {
|
for (int x = 0; x < pChannelWidth; x++) {
|
||||||
pRow[x] = input.readInt();
|
pRow[x] = input.readInt();
|
||||||
@ -853,17 +870,15 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
metadata = new PSDMetadata();
|
metadata = new PSDMetadata();
|
||||||
metadata.header = header;
|
metadata.header = header;
|
||||||
|
|
||||||
/*
|
// Contains the required data to define the color mode.
|
||||||
Contains the required data to define the color mode.
|
//
|
||||||
|
// For indexed color images, the count will be equal to 768, and the mode data
|
||||||
For indexed color images, the count will be equal to 768, and the mode data
|
// will contain the color table for the image, in non-interleaved order.
|
||||||
will contain the color table for the image, in non-interleaved order.
|
//
|
||||||
|
// For duotone images, the mode data will contain the duotone specification,
|
||||||
For duotone images, the mode data will contain the duotone specification,
|
// the format of which is not documented. Non-Photoshop readers can treat
|
||||||
the format of which is not documented. Non-Photoshop readers can treat
|
// the duotone image as a grayscale image, and keep the duotone specification
|
||||||
the duotone image as a grayscale image, and keep the duotone specification
|
// around as a black box for use when saving the file.
|
||||||
around as a black box for use when saving the file.
|
|
||||||
*/
|
|
||||||
if (header.mode == PSD.COLOR_MODE_INDEXED) {
|
if (header.mode == PSD.COLOR_MODE_INDEXED) {
|
||||||
metadata.colorData = new PSDColorData(imageInput);
|
metadata.colorData = new PSDColorData(imageInput);
|
||||||
}
|
}
|
||||||
@ -936,11 +951,9 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
|
long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
|
||||||
|
|
||||||
if (layerInfoLength > 0) {
|
if (layerInfoLength > 0) {
|
||||||
/*
|
// "Layer count. If it is a negative number, its absolute value is the number of
|
||||||
"Layer count. If it is a negative number, its absolute value is the number of
|
// layers and the first alpha channel contains the transparency data for the
|
||||||
layers and the first alpha channel contains the transparency data for the
|
// merged result."
|
||||||
merged result."
|
|
||||||
*/
|
|
||||||
int layerCount = imageInput.readShort();
|
int layerCount = imageInput.readShort();
|
||||||
|
|
||||||
PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layerCount)];
|
PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layerCount)];
|
||||||
@ -985,7 +998,6 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private BufferedImage readLayerData(int layerIndex, final PSDLayerInfo pLayerInfo, final ImageTypeSpecifier pRawType, final ImageTypeSpecifier pImageType, ImageReadParam param) throws IOException {
|
|
||||||
private BufferedImage readLayerData(final int layerIndex, final ImageReadParam param) throws IOException {
|
private BufferedImage readLayerData(final int layerIndex, final ImageReadParam param) throws IOException {
|
||||||
final int width = getLayerWidth(layerIndex);
|
final int width = getLayerWidth(layerIndex);
|
||||||
final int height = getLayerHeight(layerIndex);
|
final int height = getLayerHeight(layerIndex);
|
||||||
@ -1015,14 +1027,12 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
final int ysub = 1;
|
final int ysub = 1;
|
||||||
|
|
||||||
final WritableRaster raster = layer.getRaster();
|
final WritableRaster raster = layer.getRaster();
|
||||||
// TODO: Conversion if destination cm is not compatible
|
|
||||||
final ColorModel destCM = layer.getColorModel();
|
final ColorModel destCM = layer.getColorModel();
|
||||||
|
|
||||||
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
||||||
ColorModel sourceCM = imageType.getColorModel();
|
ColorModel sourceCM = imageType.getColorModel();
|
||||||
final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null;
|
final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null;
|
||||||
|
|
||||||
// final int channels = rowRaster.getNumBands();
|
|
||||||
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
|
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
|
||||||
final int interleavedBands = banded ? 1 : raster.getNumBands();
|
final int interleavedBands = banded ? 1 : raster.getNumBands();
|
||||||
|
|
||||||
@ -1101,6 +1111,10 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!sourceCM.getColorSpace().equals(destCM.getColorSpace())) {
|
||||||
|
convertToDestinationCS(sourceCM, destCM, raster);
|
||||||
|
}
|
||||||
|
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1143,7 +1157,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
/// Layer support
|
/// Layer support
|
||||||
|
|
||||||
@Override protected void checkBounds(final int index) throws IOException {
|
@Override
|
||||||
|
protected void checkBounds(final int index) throws IOException {
|
||||||
// Avoid parsing layer stuff, if we just want to read the composite data
|
// Avoid parsing layer stuff, if we just want to read the composite data
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
assertInput();
|
assertInput();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user