/* * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.imageio.plugins.tiff; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import javax.imageio.ImageReader; import javax.imageio.metadata.IIOMetadata; import javax.imageio.stream.MemoryCacheImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.plugins.tiff.BaselineTIFFTagSet; import javax.imageio.plugins.tiff.TIFFField; public class TIFFYCbCrDecompressor extends TIFFDecompressor { // Store constants in S15.16 format private static final int FRAC_BITS = 16; private static final float FRAC_SCALE = (float)(1 << FRAC_BITS); private float lumaRed = 0.299f; private float lumaGreen = 0.587f; private float lumaBlue = 0.114f; private float referenceBlackY = 0.0f; private float referenceWhiteY = 255.0f; private float referenceBlackCb = 128.0f; private float referenceWhiteCb = 255.0f; private float referenceBlackCr = 128.0f; private float referenceWhiteCr = 255.0f; private float codingRangeY = 255.0f; private int[] iYTab = new int[256]; private int[] iCbTab = new int[256]; private int[] iCrTab = new int[256]; private int[] iGYTab = new int[256]; private int[] iGCbTab = new int[256]; private int[] iGCrTab = new int[256]; private int chromaSubsampleH = 2; private int chromaSubsampleV = 2; private boolean colorConvert; private TIFFDecompressor decompressor; private BufferedImage tmpImage; // // If 'decompressor' is not null then it reads the data from the // actual stream first and passes the result on to YCrCr decompression // and possibly color conversion. // public TIFFYCbCrDecompressor(TIFFDecompressor decompressor, boolean colorConvert) { this.decompressor = decompressor; this.colorConvert = colorConvert; } private void warning(String message) { if(this.reader instanceof TIFFImageReader) { ((TIFFImageReader)reader).forwardWarningMessage(message); } } // // "Chained" decompressor methods. // public void setReader(ImageReader reader) { if(decompressor != null) { decompressor.setReader(reader); } super.setReader(reader); } public void setMetadata(IIOMetadata metadata) { if(decompressor != null) { decompressor.setMetadata(metadata); } super.setMetadata(metadata); } public void setPhotometricInterpretation(int photometricInterpretation) { if(decompressor != null) { decompressor.setPhotometricInterpretation(photometricInterpretation); } super.setPhotometricInterpretation(photometricInterpretation); } public void setCompression(int compression) { if(decompressor != null) { decompressor.setCompression(compression); } super.setCompression(compression); } public void setPlanar(boolean planar) { if(decompressor != null) { decompressor.setPlanar(planar); } super.setPlanar(planar); } public void setSamplesPerPixel(int samplesPerPixel) { if(decompressor != null) { decompressor.setSamplesPerPixel(samplesPerPixel); } super.setSamplesPerPixel(samplesPerPixel); } public void setBitsPerSample(int[] bitsPerSample) { if(decompressor != null) { decompressor.setBitsPerSample(bitsPerSample); } super.setBitsPerSample(bitsPerSample); } public void setSampleFormat(int[] sampleFormat) { if(decompressor != null) { decompressor.setSampleFormat(sampleFormat); } super.setSampleFormat(sampleFormat); } public void setExtraSamples(int[] extraSamples) { if(decompressor != null) { decompressor.setExtraSamples(extraSamples); } super.setExtraSamples(extraSamples); } public void setColorMap(char[] colorMap) { if(decompressor != null) { decompressor.setColorMap(colorMap); } super.setColorMap(colorMap); } public void setStream(ImageInputStream stream) { if(decompressor != null) { decompressor.setStream(stream); } else { super.setStream(stream); } } public void setOffset(long offset) { if(decompressor != null) { decompressor.setOffset(offset); } super.setOffset(offset); } public void setByteCount(int byteCount) { if(decompressor != null) { decompressor.setByteCount(byteCount); } super.setByteCount(byteCount); } public void setSrcMinX(int srcMinX) { if(decompressor != null) { decompressor.setSrcMinX(srcMinX); } super.setSrcMinX(srcMinX); } public void setSrcMinY(int srcMinY) { if(decompressor != null) { decompressor.setSrcMinY(srcMinY); } super.setSrcMinY(srcMinY); } public void setSrcWidth(int srcWidth) { if(decompressor != null) { decompressor.setSrcWidth(srcWidth); } super.setSrcWidth(srcWidth); } public void setSrcHeight(int srcHeight) { if(decompressor != null) { decompressor.setSrcHeight(srcHeight); } super.setSrcHeight(srcHeight); } public void setSourceXOffset(int sourceXOffset) { if(decompressor != null) { decompressor.setSourceXOffset(sourceXOffset); } super.setSourceXOffset(sourceXOffset); } public void setDstXOffset(int dstXOffset) { if(decompressor != null) { decompressor.setDstXOffset(dstXOffset); } super.setDstXOffset(dstXOffset); } public void setSourceYOffset(int sourceYOffset) { if(decompressor != null) { decompressor.setSourceYOffset(sourceYOffset); } super.setSourceYOffset(sourceYOffset); } public void setDstYOffset(int dstYOffset) { if(decompressor != null) { decompressor.setDstYOffset(dstYOffset); } super.setDstYOffset(dstYOffset); } /* Should not need to override these mutators as subsampling should not be done by the wrapped decompressor. public void setSubsampleX(int subsampleX) { if(decompressor != null) { decompressor.setSubsampleX(subsampleX); } super.setSubsampleX(subsampleX); } public void setSubsampleY(int subsampleY) { if(decompressor != null) { decompressor.setSubsampleY(subsampleY); } super.setSubsampleY(subsampleY); } */ public void setSourceBands(int[] sourceBands) { if(decompressor != null) { decompressor.setSourceBands(sourceBands); } super.setSourceBands(sourceBands); } public void setDestinationBands(int[] destinationBands) { if(decompressor != null) { decompressor.setDestinationBands(destinationBands); } super.setDestinationBands(destinationBands); } public void setImage(BufferedImage image) { if(decompressor != null) { ColorModel cm = image.getColorModel(); tmpImage = new BufferedImage(cm, image.getRaster().createCompatibleWritableRaster(1, 1), cm.isAlphaPremultiplied(), null); decompressor.setImage(tmpImage); } super.setImage(image); } public void setDstMinX(int dstMinX) { if(decompressor != null) { decompressor.setDstMinX(dstMinX); } super.setDstMinX(dstMinX); } public void setDstMinY(int dstMinY) { if(decompressor != null) { decompressor.setDstMinY(dstMinY); } super.setDstMinY(dstMinY); } public void setDstWidth(int dstWidth) { if(decompressor != null) { decompressor.setDstWidth(dstWidth); } super.setDstWidth(dstWidth); } public void setDstHeight(int dstHeight) { if(decompressor != null) { decompressor.setDstHeight(dstHeight); } super.setDstHeight(dstHeight); } public void setActiveSrcMinX(int activeSrcMinX) { if(decompressor != null) { decompressor.setActiveSrcMinX(activeSrcMinX); } super.setActiveSrcMinX(activeSrcMinX); } public void setActiveSrcMinY(int activeSrcMinY) { if(decompressor != null) { decompressor.setActiveSrcMinY(activeSrcMinY); } super.setActiveSrcMinY(activeSrcMinY); } public void setActiveSrcWidth(int activeSrcWidth) { if(decompressor != null) { decompressor.setActiveSrcWidth(activeSrcWidth); } super.setActiveSrcWidth(activeSrcWidth); } public void setActiveSrcHeight(int activeSrcHeight) { if(decompressor != null) { decompressor.setActiveSrcHeight(activeSrcHeight); } super.setActiveSrcHeight(activeSrcHeight); } private byte clamp(int f) { if (f < 0) { return (byte)0; } else if (f > 255*65536) { return (byte)255; } else { return (byte)(f >> 16); } } public void beginDecoding() { if(decompressor != null) { decompressor.beginDecoding(); } TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata; TIFFField f; f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); if (f != null) { if (f.getCount() == 2) { this.chromaSubsampleH = f.getAsInt(0); this.chromaSubsampleV = f.getAsInt(1); if (chromaSubsampleH != 1 && chromaSubsampleH != 2 && chromaSubsampleH != 4) { warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " + chromaSubsampleH + " (should be 1, 2, or 4), setting to 1"); chromaSubsampleH = 1; } if (chromaSubsampleV != 1 && chromaSubsampleV != 2 && chromaSubsampleV != 4) { warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " + chromaSubsampleV + " (should be 1, 2, or 4), setting to 1"); chromaSubsampleV = 1; } } else { warning("Y_CB_CR_SUBSAMPLING count != 2, " + "assuming no subsampling"); } } f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS); if (f != null) { if (f.getCount() == 3) { this.lumaRed = f.getAsFloat(0); this.lumaGreen = f.getAsFloat(1); this.lumaBlue = f.getAsFloat(2); } else { warning("Y_CB_CR_COEFFICIENTS count != 3, " + "assuming default values for CCIR 601-1"); } } f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE); if (f != null) { if (f.getCount() == 6) { this.referenceBlackY = f.getAsFloat(0); this.referenceWhiteY = f.getAsFloat(1); this.referenceBlackCb = f.getAsFloat(2); this.referenceWhiteCb = f.getAsFloat(3); this.referenceBlackCr = f.getAsFloat(4); this.referenceWhiteCr = f.getAsFloat(5); } else { warning("REFERENCE_BLACK_WHITE count != 6, ignoring it"); } } else { warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255"); } this.colorConvert = true; float BCb = (2.0f - 2.0f*lumaBlue); float RCr = (2.0f - 2.0f*lumaRed); float GY = (1.0f - lumaBlue - lumaRed)/lumaGreen; float GCb = 2.0f*lumaBlue*(lumaBlue - 1.0f)/lumaGreen; float GCr = 2.0f*lumaRed*(lumaRed - 1.0f)/lumaGreen; for (int i = 0; i < 256; i++) { float fY = (i - referenceBlackY)*codingRangeY/ (referenceWhiteY - referenceBlackY); float fCb = (i - referenceBlackCb)*127.0f/ (referenceWhiteCb - referenceBlackCb); float fCr = (i - referenceBlackCr)*127.0f/ (referenceWhiteCr - referenceBlackCr); iYTab[i] = (int)(fY*FRAC_SCALE); iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE); iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE); iGYTab[i] = (int)(fY*GY*FRAC_SCALE); iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE); iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE); } } public void decodeRaw(byte[] buf, int dstOffset, int bitsPerPixel, int scanlineStride) throws IOException { int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2; byte[] packet = new byte[elementsPerPacket]; if(decompressor != null) { int bytesPerRow = 3*srcWidth; byte[] tmpBuf = new byte[bytesPerRow*srcHeight]; decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel, bytesPerRow); ByteArrayInputStream byteStream = new ByteArrayInputStream(tmpBuf); stream = new MemoryCacheImageInputStream(byteStream); } else { stream.seek(offset); } for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) { // Decode chromaSubsampleV rows for (int x = srcMinX; x < srcMinX + srcWidth; x += chromaSubsampleH) { try { stream.readFully(packet); } catch (EOFException e) { return; } byte Cb = packet[elementsPerPacket - 2]; byte Cr = packet[elementsPerPacket - 1]; int iCb = 0, iCr = 0, iGCb = 0, iGCr = 0; if (colorConvert) { int Cbp = Cb & 0xff; int Crp = Cr & 0xff; iCb = iCbTab[Cbp]; iCr = iCrTab[Crp]; iGCb = iGCbTab[Cbp]; iGCr = iGCrTab[Crp]; } int yIndex = 0; for (int v = 0; v < chromaSubsampleV; v++) { int idx = dstOffset + 3*(x - srcMinX) + scanlineStride*(y - srcMinY + v); // Check if we reached the last scanline if (y + v >= srcMinY + srcHeight) { break; } for (int h = 0; h < chromaSubsampleH; h++) { if (x + h >= srcMinX + srcWidth) { break; } byte Y = packet[yIndex++]; if (colorConvert) { int Yp = Y & 0xff; int iY = iYTab[Yp]; int iGY = iGYTab[Yp]; int iR = iY + iCr; int iG = iGY + iGCb + iGCr; int iB = iY + iCb; byte r = clamp(iR); byte g = clamp(iG); byte b = clamp(iB); buf[idx] = r; buf[idx + 1] = g; buf[idx + 2] = b; } else { buf[idx] = Y; buf[idx + 1] = Cb; buf[idx + 2] = Cr; } idx += 3; } } } } } }