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: */
045: package com.sun.media.imageioimpl.plugins.tiff;
046:
047: import java.awt.Rectangle;
048: import java.awt.image.BufferedImage;
049: import java.awt.image.ColorModel;
050: import java.awt.image.DataBufferByte;
051: import java.io.ByteArrayInputStream;
052: import java.io.EOFException;
053: import java.io.IOException;
054: import javax.imageio.ImageReader;
055: import javax.imageio.metadata.IIOMetadata;
056: import javax.imageio.stream.MemoryCacheImageInputStream;
057: import javax.imageio.stream.ImageInputStream;
058: import com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet;
059: import com.sun.media.imageio.plugins.tiff.TIFFDecompressor;
060: import com.sun.media.imageio.plugins.tiff.TIFFField;
061:
062: public 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(
139: int photometricInterpretation) {
140: if (decompressor != null) {
141: decompressor
142: .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 = new BufferedImage(cm, image.getRaster()
309: .createCompatibleWritableRaster(1, 1), cm
310: .isAlphaPremultiplied(), 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
391: .getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
392: if (f != null) {
393: if (f.getCount() == 2) {
394: this .chromaSubsampleH = f.getAsInt(0);
395: this .chromaSubsampleV = f.getAsInt(1);
396:
397: if (chromaSubsampleH != 1 && chromaSubsampleH != 2
398: && chromaSubsampleH != 4) {
399: warning("Y_CB_CR_SUBSAMPLING[0] has illegal value "
400: + chromaSubsampleH
401: + " (should be 1, 2, or 4), setting to 1");
402: chromaSubsampleH = 1;
403: }
404:
405: if (chromaSubsampleV != 1 && chromaSubsampleV != 2
406: && chromaSubsampleV != 4) {
407: warning("Y_CB_CR_SUBSAMPLING[1] has illegal value "
408: + chromaSubsampleV
409: + " (should be 1, 2, or 4), setting to 1");
410: chromaSubsampleV = 1;
411: }
412: } else {
413: warning("Y_CB_CR_SUBSAMPLING count != 2, "
414: + "assuming no subsampling");
415: }
416: }
417:
418: f = tmetadata
419: .getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
420: if (f != null) {
421: if (f.getCount() == 3) {
422: this .LumaRed = f.getAsFloat(0);
423: this .LumaGreen = f.getAsFloat(1);
424: this .LumaBlue = f.getAsFloat(2);
425: } else {
426: warning("Y_CB_CR_COEFFICIENTS count != 3, "
427: + "assuming default values for CCIR 601-1");
428: }
429: }
430:
431: f = tmetadata
432: .getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
433: if (f != null) {
434: if (f.getCount() == 6) {
435: this .referenceBlackY = f.getAsFloat(0);
436: this .referenceWhiteY = f.getAsFloat(1);
437: this .referenceBlackCb = f.getAsFloat(2);
438: this .referenceWhiteCb = f.getAsFloat(3);
439: this .referenceBlackCr = f.getAsFloat(4);
440: this .referenceWhiteCr = f.getAsFloat(5);
441: } else {
442: warning("REFERENCE_BLACK_WHITE count != 6, ignoring it");
443: }
444: } else {
445: warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255");
446: }
447:
448: this .colorConvert = true;
449:
450: float BCb = (2.0f - 2.0f * LumaBlue);
451: float RCr = (2.0f - 2.0f * LumaRed);
452:
453: float GY = (1.0f - LumaBlue - LumaRed) / LumaGreen;
454: float GCb = 2.0f * LumaBlue * (LumaBlue - 1.0f) / LumaGreen;
455: float GCr = 2.0f * LumaRed * (LumaRed - 1.0f) / LumaGreen;
456:
457: for (int i = 0; i < 256; i++) {
458: float fY = (i - referenceBlackY) * codingRangeY
459: / (referenceWhiteY - referenceBlackY);
460: float fCb = (i - referenceBlackCb) * 127.0f
461: / (referenceWhiteCb - referenceBlackCb);
462: float fCr = (i - referenceBlackCr) * 127.0f
463: / (referenceWhiteCr - referenceBlackCr);
464:
465: iYTab[i] = (int) (fY * FRAC_SCALE);
466: iCbTab[i] = (int) (fCb * BCb * FRAC_SCALE);
467: iCrTab[i] = (int) (fCr * RCr * FRAC_SCALE);
468:
469: iGYTab[i] = (int) (fY * GY * FRAC_SCALE);
470: iGCbTab[i] = (int) (fCb * GCb * FRAC_SCALE);
471: iGCrTab[i] = (int) (fCr * GCr * FRAC_SCALE);
472: }
473: }
474:
475: public void decodeRaw(byte[] buf, int dstOffset, int bitsPerPixel,
476: int scanlineStride) throws IOException {
477: byte[] rows = new byte[3 * srcWidth * chromaSubsampleV];
478:
479: int elementsPerPacket = chromaSubsampleH * chromaSubsampleV + 2;
480: byte[] packet = new byte[elementsPerPacket];
481:
482: if (decompressor != null) {
483: int bytesPerRow = 3 * srcWidth;
484: byte[] tmpBuf = new byte[bytesPerRow * srcHeight];
485: decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel,
486: bytesPerRow);
487: ByteArrayInputStream byteStream = new ByteArrayInputStream(
488: tmpBuf);
489: stream = new MemoryCacheImageInputStream(byteStream);
490: } else {
491: stream.seek(offset);
492: }
493:
494: for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) {
495: // Decode chromaSubsampleV rows
496: for (int x = srcMinX; x < srcMinX + srcWidth; x += chromaSubsampleH) {
497: try {
498: stream.readFully(packet);
499: } catch (EOFException e) {
500: System.out.println("e = " + e);
501: return;
502: }
503:
504: byte Cb = packet[elementsPerPacket - 2];
505: byte Cr = packet[elementsPerPacket - 1];
506:
507: int iCb = 0, iCr = 0, iGCb = 0, iGCr = 0;
508:
509: if (colorConvert) {
510: int Cbp = Cb & 0xff;
511: int Crp = Cr & 0xff;
512:
513: iCb = iCbTab[Cbp];
514: iCr = iCrTab[Crp];
515:
516: iGCb = iGCbTab[Cbp];
517: iGCr = iGCrTab[Crp];
518: }
519:
520: int yIndex = 0;
521: for (int v = 0; v < chromaSubsampleV; v++) {
522: int idx = dstOffset + 3 * (x - srcMinX)
523: + scanlineStride * (y - srcMinY + v);
524:
525: // Check if we reached the last scanline
526: if (y + v >= srcMinY + srcHeight) {
527: break;
528: }
529:
530: for (int h = 0; h < chromaSubsampleH; h++) {
531: if (x + h >= srcMinX + srcWidth) {
532: break;
533: }
534:
535: byte Y = packet[yIndex++];
536:
537: if (colorConvert) {
538: int Yp = Y & 0xff;
539: int iY = iYTab[Yp];
540: int iGY = iGYTab[Yp];
541:
542: int iR = iY + iCr;
543: int iG = iGY + iGCb + iGCr;
544: int iB = iY + iCb;
545:
546: byte r = clamp(iR);
547: byte g = clamp(iG);
548: byte b = clamp(iB);
549:
550: buf[idx] = r;
551: buf[idx + 1] = g;
552: buf[idx + 2] = b;
553: } else {
554: buf[idx] = Y;
555: buf[idx + 1] = Cb;
556: buf[idx + 2] = Cr;
557: }
558:
559: idx += 3;
560: }
561: }
562: }
563: }
564: }
565: }
|