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}