001/*
002 * $RCSfile: TIFFYCbCrDecompressor.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.3 $
042 * $Date: 2006/06/23 19:48:28 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.tiff;
046
047import java.awt.Rectangle;
048import java.awt.image.BufferedImage;
049import java.awt.image.ColorModel;
050import java.awt.image.DataBufferByte;
051import java.io.ByteArrayInputStream;
052import java.io.EOFException;
053import java.io.IOException;
054
055import javax.imageio.ImageReader;
056import javax.imageio.metadata.IIOMetadata;
057import javax.imageio.stream.MemoryCacheImageInputStream;
058import javax.imageio.stream.ImageInputStream;
059
060import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
061import com.github.jaiimageio.plugins.tiff.TIFFDecompressor;
062import com.github.jaiimageio.plugins.tiff.TIFFField;
063
064public class TIFFYCbCrDecompressor extends TIFFDecompressor {
065
066    private static final boolean debug = false;
067
068    // Store constants in S15.16 format 
069    private static final int FRAC_BITS = 16;
070    private static final float FRAC_SCALE = (float)(1 << FRAC_BITS);
071
072    private float LumaRed = 0.299f;
073    private float LumaGreen = 0.587f;
074    private float LumaBlue = 0.114f;
075
076    private float referenceBlackY = 0.0f;
077    private float referenceWhiteY = 255.0f;
078
079    private float referenceBlackCb = 128.0f;
080    private float referenceWhiteCb = 255.0f;
081
082    private float referenceBlackCr = 128.0f;
083    private float referenceWhiteCr = 255.0f;
084
085    private float codingRangeY = 255.0f;
086
087    private int[] iYTab = new int[256];
088    private int[] iCbTab = new int[256];
089    private int[] iCrTab = new int[256];
090
091    private int[] iGYTab = new int[256];
092    private int[] iGCbTab = new int[256];
093    private int[] iGCrTab = new int[256];
094
095    private int chromaSubsampleH = 2;
096    private int chromaSubsampleV = 2;
097
098    private boolean colorConvert;
099
100    private TIFFDecompressor decompressor;
101
102    private BufferedImage tmpImage;
103
104    //
105    // If 'decompressor' is not null then it reads the data from the
106    // actual stream first and passes the result on to YCrCr decompression
107    // and possibly color conversion.
108    //
109
110    public TIFFYCbCrDecompressor(TIFFDecompressor decompressor,
111                                 boolean colorConvert) {
112        this.decompressor = decompressor;
113        this.colorConvert = colorConvert;
114    }
115
116    private void warning(String message) {
117        if(this.reader instanceof TIFFImageReader) {
118            ((TIFFImageReader)reader).forwardWarningMessage(message);
119        }
120    }
121
122    //
123    // "Chained" decompressor methods.
124    //
125
126    public void setReader(ImageReader reader) {
127        if(decompressor != null) {
128            decompressor.setReader(reader);
129        }
130        super.setReader(reader);
131    }
132
133    public void setMetadata(IIOMetadata metadata) {
134        if(decompressor != null) {
135            decompressor.setMetadata(metadata);
136        }
137        super.setMetadata(metadata);
138    }
139
140    public void setPhotometricInterpretation(int photometricInterpretation) {
141        if(decompressor != null) {
142            decompressor.setPhotometricInterpretation(photometricInterpretation);
143        }
144        super.setPhotometricInterpretation(photometricInterpretation);
145    }
146
147    public void setCompression(int compression) {
148        if(decompressor != null) {
149            decompressor.setCompression(compression);
150        }
151        super.setCompression(compression);
152    }
153
154    public void setPlanar(boolean planar) {
155        if(decompressor != null) {
156            decompressor.setPlanar(planar);
157        }
158        super.setPlanar(planar);
159    }
160
161    public void setSamplesPerPixel(int samplesPerPixel) {
162        if(decompressor != null) {
163            decompressor.setSamplesPerPixel(samplesPerPixel);
164        }
165        super.setSamplesPerPixel(samplesPerPixel);
166    }
167
168    public void setBitsPerSample(int[] bitsPerSample) {
169        if(decompressor != null) {
170            decompressor.setBitsPerSample(bitsPerSample);
171        }
172        super.setBitsPerSample(bitsPerSample);
173    }
174
175    public void setSampleFormat(int[] sampleFormat) {
176        if(decompressor != null) {
177            decompressor.setSampleFormat(sampleFormat);
178        }
179        super.setSampleFormat(sampleFormat);
180    }
181
182    public void setExtraSamples(int[] extraSamples) {
183        if(decompressor != null) {
184            decompressor.setExtraSamples(extraSamples);
185        }
186        super.setExtraSamples(extraSamples);
187    }
188    
189    public void setColorMap(char[] colorMap) {
190        if(decompressor != null) {
191            decompressor.setColorMap(colorMap);
192        }
193        super.setColorMap(colorMap);
194    }
195
196    public void setStream(ImageInputStream stream) {
197        if(decompressor != null) {
198            decompressor.setStream(stream);
199        } else {
200            super.setStream(stream);
201        }
202    }
203
204    public void setOffset(long offset) {
205        if(decompressor != null) {
206            decompressor.setOffset(offset);
207        }
208        super.setOffset(offset);
209    }
210
211    public void setByteCount(int byteCount) {
212        if(decompressor != null) {
213            decompressor.setByteCount(byteCount);
214        }
215        super.setByteCount(byteCount);
216    }
217
218    public void setSrcMinX(int srcMinX) {
219        if(decompressor != null) {
220            decompressor.setSrcMinX(srcMinX);
221        }
222        super.setSrcMinX(srcMinX);
223    }
224    
225    public void setSrcMinY(int srcMinY) {
226        if(decompressor != null) {
227            decompressor.setSrcMinY(srcMinY);
228        }
229        super.setSrcMinY(srcMinY);
230    }
231    
232    public void setSrcWidth(int srcWidth) {
233        if(decompressor != null) {
234            decompressor.setSrcWidth(srcWidth);
235        }
236        super.setSrcWidth(srcWidth);
237    }
238    
239    public void setSrcHeight(int srcHeight) {
240        if(decompressor != null) {
241            decompressor.setSrcHeight(srcHeight);
242        }
243        super.setSrcHeight(srcHeight);
244    }
245
246    public void setSourceXOffset(int sourceXOffset) {
247        if(decompressor != null) {
248            decompressor.setSourceXOffset(sourceXOffset);
249        }
250        super.setSourceXOffset(sourceXOffset);
251    }
252
253    public void setDstXOffset(int dstXOffset) {
254        if(decompressor != null) {
255            decompressor.setDstXOffset(dstXOffset);
256        }
257        super.setDstXOffset(dstXOffset);
258    }
259
260    public void setSourceYOffset(int sourceYOffset) {
261        if(decompressor != null) {
262            decompressor.setSourceYOffset(sourceYOffset);
263        }
264        super.setSourceYOffset(sourceYOffset);
265    }
266
267    public void setDstYOffset(int dstYOffset) {
268        if(decompressor != null) {
269            decompressor.setDstYOffset(dstYOffset);
270        }
271        super.setDstYOffset(dstYOffset);
272    }
273
274    /* Should not need to override these mutators as subsampling
275       should not be done by the wrapped decompressor.
276    public void setSubsampleX(int subsampleX) {
277        if(decompressor != null) {
278            decompressor.setSubsampleX(subsampleX);
279        }
280        super.setSubsampleX(subsampleX);
281    }
282    
283    public void setSubsampleY(int subsampleY) {
284        if(decompressor != null) {
285            decompressor.setSubsampleY(subsampleY);
286        }
287        super.setSubsampleY(subsampleY);
288    }
289    */
290    
291    public void setSourceBands(int[] sourceBands) {
292        if(decompressor != null) {
293            decompressor.setSourceBands(sourceBands);
294        }
295        super.setSourceBands(sourceBands);
296    }
297
298    public void setDestinationBands(int[] destinationBands) {
299        if(decompressor != null) {
300            decompressor.setDestinationBands(destinationBands);
301        }
302        super.setDestinationBands(destinationBands);
303    }
304
305    public void setImage(BufferedImage image) {
306        if(decompressor != null) {
307            ColorModel cm = image.getColorModel();
308            tmpImage =
309                new BufferedImage(cm,
310                                  image.getRaster().createCompatibleWritableRaster(1, 1),
311                                  cm.isAlphaPremultiplied(),
312                                  null);
313            decompressor.setImage(tmpImage);
314        }
315        super.setImage(image);
316    }
317    
318    public void setDstMinX(int dstMinX) {
319        if(decompressor != null) {
320            decompressor.setDstMinX(dstMinX);
321        }
322        super.setDstMinX(dstMinX);
323    }
324    
325    public void setDstMinY(int dstMinY) {
326        if(decompressor != null) {
327            decompressor.setDstMinY(dstMinY);
328        }
329        super.setDstMinY(dstMinY);
330    }
331    
332    public void setDstWidth(int dstWidth) {
333        if(decompressor != null) {
334            decompressor.setDstWidth(dstWidth);
335        }
336        super.setDstWidth(dstWidth);
337    }
338
339    public void setDstHeight(int dstHeight) {
340        if(decompressor != null) {
341            decompressor.setDstHeight(dstHeight);
342        }
343        super.setDstHeight(dstHeight);
344    }
345
346    public void setActiveSrcMinX(int activeSrcMinX) {
347        if(decompressor != null) {
348            decompressor.setActiveSrcMinX(activeSrcMinX);
349        }
350        super.setActiveSrcMinX(activeSrcMinX);
351    }
352    
353    public void setActiveSrcMinY(int activeSrcMinY) {
354        if(decompressor != null) {
355            decompressor.setActiveSrcMinY(activeSrcMinY);
356        }
357        super.setActiveSrcMinY(activeSrcMinY);
358    }
359    
360    public void setActiveSrcWidth(int activeSrcWidth) {
361        if(decompressor != null) {
362            decompressor.setActiveSrcWidth(activeSrcWidth);
363        }
364        super.setActiveSrcWidth(activeSrcWidth);
365    }
366    
367    public void setActiveSrcHeight(int activeSrcHeight) {
368        if(decompressor != null) {
369            decompressor.setActiveSrcHeight(activeSrcHeight);
370        }
371        super.setActiveSrcHeight(activeSrcHeight);
372    }
373
374    private byte clamp(int f) {
375        if (f < 0) {
376            return (byte)0;
377        } else if (f > 255*65536) {
378            return (byte)255;
379        } else {
380            return (byte)(f >> 16);
381        }
382    }
383
384    public void beginDecoding() {
385        if(decompressor != null) {
386            decompressor.beginDecoding();
387        }
388
389        TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata;
390        TIFFField f;
391
392        f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
393        if (f != null) {
394            if (f.getCount() == 2) {
395                this.chromaSubsampleH = f.getAsInt(0);
396                this.chromaSubsampleV = f.getAsInt(1);
397
398                if (chromaSubsampleH != 1 && chromaSubsampleH != 2 &&
399                    chromaSubsampleH != 4) {
400                    warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " +
401                            chromaSubsampleH +
402                            " (should be 1, 2, or 4), setting to 1");
403                    chromaSubsampleH = 1;
404                }
405
406                if (chromaSubsampleV != 1 && chromaSubsampleV != 2 &&
407                    chromaSubsampleV != 4) {
408                    warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " +
409                            chromaSubsampleV +
410                            " (should be 1, 2, or 4), setting to 1");
411                    chromaSubsampleV = 1;
412                }
413            } else {
414                warning("Y_CB_CR_SUBSAMPLING count != 2, " +
415                        "assuming no subsampling");
416            }
417        }
418
419        f =
420           tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
421        if (f != null) {
422            if (f.getCount() == 3) {
423                this.LumaRed = f.getAsFloat(0);
424                this.LumaGreen = f.getAsFloat(1);
425                this.LumaBlue = f.getAsFloat(2);
426            } else {
427                warning("Y_CB_CR_COEFFICIENTS count != 3, " +
428                        "assuming default values for CCIR 601-1");
429            }
430        }
431
432        f =
433          tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
434        if (f != null) {
435            if (f.getCount() == 6) {
436                this.referenceBlackY = f.getAsFloat(0);
437                this.referenceWhiteY = f.getAsFloat(1);
438                this.referenceBlackCb = f.getAsFloat(2);
439                this.referenceWhiteCb = f.getAsFloat(3);
440                this.referenceBlackCr = f.getAsFloat(4);
441                this.referenceWhiteCr = f.getAsFloat(5);
442            } else {
443                warning("REFERENCE_BLACK_WHITE count != 6, ignoring it");
444            }
445        } else {
446                warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255");
447        }
448
449        this.colorConvert = true;
450        
451        float BCb = (2.0f - 2.0f*LumaBlue);
452        float RCr = (2.0f - 2.0f*LumaRed);
453
454        float GY = (1.0f - LumaBlue - LumaRed)/LumaGreen;
455        float GCb = 2.0f*LumaBlue*(LumaBlue - 1.0f)/LumaGreen;
456        float GCr = 2.0f*LumaRed*(LumaRed - 1.0f)/LumaGreen;
457
458        for (int i = 0; i < 256; i++) {
459            float fY = (i - referenceBlackY)*codingRangeY/
460                (referenceWhiteY - referenceBlackY);
461            float fCb = (i - referenceBlackCb)*127.0f/
462                (referenceWhiteCb - referenceBlackCb);
463            float fCr = (i - referenceBlackCr)*127.0f/
464                (referenceWhiteCr - referenceBlackCr);
465
466            iYTab[i] = (int)(fY*FRAC_SCALE);
467            iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE);
468            iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE);
469
470            iGYTab[i] = (int)(fY*GY*FRAC_SCALE);
471            iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE);
472            iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE);
473        }
474    }
475
476    public void decodeRaw(byte[] buf,
477                          int dstOffset,
478                          int bitsPerPixel,
479                          int scanlineStride) throws IOException {
480        byte[] rows = new byte[3*srcWidth*chromaSubsampleV];
481
482        int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2;
483        byte[] packet = new byte[elementsPerPacket];
484
485        if(decompressor != null) {
486            int bytesPerRow = 3*srcWidth;
487            byte[] tmpBuf = new byte[bytesPerRow*srcHeight];
488            decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel,
489                                   bytesPerRow);
490            ByteArrayInputStream byteStream =
491                new ByteArrayInputStream(tmpBuf);
492            stream = new MemoryCacheImageInputStream(byteStream);
493        } else {
494            stream.seek(offset);
495        }
496        
497        for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) {
498            // Decode chromaSubsampleV rows
499            for (int x = srcMinX; x < srcMinX + srcWidth;
500                 x += chromaSubsampleH) {
501                try {
502                    stream.readFully(packet);
503                } catch (EOFException e) {
504                    System.out.println("e = " + e);
505                    return;
506                }
507                
508                byte Cb = packet[elementsPerPacket - 2];
509                byte Cr = packet[elementsPerPacket - 1];
510
511                int iCb  = 0, iCr = 0, iGCb = 0, iGCr = 0;
512
513                if (colorConvert) {
514                    int Cbp = Cb & 0xff;
515                    int Crp = Cr & 0xff;
516
517                    iCb = iCbTab[Cbp];
518                    iCr = iCrTab[Crp];
519                    
520                    iGCb = iGCbTab[Cbp];
521                    iGCr = iGCrTab[Crp];
522                }
523
524                int yIndex = 0;
525                for (int v = 0; v < chromaSubsampleV; v++) {
526                    int idx = dstOffset + 3*(x - srcMinX) +
527                        scanlineStride*(y - srcMinY + v);
528
529                    // Check if we reached the last scanline
530                    if (y + v >= srcMinY + srcHeight) {
531                        break;
532                    }
533
534                    for (int h = 0; h < chromaSubsampleH; h++) {
535                        if (x + h >= srcMinX + srcWidth) {
536                            break;
537                        }
538                        
539                        byte Y = packet[yIndex++];
540                        
541                        if (colorConvert) {
542                            int Yp = Y & 0xff;
543                            int iY = iYTab[Yp];
544                            int iGY = iGYTab[Yp];
545                            
546                            int iR = iY + iCr;
547                            int iG = iGY + iGCb + iGCr;
548                            int iB = iY + iCb;
549                            
550                            byte r = clamp(iR);
551                            byte g = clamp(iG);
552                            byte b = clamp(iB);
553
554                            buf[idx] = r;
555                            buf[idx + 1] = g;
556                            buf[idx + 2] = b;
557                        } else {
558                            buf[idx] = Y;
559                            buf[idx + 1] = Cb;
560                            buf[idx + 2] = Cr;
561                        }
562
563                        idx += 3;
564                    }
565                }
566            }
567        }
568    }
569}