mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 11:05:29 -04:00
TMI-TIFF: Implemented YCbCr reading.
This commit is contained in:
parent
8c4f9d3ed6
commit
47fbf473db
@ -35,7 +35,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* LZWDecoder
|
||||
* Implements Lempel-Ziv & Welch (LZW) decompression.
|
||||
* Inspired by libTiff's LZW decompression.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
@ -67,8 +68,9 @@ abstract class LZWDecoder implements Decoder {
|
||||
protected LZWDecoder(final boolean compatibilityMode) {
|
||||
this.compatibilityMode = compatibilityMode;
|
||||
|
||||
table = new String[compatibilityMode ? 4096 + 1024 : 4096]; // libTiff adds another 1024 "for compatibility"...
|
||||
table = new String[compatibilityMode ? 4096 + 1024 : 4096]; // libTiff adds 1024 "for compatibility"...
|
||||
|
||||
// First 258 entries of table is always fixed
|
||||
for (int i = 0; i < 256; i++) {
|
||||
table[i] = new String((byte) i);
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
@ -129,6 +130,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
||||
|
||||
// NOTE: DO NOT MODIFY OR EXPOSE!
|
||||
static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
|
||||
|
||||
private CompoundDirectory IFDs;
|
||||
private Directory currentIFD;
|
||||
|
||||
@ -217,7 +221,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
getSampleFormat(); // We don't support anything but SAMPLEFORMAT_UINT at the moment, just sanity checking input
|
||||
int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
|
||||
int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
|
||||
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXELS, 1);
|
||||
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
|
||||
int bitsPerSample = getBitsPerSample();
|
||||
int dataType = bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
|
||||
|
||||
@ -254,6 +258,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
||||
// JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not JPEG...
|
||||
// TODO: Sanity check that we have SamplesPerPixel == 3, BitsPerSample == [8,8,8] and Compression == 1 (none), 5 (LZW), or 6 (JPEG)
|
||||
// TODO: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?)
|
||||
case TIFFBaseline.PHOTOMETRIC_RGB:
|
||||
// RGB
|
||||
@ -427,6 +432,10 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||
readIFD(imageIndex);
|
||||
|
||||
System.err.println("currentIFD.getEntryById(TIFF.TAG_REFERENCE_BLACK_WHITE): " + currentIFD.getEntryById(TIFF.TAG_REFERENCE_BLACK_WHITE));
|
||||
System.err.println("currentIFD.getEntryById(TIFF.TAG_TRANSFER_FUNCTION): " + currentIFD.getEntryById(TIFF.TAG_TRANSFER_FUNCTION));
|
||||
System.err.println("currentIFD.getEntryById(TIFF.TAG_TRANSFER_RANGE): " + currentIFD.getEntryById(TIFF.TAG_TRANSFER_RANGE));
|
||||
|
||||
int width = getWidth(imageIndex);
|
||||
int height = getHeight(imageIndex);
|
||||
|
||||
@ -496,8 +505,53 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
case TIFFExtension.COMPRESSION_ZLIB:
|
||||
// 'Adobe-style' Deflate
|
||||
|
||||
// TODO: Read only tiles that lies within region
|
||||
int[] yCbCrSubsampling = null;
|
||||
int yCbCrPos = 1;
|
||||
double[] yCbCrCoefficients = null;
|
||||
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||
// getRawImageType does the lookup/conversion for these
|
||||
if (raster.getNumBands() != 3) {
|
||||
throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires SamplesPerPixel == 3: " + raster.getNumBands());
|
||||
}
|
||||
if (raster.getTransferType() != DataBuffer.TYPE_BYTE) {
|
||||
throw new IIOException("TIFF PhotometricInterpreatation YCbCr requires BitsPerSample == [8,8,8]");
|
||||
}
|
||||
|
||||
yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, 1);
|
||||
|
||||
Entry subSampling = currentIFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING);
|
||||
|
||||
if (subSampling != null) {
|
||||
try {
|
||||
yCbCrSubsampling = (int[]) subSampling.getValue();
|
||||
}
|
||||
catch (ClassCastException e) {
|
||||
throw new IIOException("Unknown TIFF YCbCrSubSampling value type: " + subSampling.getTypeName(), e);
|
||||
}
|
||||
|
||||
if (yCbCrSubsampling.length != 2 ||
|
||||
yCbCrSubsampling[0] != 1 && yCbCrSubsampling[0] != 2 && yCbCrSubsampling[0] != 4 ||
|
||||
yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4 ||
|
||||
yCbCrSubsampling[0] < yCbCrSubsampling[1]) {
|
||||
throw new IIOException("Bad TIFF YCbCrSubSampling value: " + Arrays.toString(yCbCrSubsampling));
|
||||
}
|
||||
}
|
||||
else {
|
||||
yCbCrSubsampling = new int[] {2, 2};
|
||||
}
|
||||
|
||||
Entry coefficients = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS);
|
||||
if (coefficients != null) {
|
||||
Rational[] value = (Rational[]) coefficients.getValue();
|
||||
yCbCrCoefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()};
|
||||
}
|
||||
else {
|
||||
// Default to y CCIR Recommendation 601-1 values
|
||||
yCbCrCoefficients = CCIR_601_1_COEFFICIENTS;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Read only tiles that lies within region
|
||||
// General uncompressed/compressed reading
|
||||
for (int y = 0; y < tilesDown; y++) {
|
||||
int col = 0;
|
||||
@ -510,7 +564,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
imageInput.seek(stripTileOffsets[i]);
|
||||
|
||||
DataInput input;
|
||||
if (compression == TIFFBaseline.COMPRESSION_NONE) {
|
||||
if (compression == TIFFBaseline.COMPRESSION_NONE && interpretation != TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||
// No need for transformation, fast forward
|
||||
input = imageInput;
|
||||
}
|
||||
else {
|
||||
@ -518,11 +573,16 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
|
||||
: IIOUtil.createStreamAdapter(imageInput);
|
||||
|
||||
adapter = createDecoderInputStream(compression, adapter);
|
||||
|
||||
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, colsInTile, yCbCrCoefficients);
|
||||
}
|
||||
|
||||
// According to the spec, short/long/etc should follow order of containing stream
|
||||
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||
? new DataInputStream(createDecoderInputStream(compression, adapter))
|
||||
: new LittleEndianDataInputStream(createDecoderInputStream(compression, adapter));
|
||||
|
||||
? new DataInputStream(adapter)
|
||||
: new LittleEndianDataInputStream(adapter);
|
||||
}
|
||||
|
||||
// Read a full strip/tile
|
||||
@ -566,6 +626,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
// Might have something to do with subsampling?
|
||||
// How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
|
||||
|
||||
// TODO: Consider splicing the TAG_JPEG_TABLES into the streams for each tile, for a more
|
||||
// compatible approach..?
|
||||
|
||||
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
|
||||
|
||||
// NOTE: This initializes the tables AND MORE secret internal settings for the reader (as if by magic).
|
||||
@ -690,6 +753,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
switch (rowRaster.getTransferType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int j = 0; j < rowsInStrip; j++) {
|
||||
int row = startRow + j;
|
||||
|
||||
@ -699,17 +763,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
input.readFully(rowData);
|
||||
|
||||
// for (int k = 0; k < rowData.length; k++) {
|
||||
// try {
|
||||
// rowData[k] = input.readByte();
|
||||
// }
|
||||
// catch (IOException e) {
|
||||
// Arrays.fill(rowData, k, rowData.length, (byte) -1);
|
||||
// System.err.printf("Unexpected EOF or bad data at [%d, %d]\n", col + k, row);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
unPredict(predictor, colsInStrip, 1, numBands, rowData);
|
||||
normalizeBlack(interpretation, rowData);
|
||||
|
||||
@ -725,6 +778,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int j = 0; j < rowsInStrip; j++) {
|
||||
int row = startRow + j;
|
||||
|
||||
@ -751,6 +805,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
break;
|
||||
case DataBuffer.TYPE_INT:
|
||||
int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int j = 0; j < rowsInStrip; j++) {
|
||||
int row = startRow + j;
|
||||
|
||||
@ -861,6 +916,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException {
|
||||
switch (compression) {
|
||||
case TIFFBaseline.COMPRESSION_NONE:
|
||||
return stream;
|
||||
case TIFFBaseline.COMPRESSION_PACKBITS:
|
||||
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
|
||||
case TIFFExtension.COMPRESSION_LZW:
|
||||
@ -894,7 +951,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
short[] shorts = (short[]) entry.getValue();
|
||||
value = new long[shorts.length];
|
||||
|
||||
for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
value[i] = shorts[i];
|
||||
}
|
||||
}
|
||||
@ -902,7 +959,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
int[] ints = (int[]) entry.getValue();
|
||||
value = new long[ints.length];
|
||||
|
||||
for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
value[i] = ints[i];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* 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.
|
||||
* * Redistributions in binary form must reproduce 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* YCbCrUpsamplerStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
||||
*/
|
||||
final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
private final int horizChromaSub;
|
||||
private final int vertChromaSub;
|
||||
private final double[] coefficients;
|
||||
|
||||
private final int units;
|
||||
private final int unitSize;
|
||||
private final byte[] decodedRows;
|
||||
int decodedLength;
|
||||
int decodedPos;
|
||||
|
||||
private final byte[] buffer;
|
||||
int bufferLength;
|
||||
int bufferPos;
|
||||
|
||||
public YCbCrUpsamplerStream(InputStream stream, int[] chromaSub, int cols, double[] coefficients) {
|
||||
super(stream);
|
||||
|
||||
this.horizChromaSub = chromaSub[0];
|
||||
this.vertChromaSub = chromaSub[1];
|
||||
this.coefficients = Arrays.equals(TIFFImageReader.CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients;
|
||||
|
||||
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
|
||||
// For a 4:2 subsampled stream like this:
|
||||
//
|
||||
// Y0 Y1 Y2 Y3 Cb0 Cr0 Y8 Y9 Y10 Y11 Cb1 Cr1
|
||||
// Y4 Y5 Y6 Y7 Y12Y13Y14 Y15
|
||||
//
|
||||
// In the stream, the order is: Y0,Y1,Y2..Y7,Cb0,Cr0, Y8...Y15,Cb1,Cr1, Y16...
|
||||
|
||||
units = cols / horizChromaSub;
|
||||
unitSize = horizChromaSub * vertChromaSub + 2;
|
||||
decodedRows = new byte[cols * vertChromaSub * 3];
|
||||
buffer = new byte[unitSize * units];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return decodedRows[decodedPos++];
|
||||
}
|
||||
|
||||
private void fetch() throws IOException {
|
||||
if (bufferPos >= bufferLength) {
|
||||
int pos = 0;
|
||||
int read;
|
||||
|
||||
// This *SHOULD* read an entire row of units into the buffer, otherwise decodeRows will throw EOFException
|
||||
while (pos < buffer.length && (read = super.read(buffer, pos, buffer.length - pos)) > 0) {
|
||||
pos += read;
|
||||
}
|
||||
|
||||
bufferLength = pos;
|
||||
bufferPos = 0;
|
||||
}
|
||||
|
||||
if (bufferLength > 0) {
|
||||
decodeRows();
|
||||
}
|
||||
else {
|
||||
decodedLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeRows() throws EOFException {
|
||||
decodedLength = decodedRows.length;
|
||||
|
||||
int rowOff = horizChromaSub * units;
|
||||
|
||||
for (int u = 0; u < units; u++) {
|
||||
if (bufferPos >= bufferLength) {
|
||||
throw new EOFException("Unexpected end of stream");
|
||||
}
|
||||
|
||||
// Decode one unit
|
||||
byte cb = buffer[bufferPos + unitSize - 2];
|
||||
byte cr = buffer[bufferPos + unitSize - 1];
|
||||
|
||||
for (int y = 0; y < vertChromaSub; y++) {
|
||||
for (int x = 0; x < horizChromaSub; x++) {
|
||||
int pixelOff = 3 * (rowOff * y + horizChromaSub * u + x);
|
||||
|
||||
decodedRows[pixelOff] = buffer[bufferPos++];
|
||||
decodedRows[pixelOff + 1] = cb;
|
||||
decodedRows[pixelOff + 2] = cr;
|
||||
|
||||
// Convert to RGB
|
||||
if (coefficients == null) {
|
||||
YCbCrConverter.convertYCbCr2RGB(decodedRows, decodedRows, pixelOff);
|
||||
}
|
||||
else {
|
||||
convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufferPos+= 2;
|
||||
}
|
||||
|
||||
bufferPos = bufferLength;
|
||||
decodedPos = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int read = Math.min(decodedLength - decodedPos, len);
|
||||
System.arraycopy(decodedRows, decodedPos, b, off, read);
|
||||
decodedPos += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int skipped = (int) Math.min(decodedLength - decodedPos, n);
|
||||
decodedPos += skipped;
|
||||
|
||||
return skipped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
|
||||
private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||
// TODO: FixMe: This is bogus...
|
||||
double y = yCbCr[offset ] & 0xff;
|
||||
double cb = yCbCr[offset + 1] & 0xff;
|
||||
double cr = yCbCr[offset + 2] & 0xff;
|
||||
|
||||
double lumaRed = coefficients[0];
|
||||
double lumaGreen = coefficients[1];
|
||||
double lumaBlue = coefficients[2];
|
||||
|
||||
rgb[offset ] = clamp((int) Math.round(cr * (2 - 2 * lumaRed) + y));
|
||||
rgb[offset + 2] = clamp((int) Math.round(cb * (2 - 2 * lumaBlue) + y) - 128);
|
||||
rgb[offset + 1] = clamp((int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen));
|
||||
}
|
||||
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
|
||||
// TODO: This code is copied from JPEG package, make it "more" public: com.tm.imageio.color package?
|
||||
/**
|
||||
* Static inner class for lazy-loading of conversion tables.
|
||||
*/
|
||||
static final class YCbCrConverter {
|
||||
/** Define tables for YCC->RGB color space conversion. */
|
||||
private final static int SCALEBITS = 16;
|
||||
private final static int MAXJSAMPLE = 255;
|
||||
private final static int CENTERJSAMPLE = 128;
|
||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (DEBUG) {
|
||||
System.err.println("Building YCC conversion table");
|
||||
}
|
||||
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
// Cr=>R value is nearest int to 1.40200 * x
|
||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 1.77200 * x
|
||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.71414 * x
|
||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.34414 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
|
||||
static void convertYCbCr2RGB(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset ] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
|
||||
rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
static void convertYCCK2CMYK(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
||||
// Inverted
|
||||
int y = 255 - ycck[offset ] & 0xff;
|
||||
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||
int cr = 255 - ycck[offset + 2] & 0xff;
|
||||
int k = 255 - ycck[offset + 3] & 0xff;
|
||||
|
||||
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||
|
||||
cmyk[offset ] = clamp(cmykC);
|
||||
cmyk[offset + 1] = clamp(cmykM);
|
||||
cmyk[offset + 2] = clamp(cmykY);
|
||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||
}
|
||||
|
||||
// private static byte clamp(int val) {
|
||||
// return (byte) Math.max(0, Math.min(255, val));
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* 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.
|
||||
* * Redistributions in binary form must reproduce 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.io.InputStreamAbstractTestCase;
|
||||
import org.junit.Ignore;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* YCbCrUpsamplerStreamTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: YCbCrUpsamplerStreamTest.java,v 1.0 31.01.13 14:35 haraldk Exp$
|
||||
*/
|
||||
@Ignore
|
||||
public class YCbCrUpsamplerStreamTest extends InputStreamAbstractTestCase {
|
||||
// TODO: Implement
|
||||
@Override
|
||||
protected InputStream makeInputStream(byte[] pBytes) {
|
||||
return new YCbCrUpsamplerStream(new ByteArrayInputStream(pBytes), new int[] {2, 2}, pBytes.length / 4, null);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user