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}