1 /*
2  * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package com.sun.imageio.plugins.tiff;
26 
27 import java.awt.image.BufferedImage;
28 import java.awt.image.ColorModel;
29 import java.io.ByteArrayInputStream;
30 import java.io.EOFException;
31 import java.io.IOException;
32 import javax.imageio.ImageReader;
33 import javax.imageio.metadata.IIOMetadata;
34 import javax.imageio.stream.MemoryCacheImageInputStream;
35 import javax.imageio.stream.ImageInputStream;
36 import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
37 import javax.imageio.plugins.tiff.TIFFField;
38 
39 public class TIFFYCbCrDecompressor extends TIFFDecompressor {
40     // Store constants in S15.16 format
41     private static final int FRAC_BITS = 16;
42     private static final float FRAC_SCALE = (float)(1 << FRAC_BITS);
43 
44     private float lumaRed = 0.299f;
45     private float lumaGreen = 0.587f;
46     private float lumaBlue = 0.114f;
47 
48     private float referenceBlackY = 0.0f;
49     private float referenceWhiteY = 255.0f;
50 
51     private float referenceBlackCb = 128.0f;
52     private float referenceWhiteCb = 255.0f;
53 
54     private float referenceBlackCr = 128.0f;
55     private float referenceWhiteCr = 255.0f;
56 
57     private float codingRangeY = 255.0f;
58 
59     private int[] iYTab = new int[256];
60     private int[] iCbTab = new int[256];
61     private int[] iCrTab = new int[256];
62 
63     private int[] iGYTab = new int[256];
64     private int[] iGCbTab = new int[256];
65     private int[] iGCrTab = new int[256];
66 
67     private int chromaSubsampleH = 2;
68     private int chromaSubsampleV = 2;
69 
70     private boolean colorConvert;
71 
72     private TIFFDecompressor decompressor;
73 
74     private BufferedImage tmpImage;
75 
76     //
77     // If 'decompressor' is not null then it reads the data from the
78     // actual stream first and passes the result on to YCrCr decompression
79     // and possibly color conversion.
80     //
81 
TIFFYCbCrDecompressor(TIFFDecompressor decompressor, boolean colorConvert)82     public TIFFYCbCrDecompressor(TIFFDecompressor decompressor,
83                                  boolean colorConvert) {
84         this.decompressor = decompressor;
85         this.colorConvert = colorConvert;
86     }
87 
warning(String message)88     private void warning(String message) {
89         if(this.reader instanceof TIFFImageReader) {
90             ((TIFFImageReader)reader).forwardWarningMessage(message);
91         }
92     }
93 
94     //
95     // "Chained" decompressor methods.
96     //
97 
setReader(ImageReader reader)98     public void setReader(ImageReader reader) {
99         if(decompressor != null) {
100             decompressor.setReader(reader);
101         }
102         super.setReader(reader);
103     }
104 
setMetadata(IIOMetadata metadata)105     public void setMetadata(IIOMetadata metadata) {
106         if(decompressor != null) {
107             decompressor.setMetadata(metadata);
108         }
109         super.setMetadata(metadata);
110     }
111 
setPhotometricInterpretation(int photometricInterpretation)112     public void setPhotometricInterpretation(int photometricInterpretation) {
113         if(decompressor != null) {
114             decompressor.setPhotometricInterpretation(photometricInterpretation);
115         }
116         super.setPhotometricInterpretation(photometricInterpretation);
117     }
118 
setCompression(int compression)119     public void setCompression(int compression) {
120         if(decompressor != null) {
121             decompressor.setCompression(compression);
122         }
123         super.setCompression(compression);
124     }
125 
setPlanar(boolean planar)126     public void setPlanar(boolean planar) {
127         if(decompressor != null) {
128             decompressor.setPlanar(planar);
129         }
130         super.setPlanar(planar);
131     }
132 
setSamplesPerPixel(int samplesPerPixel)133     public void setSamplesPerPixel(int samplesPerPixel) {
134         if(decompressor != null) {
135             decompressor.setSamplesPerPixel(samplesPerPixel);
136         }
137         super.setSamplesPerPixel(samplesPerPixel);
138     }
139 
setBitsPerSample(int[] bitsPerSample)140     public void setBitsPerSample(int[] bitsPerSample) {
141         if(decompressor != null) {
142             decompressor.setBitsPerSample(bitsPerSample);
143         }
144         super.setBitsPerSample(bitsPerSample);
145     }
146 
setSampleFormat(int[] sampleFormat)147     public void setSampleFormat(int[] sampleFormat) {
148         if(decompressor != null) {
149             decompressor.setSampleFormat(sampleFormat);
150         }
151         super.setSampleFormat(sampleFormat);
152     }
153 
setExtraSamples(int[] extraSamples)154     public void setExtraSamples(int[] extraSamples) {
155         if(decompressor != null) {
156             decompressor.setExtraSamples(extraSamples);
157         }
158         super.setExtraSamples(extraSamples);
159     }
160 
setColorMap(char[] colorMap)161     public void setColorMap(char[] colorMap) {
162         if(decompressor != null) {
163             decompressor.setColorMap(colorMap);
164         }
165         super.setColorMap(colorMap);
166     }
167 
setStream(ImageInputStream stream)168     public void setStream(ImageInputStream stream) {
169         if(decompressor != null) {
170             decompressor.setStream(stream);
171         } else {
172             super.setStream(stream);
173         }
174     }
175 
setOffset(long offset)176     public void setOffset(long offset) {
177         if(decompressor != null) {
178             decompressor.setOffset(offset);
179         }
180         super.setOffset(offset);
181     }
182 
setByteCount(int byteCount)183     public void setByteCount(int byteCount) {
184         if(decompressor != null) {
185             decompressor.setByteCount(byteCount);
186         }
187         super.setByteCount(byteCount);
188     }
189 
setSrcMinX(int srcMinX)190     public void setSrcMinX(int srcMinX) {
191         if(decompressor != null) {
192             decompressor.setSrcMinX(srcMinX);
193         }
194         super.setSrcMinX(srcMinX);
195     }
196 
setSrcMinY(int srcMinY)197     public void setSrcMinY(int srcMinY) {
198         if(decompressor != null) {
199             decompressor.setSrcMinY(srcMinY);
200         }
201         super.setSrcMinY(srcMinY);
202     }
203 
setSrcWidth(int srcWidth)204     public void setSrcWidth(int srcWidth) {
205         if(decompressor != null) {
206             decompressor.setSrcWidth(srcWidth);
207         }
208         super.setSrcWidth(srcWidth);
209     }
210 
setSrcHeight(int srcHeight)211     public void setSrcHeight(int srcHeight) {
212         if(decompressor != null) {
213             decompressor.setSrcHeight(srcHeight);
214         }
215         super.setSrcHeight(srcHeight);
216     }
217 
setSourceXOffset(int sourceXOffset)218     public void setSourceXOffset(int sourceXOffset) {
219         if(decompressor != null) {
220             decompressor.setSourceXOffset(sourceXOffset);
221         }
222         super.setSourceXOffset(sourceXOffset);
223     }
224 
setDstXOffset(int dstXOffset)225     public void setDstXOffset(int dstXOffset) {
226         if(decompressor != null) {
227             decompressor.setDstXOffset(dstXOffset);
228         }
229         super.setDstXOffset(dstXOffset);
230     }
231 
setSourceYOffset(int sourceYOffset)232     public void setSourceYOffset(int sourceYOffset) {
233         if(decompressor != null) {
234             decompressor.setSourceYOffset(sourceYOffset);
235         }
236         super.setSourceYOffset(sourceYOffset);
237     }
238 
setDstYOffset(int dstYOffset)239     public void setDstYOffset(int dstYOffset) {
240         if(decompressor != null) {
241             decompressor.setDstYOffset(dstYOffset);
242         }
243         super.setDstYOffset(dstYOffset);
244     }
245 
246     /* Should not need to override these mutators as subsampling
247        should not be done by the wrapped decompressor.
248     public void setSubsampleX(int subsampleX) {
249         if(decompressor != null) {
250             decompressor.setSubsampleX(subsampleX);
251         }
252         super.setSubsampleX(subsampleX);
253     }
254 
255     public void setSubsampleY(int subsampleY) {
256         if(decompressor != null) {
257             decompressor.setSubsampleY(subsampleY);
258         }
259         super.setSubsampleY(subsampleY);
260     }
261     */
262 
setSourceBands(int[] sourceBands)263     public void setSourceBands(int[] sourceBands) {
264         if(decompressor != null) {
265             decompressor.setSourceBands(sourceBands);
266         }
267         super.setSourceBands(sourceBands);
268     }
269 
setDestinationBands(int[] destinationBands)270     public void setDestinationBands(int[] destinationBands) {
271         if(decompressor != null) {
272             decompressor.setDestinationBands(destinationBands);
273         }
274         super.setDestinationBands(destinationBands);
275     }
276 
setImage(BufferedImage image)277     public void setImage(BufferedImage image) {
278         if(decompressor != null) {
279             ColorModel cm = image.getColorModel();
280             tmpImage =
281                 new BufferedImage(cm,
282                                   image.getRaster().createCompatibleWritableRaster(1, 1),
283                                   cm.isAlphaPremultiplied(),
284                                   null);
285             decompressor.setImage(tmpImage);
286         }
287         super.setImage(image);
288     }
289 
setDstMinX(int dstMinX)290     public void setDstMinX(int dstMinX) {
291         if(decompressor != null) {
292             decompressor.setDstMinX(dstMinX);
293         }
294         super.setDstMinX(dstMinX);
295     }
296 
setDstMinY(int dstMinY)297     public void setDstMinY(int dstMinY) {
298         if(decompressor != null) {
299             decompressor.setDstMinY(dstMinY);
300         }
301         super.setDstMinY(dstMinY);
302     }
303 
setDstWidth(int dstWidth)304     public void setDstWidth(int dstWidth) {
305         if(decompressor != null) {
306             decompressor.setDstWidth(dstWidth);
307         }
308         super.setDstWidth(dstWidth);
309     }
310 
setDstHeight(int dstHeight)311     public void setDstHeight(int dstHeight) {
312         if(decompressor != null) {
313             decompressor.setDstHeight(dstHeight);
314         }
315         super.setDstHeight(dstHeight);
316     }
317 
setActiveSrcMinX(int activeSrcMinX)318     public void setActiveSrcMinX(int activeSrcMinX) {
319         if(decompressor != null) {
320             decompressor.setActiveSrcMinX(activeSrcMinX);
321         }
322         super.setActiveSrcMinX(activeSrcMinX);
323     }
324 
setActiveSrcMinY(int activeSrcMinY)325     public void setActiveSrcMinY(int activeSrcMinY) {
326         if(decompressor != null) {
327             decompressor.setActiveSrcMinY(activeSrcMinY);
328         }
329         super.setActiveSrcMinY(activeSrcMinY);
330     }
331 
setActiveSrcWidth(int activeSrcWidth)332     public void setActiveSrcWidth(int activeSrcWidth) {
333         if(decompressor != null) {
334             decompressor.setActiveSrcWidth(activeSrcWidth);
335         }
336         super.setActiveSrcWidth(activeSrcWidth);
337     }
338 
setActiveSrcHeight(int activeSrcHeight)339     public void setActiveSrcHeight(int activeSrcHeight) {
340         if(decompressor != null) {
341             decompressor.setActiveSrcHeight(activeSrcHeight);
342         }
343         super.setActiveSrcHeight(activeSrcHeight);
344     }
345 
clamp(int f)346     private byte clamp(int f) {
347         if (f < 0) {
348             return (byte)0;
349         } else if (f > 255*65536) {
350             return (byte)255;
351         } else {
352             return (byte)(f >> 16);
353         }
354     }
355 
beginDecoding()356     public void beginDecoding() {
357         if(decompressor != null) {
358             decompressor.beginDecoding();
359         }
360 
361         TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata;
362         TIFFField f;
363 
364         f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
365         if (f != null) {
366             if (f.getCount() == 2) {
367                 this.chromaSubsampleH = f.getAsInt(0);
368                 this.chromaSubsampleV = f.getAsInt(1);
369 
370                 if (chromaSubsampleH != 1 && chromaSubsampleH != 2 &&
371                     chromaSubsampleH != 4) {
372                     warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " +
373                             chromaSubsampleH +
374                             " (should be 1, 2, or 4), setting to 1");
375                     chromaSubsampleH = 1;
376                 }
377 
378                 if (chromaSubsampleV != 1 && chromaSubsampleV != 2 &&
379                     chromaSubsampleV != 4) {
380                     warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " +
381                             chromaSubsampleV +
382                             " (should be 1, 2, or 4), setting to 1");
383                     chromaSubsampleV = 1;
384                 }
385             } else {
386                 warning("Y_CB_CR_SUBSAMPLING count != 2, " +
387                         "assuming no subsampling");
388             }
389         }
390 
391         f =
392            tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
393         if (f != null) {
394             if (f.getCount() == 3) {
395                 this.lumaRed = f.getAsFloat(0);
396                 this.lumaGreen = f.getAsFloat(1);
397                 this.lumaBlue = f.getAsFloat(2);
398             } else {
399                 warning("Y_CB_CR_COEFFICIENTS count != 3, " +
400                         "assuming default values for CCIR 601-1");
401             }
402         }
403 
404         f =
405           tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
406         if (f != null) {
407             if (f.getCount() == 6) {
408                 this.referenceBlackY = f.getAsFloat(0);
409                 this.referenceWhiteY = f.getAsFloat(1);
410                 this.referenceBlackCb = f.getAsFloat(2);
411                 this.referenceWhiteCb = f.getAsFloat(3);
412                 this.referenceBlackCr = f.getAsFloat(4);
413                 this.referenceWhiteCr = f.getAsFloat(5);
414             } else {
415                 warning("REFERENCE_BLACK_WHITE count != 6, ignoring it");
416             }
417         } else {
418                 warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255");
419         }
420 
421         this.colorConvert = true;
422 
423         float BCb = (2.0f - 2.0f*lumaBlue);
424         float RCr = (2.0f - 2.0f*lumaRed);
425 
426         float GY = (1.0f - lumaBlue - lumaRed)/lumaGreen;
427         float GCb = 2.0f*lumaBlue*(lumaBlue - 1.0f)/lumaGreen;
428         float GCr = 2.0f*lumaRed*(lumaRed - 1.0f)/lumaGreen;
429 
430         for (int i = 0; i < 256; i++) {
431             float fY = (i - referenceBlackY)*codingRangeY/
432                 (referenceWhiteY - referenceBlackY);
433             float fCb = (i - referenceBlackCb)*127.0f/
434                 (referenceWhiteCb - referenceBlackCb);
435             float fCr = (i - referenceBlackCr)*127.0f/
436                 (referenceWhiteCr - referenceBlackCr);
437 
438             iYTab[i] = (int)(fY*FRAC_SCALE);
439             iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE);
440             iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE);
441 
442             iGYTab[i] = (int)(fY*GY*FRAC_SCALE);
443             iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE);
444             iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE);
445         }
446     }
447 
decodeRaw(byte[] buf, int dstOffset, int bitsPerPixel, int scanlineStride)448     public void decodeRaw(byte[] buf,
449                           int dstOffset,
450                           int bitsPerPixel,
451                           int scanlineStride) throws IOException {
452         int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2;
453         byte[] packet = new byte[elementsPerPacket];
454 
455         if(decompressor != null) {
456             int bytesPerRow = 3*srcWidth;
457             byte[] tmpBuf = new byte[bytesPerRow*srcHeight];
458             decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel,
459                                    bytesPerRow);
460             ByteArrayInputStream byteStream =
461                 new ByteArrayInputStream(tmpBuf);
462             stream = new MemoryCacheImageInputStream(byteStream);
463         } else {
464             stream.seek(offset);
465         }
466 
467         for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) {
468             // Decode chromaSubsampleV rows
469             for (int x = srcMinX; x < srcMinX + srcWidth;
470                  x += chromaSubsampleH) {
471                 try {
472                     stream.readFully(packet);
473                 } catch (EOFException e) {
474                     return;
475                 }
476 
477                 byte Cb = packet[elementsPerPacket - 2];
478                 byte Cr = packet[elementsPerPacket - 1];
479 
480                 int iCb  = 0, iCr = 0, iGCb = 0, iGCr = 0;
481 
482                 if (colorConvert) {
483                     int Cbp = Cb & 0xff;
484                     int Crp = Cr & 0xff;
485 
486                     iCb = iCbTab[Cbp];
487                     iCr = iCrTab[Crp];
488 
489                     iGCb = iGCbTab[Cbp];
490                     iGCr = iGCrTab[Crp];
491                 }
492 
493                 int yIndex = 0;
494                 for (int v = 0; v < chromaSubsampleV; v++) {
495                     int idx = dstOffset + 3*(x - srcMinX) +
496                         scanlineStride*(y - srcMinY + v);
497 
498                     // Check if we reached the last scanline
499                     if (y + v >= srcMinY + srcHeight) {
500                         break;
501                     }
502 
503                     for (int h = 0; h < chromaSubsampleH; h++) {
504                         if (x + h >= srcMinX + srcWidth) {
505                             break;
506                         }
507 
508                         byte Y = packet[yIndex++];
509 
510                         if (colorConvert) {
511                             int Yp = Y & 0xff;
512                             int iY = iYTab[Yp];
513                             int iGY = iGYTab[Yp];
514 
515                             int iR = iY + iCr;
516                             int iG = iGY + iGCb + iGCr;
517                             int iB = iY + iCb;
518 
519                             byte r = clamp(iR);
520                             byte g = clamp(iG);
521                             byte b = clamp(iB);
522 
523                             buf[idx] = r;
524                             buf[idx + 1] = g;
525                             buf[idx + 2] = b;
526                         } else {
527                             buf[idx] = Y;
528                             buf[idx + 1] = Cb;
529                             buf[idx + 2] = Cr;
530                         }
531 
532                         idx += 3;
533                     }
534                 }
535             }
536         }
537     }
538 }
539