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.image.BufferedImage;
048import java.awt.image.ColorModel;
049import java.io.ByteArrayInputStream;
050import java.io.EOFException;
051import java.io.IOException;
052
053import javax.imageio.ImageReader;
054import javax.imageio.metadata.IIOMetadata;
055import javax.imageio.stream.ImageInputStream;
056import javax.imageio.stream.MemoryCacheImageInputStream;
057
058import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
059import com.github.jaiimageio.plugins.tiff.TIFFDecompressor;
060import com.github.jaiimageio.plugins.tiff.TIFFField;
061
062public class TIFFYCbCrDecompressor extends TIFFDecompressor {
063
064    private static final boolean debug = false;
065
066    // Store constants in S15.16 format 
067    private static final int FRAC_BITS = 16;
068    private static final float FRAC_SCALE = (float)(1 << FRAC_BITS);
069
070    private float LumaRed = 0.299f;
071    private float LumaGreen = 0.587f;
072    private float LumaBlue = 0.114f;
073
074    private float referenceBlackY = 0.0f;
075    private float referenceWhiteY = 255.0f;
076
077    private float referenceBlackCb = 128.0f;
078    private float referenceWhiteCb = 255.0f;
079
080    private float referenceBlackCr = 128.0f;
081    private float referenceWhiteCr = 255.0f;
082
083    private float codingRangeY = 255.0f;
084
085    private int[] iYTab = new int[256];
086    private int[] iCbTab = new int[256];
087    private int[] iCrTab = new int[256];
088
089    private int[] iGYTab = new int[256];
090    private int[] iGCbTab = new int[256];
091    private int[] iGCrTab = new int[256];
092
093    private int chromaSubsampleH = 2;
094    private int chromaSubsampleV = 2;
095
096    private boolean colorConvert;
097
098    private TIFFDecompressor decompressor;
099
100    private BufferedImage tmpImage;
101
102    //
103    // If 'decompressor' is not null then it reads the data from the
104    // actual stream first and passes the result on to YCrCr decompression
105    // and possibly color conversion.
106    //
107
108    public TIFFYCbCrDecompressor(TIFFDecompressor decompressor,
109                                 boolean colorConvert) {
110        this.decompressor = decompressor;
111        this.colorConvert = colorConvert;
112    }
113
114    private void warning(String message) {
115        if(this.reader instanceof TIFFImageReader) {
116            ((TIFFImageReader)reader).forwardWarningMessage(message);
117        }
118    }
119
120    //
121    // "Chained" decompressor methods.
122    //
123
124    public void setReader(ImageReader reader) {
125        if(decompressor != null) {
126            decompressor.setReader(reader);
127        }
128        super.setReader(reader);
129    }
130
131    public void setMetadata(IIOMetadata metadata) {
132        if(decompressor != null) {
133            decompressor.setMetadata(metadata);
134        }
135        super.setMetadata(metadata);
136    }
137
138    public void setPhotometricInterpretation(int photometricInterpretation) {
139        if(decompressor != null) {
140            decompressor.setPhotometricInterpretation(photometricInterpretation);
141        }
142        super.setPhotometricInterpretation(photometricInterpretation);
143    }
144
145    public void setCompression(int compression) {
146        if(decompressor != null) {
147            decompressor.setCompression(compression);
148        }
149        super.setCompression(compression);
150    }
151
152    public void setPlanar(boolean planar) {
153        if(decompressor != null) {
154            decompressor.setPlanar(planar);
155        }
156        super.setPlanar(planar);
157    }
158
159    public void setSamplesPerPixel(int samplesPerPixel) {
160        if(decompressor != null) {
161            decompressor.setSamplesPerPixel(samplesPerPixel);
162        }
163        super.setSamplesPerPixel(samplesPerPixel);
164    }
165
166    public void setBitsPerSample(int[] bitsPerSample) {
167        if(decompressor != null) {
168            decompressor.setBitsPerSample(bitsPerSample);
169        }
170        super.setBitsPerSample(bitsPerSample);
171    }
172
173    public void setSampleFormat(int[] sampleFormat) {
174        if(decompressor != null) {
175            decompressor.setSampleFormat(sampleFormat);
176        }
177        super.setSampleFormat(sampleFormat);
178    }
179
180    public void setExtraSamples(int[] extraSamples) {
181        if(decompressor != null) {
182            decompressor.setExtraSamples(extraSamples);
183        }
184        super.setExtraSamples(extraSamples);
185    }
186    
187    public void setColorMap(char[] colorMap) {
188        if(decompressor != null) {
189            decompressor.setColorMap(colorMap);
190        }
191        super.setColorMap(colorMap);
192    }
193
194    public void setStream(ImageInputStream stream) {
195        if(decompressor != null) {
196            decompressor.setStream(stream);
197        } else {
198            super.setStream(stream);
199        }
200    }
201
202    public void setOffset(long offset) {
203        if(decompressor != null) {
204            decompressor.setOffset(offset);
205        }
206        super.setOffset(offset);
207    }
208
209    public void setByteCount(int byteCount) {
210        if(decompressor != null) {
211            decompressor.setByteCount(byteCount);
212        }
213        super.setByteCount(byteCount);
214    }
215
216    public void setSrcMinX(int srcMinX) {
217        if(decompressor != null) {
218            decompressor.setSrcMinX(srcMinX);
219        }
220        super.setSrcMinX(srcMinX);
221    }
222    
223    public void setSrcMinY(int srcMinY) {
224        if(decompressor != null) {
225            decompressor.setSrcMinY(srcMinY);
226        }
227        super.setSrcMinY(srcMinY);
228    }
229    
230    public void setSrcWidth(int srcWidth) {
231        if(decompressor != null) {
232            decompressor.setSrcWidth(srcWidth);
233        }
234        super.setSrcWidth(srcWidth);
235    }
236    
237    public void setSrcHeight(int srcHeight) {
238        if(decompressor != null) {
239            decompressor.setSrcHeight(srcHeight);
240        }
241        super.setSrcHeight(srcHeight);
242    }
243
244    public void setSourceXOffset(int sourceXOffset) {
245        if(decompressor != null) {
246            decompressor.setSourceXOffset(sourceXOffset);
247        }
248        super.setSourceXOffset(sourceXOffset);
249    }
250
251    public void setDstXOffset(int dstXOffset) {
252        if(decompressor != null) {
253            decompressor.setDstXOffset(dstXOffset);
254        }
255        super.setDstXOffset(dstXOffset);
256    }
257
258    public void setSourceYOffset(int sourceYOffset) {
259        if(decompressor != null) {
260            decompressor.setSourceYOffset(sourceYOffset);
261        }
262        super.setSourceYOffset(sourceYOffset);
263    }
264
265    public void setDstYOffset(int dstYOffset) {
266        if(decompressor != null) {
267            decompressor.setDstYOffset(dstYOffset);
268        }
269        super.setDstYOffset(dstYOffset);
270    }
271
272    /* Should not need to override these mutators as subsampling
273       should not be done by the wrapped decompressor.
274    public void setSubsampleX(int subsampleX) {
275        if(decompressor != null) {
276            decompressor.setSubsampleX(subsampleX);
277        }
278        super.setSubsampleX(subsampleX);
279    }
280    
281    public void setSubsampleY(int subsampleY) {
282        if(decompressor != null) {
283            decompressor.setSubsampleY(subsampleY);
284        }
285        super.setSubsampleY(subsampleY);
286    }
287    */
288    
289    public void setSourceBands(int[] sourceBands) {
290        if(decompressor != null) {
291            decompressor.setSourceBands(sourceBands);
292        }
293        super.setSourceBands(sourceBands);
294    }
295
296    public void setDestinationBands(int[] destinationBands) {
297        if(decompressor != null) {
298            decompressor.setDestinationBands(destinationBands);
299        }
300        super.setDestinationBands(destinationBands);
301    }
302
303    public void setImage(BufferedImage image) {
304        if(decompressor != null) {
305            ColorModel cm = image.getColorModel();
306            tmpImage =
307                new BufferedImage(cm,
308                                  image.getRaster().createCompatibleWritableRaster(1, 1),
309                                  cm.isAlphaPremultiplied(),
310                                  null);
311            decompressor.setImage(tmpImage);
312        }
313        super.setImage(image);
314    }
315    
316    public void setDstMinX(int dstMinX) {
317        if(decompressor != null) {
318            decompressor.setDstMinX(dstMinX);
319        }
320        super.setDstMinX(dstMinX);
321    }
322    
323    public void setDstMinY(int dstMinY) {
324        if(decompressor != null) {
325            decompressor.setDstMinY(dstMinY);
326        }
327        super.setDstMinY(dstMinY);
328    }
329    
330    public void setDstWidth(int dstWidth) {
331        if(decompressor != null) {
332            decompressor.setDstWidth(dstWidth);
333        }
334        super.setDstWidth(dstWidth);
335    }
336
337    public void setDstHeight(int dstHeight) {
338        if(decompressor != null) {
339            decompressor.setDstHeight(dstHeight);
340        }
341        super.setDstHeight(dstHeight);
342    }
343
344    public void setActiveSrcMinX(int activeSrcMinX) {
345        if(decompressor != null) {
346            decompressor.setActiveSrcMinX(activeSrcMinX);
347        }
348        super.setActiveSrcMinX(activeSrcMinX);
349    }
350    
351    public void setActiveSrcMinY(int activeSrcMinY) {
352        if(decompressor != null) {
353            decompressor.setActiveSrcMinY(activeSrcMinY);
354        }
355        super.setActiveSrcMinY(activeSrcMinY);
356    }
357    
358    public void setActiveSrcWidth(int activeSrcWidth) {
359        if(decompressor != null) {
360            decompressor.setActiveSrcWidth(activeSrcWidth);
361        }
362        super.setActiveSrcWidth(activeSrcWidth);
363    }
364    
365    public void setActiveSrcHeight(int activeSrcHeight) {
366        if(decompressor != null) {
367            decompressor.setActiveSrcHeight(activeSrcHeight);
368        }
369        super.setActiveSrcHeight(activeSrcHeight);
370    }
371
372    private byte clamp(int f) {
373        if (f < 0) {
374            return (byte)0;
375        } else if (f > 255*65536) {
376            return (byte)255;
377        } else {
378            return (byte)(f >> 16);
379        }
380    }
381
382    public void beginDecoding() {
383        if(decompressor != null) {
384            decompressor.beginDecoding();
385        }
386
387        TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata;
388        TIFFField f;
389
390        f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
391        if (f != null) {
392            if (f.getCount() == 2) {
393                this.chromaSubsampleH = f.getAsInt(0);
394                this.chromaSubsampleV = f.getAsInt(1);
395
396                if (chromaSubsampleH != 1 && chromaSubsampleH != 2 &&
397                    chromaSubsampleH != 4) {
398                    warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " +
399                            chromaSubsampleH +
400                            " (should be 1, 2, or 4), setting to 1");
401                    chromaSubsampleH = 1;
402                }
403
404                if (chromaSubsampleV != 1 && chromaSubsampleV != 2 &&
405                    chromaSubsampleV != 4) {
406                    warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " +
407                            chromaSubsampleV +
408                            " (should be 1, 2, or 4), setting to 1");
409                    chromaSubsampleV = 1;
410                }
411            } else {
412                warning("Y_CB_CR_SUBSAMPLING count != 2, " +
413                        "assuming no subsampling");
414            }
415        }
416
417        f =
418           tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
419        if (f != null) {
420            if (f.getCount() == 3) {
421                this.LumaRed = f.getAsFloat(0);
422                this.LumaGreen = f.getAsFloat(1);
423                this.LumaBlue = f.getAsFloat(2);
424            } else {
425                warning("Y_CB_CR_COEFFICIENTS count != 3, " +
426                        "assuming default values for CCIR 601-1");
427            }
428        }
429
430        f =
431          tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
432        if (f != null) {
433            if (f.getCount() == 6) {
434                this.referenceBlackY = f.getAsFloat(0);
435                this.referenceWhiteY = f.getAsFloat(1);
436                this.referenceBlackCb = f.getAsFloat(2);
437                this.referenceWhiteCb = f.getAsFloat(3);
438                this.referenceBlackCr = f.getAsFloat(4);
439                this.referenceWhiteCr = f.getAsFloat(5);
440            } else {
441                warning("REFERENCE_BLACK_WHITE count != 6, ignoring it");
442            }
443        } else {
444                warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255");
445        }
446
447        this.colorConvert = true;
448        
449        float BCb = (2.0f - 2.0f*LumaBlue);
450        float RCr = (2.0f - 2.0f*LumaRed);
451
452        float GY = (1.0f - LumaBlue - LumaRed)/LumaGreen;
453        float GCb = 2.0f*LumaBlue*(LumaBlue - 1.0f)/LumaGreen;
454        float GCr = 2.0f*LumaRed*(LumaRed - 1.0f)/LumaGreen;
455
456        for (int i = 0; i < 256; i++) {
457            float fY = (i - referenceBlackY)*codingRangeY/
458                (referenceWhiteY - referenceBlackY);
459            float fCb = (i - referenceBlackCb)*127.0f/
460                (referenceWhiteCb - referenceBlackCb);
461            float fCr = (i - referenceBlackCr)*127.0f/
462                (referenceWhiteCr - referenceBlackCr);
463
464            iYTab[i] = (int)(fY*FRAC_SCALE);
465            iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE);
466            iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE);
467
468            iGYTab[i] = (int)(fY*GY*FRAC_SCALE);
469            iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE);
470            iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE);
471        }
472    }
473
474    public void decodeRaw(byte[] buf,
475                          int dstOffset,
476                          int bitsPerPixel,
477                          int scanlineStride) throws IOException {
478        byte[] rows = new byte[3*srcWidth*chromaSubsampleV];
479
480        int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2;
481        byte[] packet = new byte[elementsPerPacket];
482
483        if(decompressor != null) {
484            int bytesPerRow = 3*srcWidth;
485            byte[] tmpBuf = new byte[bytesPerRow*srcHeight];
486            decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel,
487                                   bytesPerRow);
488            ByteArrayInputStream byteStream =
489                new ByteArrayInputStream(tmpBuf);
490            stream = new MemoryCacheImageInputStream(byteStream);
491        } else {
492            stream.seek(offset);
493        }
494        
495        for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) {
496            // Decode chromaSubsampleV rows
497            for (int x = srcMinX; x < srcMinX + srcWidth;
498                 x += chromaSubsampleH) {
499                try {
500                    stream.readFully(packet);
501                } catch (EOFException e) {
502                    System.out.println("e = " + e);
503                    return;
504                }
505                
506                byte Cb = packet[elementsPerPacket - 2];
507                byte Cr = packet[elementsPerPacket - 1];
508
509                int iCb  = 0, iCr = 0, iGCb = 0, iGCr = 0;
510
511                if (colorConvert) {
512                    int Cbp = Cb & 0xff;
513                    int Crp = Cr & 0xff;
514
515                    iCb = iCbTab[Cbp];
516                    iCr = iCrTab[Crp];
517                    
518                    iGCb = iGCbTab[Cbp];
519                    iGCr = iGCrTab[Crp];
520                }
521
522                int yIndex = 0;
523                for (int v = 0; v < chromaSubsampleV; v++) {
524                    int idx = dstOffset + 3*(x - srcMinX) +
525                        scanlineStride*(y - srcMinY + v);
526
527                    // Check if we reached the last scanline
528                    if (y + v >= srcMinY + srcHeight) {
529                        break;
530                    }
531
532                    for (int h = 0; h < chromaSubsampleH; h++) {
533                        if (x + h >= srcMinX + srcWidth) {
534                            break;
535                        }
536                        
537                        byte Y = packet[yIndex++];
538                        
539                        if (colorConvert) {
540                            int Yp = Y & 0xff;
541                            int iY = iYTab[Yp];
542                            int iGY = iGYTab[Yp];
543                            
544                            int iR = iY + iCr;
545                            int iG = iGY + iGCb + iGCr;
546                            int iB = iY + iCb;
547                            
548                            byte r = clamp(iR);
549                            byte g = clamp(iG);
550                            byte b = clamp(iB);
551
552                            buf[idx] = r;
553                            buf[idx + 1] = g;
554                            buf[idx + 2] = b;
555                        } else {
556                            buf[idx] = Y;
557                            buf[idx + 1] = Cb;
558                            buf[idx + 2] = Cr;
559                        }
560
561                        idx += 3;
562                    }
563                }
564            }
565        }
566    }
567}