Major refactor of image reading. Now reading each channel more cleanly.

This commit is contained in:
Harald Kuhr 2009-10-03 17:55:25 +02:00
parent 4848a3caff
commit ad904ccd90

View File

@ -278,6 +278,7 @@ public class PSDImageReader extends ImageReaderBase {
// TODO: Test if explicit destination is compatible or throw IllegalArgumentException // TODO: Test if explicit destination is compatible or throw IllegalArgumentException
BufferedImage image = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight); BufferedImage image = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight);
ImageTypeSpecifier rawType = getRawImageType(pIndex);
processImageStarted(pIndex); processImageStarted(pIndex);
@ -344,7 +345,8 @@ public class PSDImageReader extends ImageReaderBase {
throw new IIOException("Unknown compression type: " + compression); throw new IIOException("Unknown compression type: " + compression);
} }
readImageData(image, source, dest, xSub, ySub, offsets, compression); // What we read here is the "composite layer" of the PSD file
readImageData(image, rawType.getColorModel(), source, dest, xSub, ySub, offsets, compression);
if (abortRequested()) { if (abortRequested()) {
processReadAborted(); processReadAborted();
@ -357,320 +359,247 @@ public class PSDImageReader extends ImageReaderBase {
} }
private void readImageData(final BufferedImage pImage, private void readImageData(final BufferedImage pImage,
final Rectangle pSource, final Rectangle pDest, final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub, final int pXSub, final int pYSub,
final int[] pOffsets, final int pCompression) throws IOException { final int[] pOffsets, final int pCompression) throws IOException {
// TODO: Refactor so that we loop through channels here, and read one channel in each of the methods below
// Compute pixel (not array) offsets based on raster (banded/interleaved)?
switch (mHeader.mBits) { final WritableRaster raster = pImage.getRaster();
case 1: // TODO: Conversion if destination cm is not compatible
read1bitData(pImage.getRaster(), pImage.getColorModel(), pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE); final ColorModel destCM = pImage.getColorModel();
break;
case 8:
read8bitData(pImage.getRaster(), pImage.getColorModel(), pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
break;
case 16:
read16bitData(pImage.getRaster(), pImage.getColorModel(), pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
break;
default:
throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits);
}
}
private void read16bitData(final WritableRaster pRaster, final ColorModel pDestinationColorModel, // TODO: This raster is 3-5 times longer than needed, depending on number of channels...
final Rectangle pSource, final Rectangle pDest, final WritableRaster rowRaster = pSourceCM.createCompatibleWritableRaster(mHeader.mWidth, 1);
final int pXSub, final int pYSub,
final int[] pRowOffsets, final boolean pRLECompressed) throws IOException {
final int channels = pRaster.getNumBands();
// TODO: FixMe: Use real source color model from native (raw) image type, and convert if needed final int channels = rowRaster.getNumBands();
ColorModel sourceColorModel = pDestinationColorModel; final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
// TODO: This raster is 3-4 times longer than needed, depending on number of channels... final int interleavedBands = banded ? 1 : raster.getNumBands();
WritableRaster rowRaster = sourceColorModel.createCompatibleWritableRaster(mHeader.mWidth, 1);
final short[] row = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
final boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK; for (int c = 0; c < channels; c++) {
final int colorComponents = sourceColorModel.getColorSpace().getNumComponents(); int bandOffset = banded ? 0 : interleavedBands - 1 - c;
DataBufferUShort buffer = (DataBufferUShort) pRaster.getDataBuffer(); switch (mHeader.mBits) {
final boolean banded = buffer.getNumBanks() > 1; case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer();
byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData();
short[] data = null; read1bitChannel(c, data1, interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
int x = 0, y = 0, c = 0;
try {
for (c = 0; c < channels; c++) {
data = banded ? buffer.getData(c) : buffer.getData();
for (y = 0; y < mHeader.mHeight; y++) {
// Length is in shorts!?
int length = 2 * (pRLECompressed ? pRowOffsets[c * mHeader.mHeight + y] : mHeader.mWidth);
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
try {
for (x = 0; x < mHeader.mWidth; x++) {
row[x] = input.readShort();
}
}
finally {
input.close();
}
}
else {
mImageInput.readFully(row, 0, mHeader.mWidth);
}
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = banded ?
(y - pSource.y) / pYSub * pDest.width :
(y - pSource.y) / pYSub * pDest.width * channels + (channels - 1 - c);
for (int i = 0; i < pDest.width; i++) {
short value = row[pSource.x + i * pXSub];
// CMYK values are stored inverted, but alpha is not
if (isCMYK && c < colorComponents) {
value = (short) (65535 - value & 0xffff);
}
if (banded) {
data[offset + i] = value;
}
else {
data[offset + i * channels] = value;
}
}
}
else {
mImageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgress((c * y * 100) / mHeader.mChannels * mHeader.mHeight);
}
if (abortRequested()) {
break; break;
} case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer();
byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData();
read8bitChannel(c, data8, interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer();
short[] data16 = banded ? buffer16.getData(c) : buffer16.getData();
read16bitChannel(c, data16, interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
break;
default:
throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits);
}
if (abortRequested()) {
break;
} }
} }
catch (IOException e) {
System.err.println("c: " + c);
System.err.println("y: " + y);
System.err.println("x: " + x);
throw e;
}
catch (IndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("data.length: " + data.length);
System.err.println("c: " + c);
System.err.println("y: " + y);
System.err.println("x: " + x);
throw e;
}
// TODO: Alpha in 16 bits samples!? if (mHeader.mBits == 8) {
// 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(sourceColorModel, data, pDest.width, pDest.height, channels); decomposeAlpha(destCM, (DataBufferByte) raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands());
}
} }
private void read8bitData(final WritableRaster pRaster, final ColorModel pDestinationColorModel, private void read16bitChannel(final int pChannel,
final Rectangle pSource, final Rectangle pDest, final short[] pData, final int pBands, final int pBandOffset,
final int pXSub, final int pYSub, final ColorModel pSourceColorModel,
final int[] pRowOffsets, final boolean pRLECompressed) throws IOException final short[] pRow,
{ final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub,
final int channels = pRaster.getNumBands(); final int[] pRowOffsets, final boolean pRLECompressed) throws IOException {
// TODO: FixMe: Use real source color model from native (raw) image type, and convert if needed final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
ColorModel sourceColorModel = pDestinationColorModel; final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
// TODO: This raster is 3-4 times longer than needed, depending on number of channels...
WritableRaster rowRaster = sourceColorModel.createCompatibleWritableRaster(mHeader.mWidth, 1);
final byte[] row = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
final boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK; for (int y = 0; y < mHeader.mHeight; y++) {
final int colorComponents = sourceColorModel.getColorSpace().getNumComponents(); // NOTE: Length is in *16 bit values* (shorts)
int length = 2 * (pRLECompressed ? pRowOffsets[pChannel * mHeader.mHeight + y] : mHeader.mWidth);
DataBufferByte buffer = (DataBufferByte) pRaster.getDataBuffer(); // TODO: Sometimes need to read the line y == source.y + source.height...
final boolean banded = buffer.getNumBanks() > 1; // Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
byte[] data = null; if (pRLECompressed) {
int x = 0, y = 0, c = 0; DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
try { try {
for (c = 0; c < channels; c++) { for (int x = 0; x < mHeader.mWidth; x++) {
data = banded ? buffer.getData(c) : buffer.getData(); pRow[x] = input.readShort();
for (y = 0; y < mHeader.mHeight; y++) {
int length = pRLECompressed ? pRowOffsets[c * mHeader.mHeight + y] : mHeader.mWidth;
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
try {
input.readFully(row, 0, mHeader.mWidth);
}
finally {
input.close();
}
}
else {
mImageInput.readFully(row, 0, mHeader.mWidth);
}
// TODO: If banded and not sub sampling/cmyk, we could just copy using System.arraycopy
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = banded ?
(y - pSource.y) / pYSub * pDest.width :
(y - pSource.y) / pYSub * pDest.width * channels + (channels - 1 - c);
for (int i = 0; i < pDest.width; i++) {
byte value = row[pSource.x + i * pXSub];
// CMYK values are stored inverted, but alpha is not
if (isCMYK && c < colorComponents) {
value = (byte) (255 - value & 0xff);
}
if (banded) {
data[offset + i] = value;
}
else {
data[offset + i * channels] = value;
}
} }
} }
else { finally {
mImageInput.skipBytes(length); input.close();
}
if (abortRequested()) {
break;
}
processImageProgress((c * y * 100) / mHeader.mChannels * mHeader.mHeight);
}
if (abortRequested()) {
break;
}
}
}
catch (IOException e) {
System.err.println("c: " + c);
System.err.println("y: " + y);
System.err.println("x: " + x);
throw e;
}
catch (IndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("data.length: " + data.length);
System.err.println("c: " + c);
System.err.println("y: " + y);
System.err.println("x: " + x);
throw e;
}
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
decomposeAlpha(sourceColorModel, buffer, pDest.width, pDest.height, channels);
}
private void read1bitData(final WritableRaster pRaster, final ColorModel pDestinationColorModel,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int[] pRowOffsets, final boolean pRLECompressed) throws IOException {
final byte[] data = ((DataBufferByte) pRaster.getDataBuffer()).getData();
// TODO: FixMe: Use real source color model from native (raw) image type, and convert if needed
ColorModel sourceColorModel = pDestinationColorModel;
WritableRaster rowRaster = sourceColorModel.createCompatibleWritableRaster(mHeader.mWidth, 1);
final byte[] row = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
final int destWidth = (pDest.width + 7) / 8;
int y = 0;
try {
for (y = 0; y < mHeader.mHeight; y++) {
int length = pRLECompressed ? pRowOffsets[y] : mHeader.mWidth;
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
try {
input.readFully(row, 0, row.length);
}
finally {
input.close();
}
}
else {
mImageInput.readFully(row, 0, row.length);
}
// TODO: Destination offset...??
int offset = (y - pSource.y) / pYSub * destWidth;
if (pXSub == 1 && pSource.x % 8 == 0) {
// Fast normal case, no sub sampling
for (int i = 0; i < destWidth; i++) {
byte value = row[pSource.x / 8 + i * pXSub];
// NOTE: Invert bits to match Java's default monochrome
data[offset + i] = (byte) (~value & 0xff);
}
}
else {
// Copy line sub sampled into real data
final int maxX = pSource.x + pSource.width;
int x = pSource.x;
for (int i = 0; i < destWidth; i++) {
byte result = 0;
for (int j = 0; j < 8 && x < maxX; j++) {
int bytePos = x / 8;
int sourceBitOff = 7 - (x % 8);
int mask = 1 << sourceBitOff;
int destBitOff = 7 - j;
// Shift bit into place
result |= ((row[bytePos] & mask) >> sourceBitOff) << destBitOff;
x += pXSub;
}
// NOTE: Invert bits to match Java's default monochrome
data[offset + i] = (byte) (~result & 0xff);
}
} }
} }
else { else {
mImageInput.skipBytes(length); mImageInput.readFully(pRow, 0, mHeader.mWidth);
} }
if (abortRequested()) { // TODO: Destination offset...??
break; // Copy line sub sampled into real data
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
short value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
if (isCMYK && pChannel < colorComponents) {
value = (short) (65535 - value & 0xffff);
}
pData[offset + x * pBands] = value;
} }
processImageProgress((y * 100) / mHeader.mHeight);
} }
} else {
catch (IOException e) { mImageInput.skipBytes(length);
System.err.println("y: " + y); }
throw e;
} if (abortRequested()) {
catch (IndexOutOfBoundsException e) { break;
e.printStackTrace(); }
System.out.println("data.length: " + data.length); processImageProgress((pChannel * y * 100) / mHeader.mChannels * mHeader.mHeight);
System.err.println("y: " + y);
throw e;
} }
} }
private void read8bitChannel(final int pChannel,
final byte[] pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final byte[] pRow,
final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub,
final int[] pRowOffsets, final boolean pRLECompressed) throws IOException {
final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
for (int y = 0; y < mHeader.mHeight; y++) {
int length = pRLECompressed ? pRowOffsets[pChannel * mHeader.mHeight + y] : mHeader.mWidth;
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
try {
input.readFully(pRow, 0, mHeader.mWidth);
}
finally {
input.close();
}
}
else {
mImageInput.readFully(pRow, 0, mHeader.mWidth);
}
// TODO: If banded and not sub sampling/cmyk, we could just copy using System.arraycopy
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
byte value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
if (isCMYK && pChannel < colorComponents) {
value = (byte) (255 - value & 0xff);
}
pData[offset + x * pBands] = value;
}
}
else {
mImageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgress((pChannel * y * 100) / mHeader.mChannels * mHeader.mHeight);
}
}
private void read1bitChannel(final int pChannel,
final byte[] pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final byte[] pRow,
final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub,
final int[] pRowOffsets, boolean pRLECompressed) throws IOException {
final int destWidth = (pDest.width + 7) / 8;
for (int y = 0; y < mHeader.mHeight; y++) {
int length = pRLECompressed ? pRowOffsets[y] : mHeader.mWidth;
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
try {
input.readFully(pRow, 0, pRow.length);
}
finally {
input.close();
}
}
else {
mImageInput.readFully(pRow, 0, pRow.length);
}
// TODO: Destination offset...??
int offset = (y - pSource.y) / pYSub * destWidth;
if (pXSub == 1 && pSource.x % 8 == 0) {
// Fast normal case, no sub sampling
for (int i = 0; i < destWidth; i++) {
byte value = pRow[pSource.x / 8 + i * pXSub];
// NOTE: Invert bits to match Java's default monochrome
pData[offset + i] = (byte) (~value & 0xff);
}
}
else {
// Copy line sub sampled into real data
final int maxX = pSource.x + pSource.width;
int x = pSource.x;
for (int i = 0; i < destWidth; i++) {
byte result = 0;
for (int j = 0; j < 8 && x < maxX; j++) {
int bytePos = x / 8;
int sourceBitOff = 7 - (x % 8);
int mask = 1 << sourceBitOff;
int destBitOff = 7 - j;
// Shift bit into place
result |= ((pRow[bytePos] & mask) >> sourceBitOff) << destBitOff;
x += pXSub;
}
// NOTE: Invert bits to match Java's default monochrome
pData[offset + i] = (byte) (~result & 0xff);
}
}
}
else {
mImageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgress((pChannel * y * 100) / mHeader.mChannels * mHeader.mHeight);
}
}
private void decomposeAlpha(final ColorModel pModel, final DataBufferByte pBuffer, private void decomposeAlpha(final ColorModel pModel, final DataBufferByte pBuffer,
final int pWidth, final int pHeight, final int pChannels) { final int pWidth, final int pHeight, final int pChannels) {
// TODO: Is the document background always white!? // TODO: Is the document background always white!?
@ -765,6 +694,7 @@ public class PSDImageReader extends ImageReaderBase {
} }
} }
// TODO: Flags or list of interesting resources to parse
private void readImageResources(final boolean pParseData) throws IOException { private void readImageResources(final boolean pParseData) throws IOException {
// TODO: Avoid unnecessary stream repositioning // TODO: Avoid unnecessary stream repositioning
long pos = mImageInput.getFlushedPosition(); long pos = mImageInput.getFlushedPosition();
@ -778,6 +708,7 @@ public class PSDImageReader extends ImageReaderBase {
long expectedEnd = mImageInput.getStreamPosition() + length; long expectedEnd = mImageInput.getStreamPosition() + length;
while (mImageInput.getStreamPosition() < expectedEnd) { while (mImageInput.getStreamPosition() < expectedEnd) {
// TODO: Have PSDImageResources defer actual parsing? (Just store stream offsets)
PSDImageResource resource = PSDImageResource.read(mImageInput); PSDImageResource resource = PSDImageResource.read(mImageInput);
mImageResources.add(resource); mImageResources.add(resource);
} }
@ -1030,6 +961,8 @@ public class PSDImageReader extends ImageReaderBase {
param.setSourceSubsampling(subsampleFactor, subsampleFactor, 0, 0); param.setSourceSubsampling(subsampleFactor, subsampleFactor, 0, 0);
} }
// param.setDestinationType(imageReader.getRawImageType(0));
BufferedImage image = imageReader.read(0, param); BufferedImage image = imageReader.read(0, param);
System.out.println("time: " + (System.currentTimeMillis() - start)); System.out.println("time: " + (System.currentTimeMillis() - start));
System.out.println("image: " + image); System.out.println("image: " + image);