1 /* 2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.imageio.plugins.tiff; 26 27 import java.awt.image.BufferedImage; 28 import java.awt.image.ColorModel; 29 import java.io.ByteArrayInputStream; 30 import java.io.EOFException; 31 import java.io.IOException; 32 import javax.imageio.ImageReader; 33 import javax.imageio.metadata.IIOMetadata; 34 import javax.imageio.stream.MemoryCacheImageInputStream; 35 import javax.imageio.stream.ImageInputStream; 36 import javax.imageio.plugins.tiff.BaselineTIFFTagSet; 37 import javax.imageio.plugins.tiff.TIFFField; 38 39 public class TIFFYCbCrDecompressor extends TIFFDecompressor { 40 // Store constants in S15.16 format 41 private static final int FRAC_BITS = 16; 42 private static final float FRAC_SCALE = (float)(1 << FRAC_BITS); 43 44 private float lumaRed = 0.299f; 45 private float lumaGreen = 0.587f; 46 private float lumaBlue = 0.114f; 47 48 private float referenceBlackY = 0.0f; 49 private float referenceWhiteY = 255.0f; 50 51 private float referenceBlackCb = 128.0f; 52 private float referenceWhiteCb = 255.0f; 53 54 private float referenceBlackCr = 128.0f; 55 private float referenceWhiteCr = 255.0f; 56 57 private float codingRangeY = 255.0f; 58 59 private int[] iYTab = new int[256]; 60 private int[] iCbTab = new int[256]; 61 private int[] iCrTab = new int[256]; 62 63 private int[] iGYTab = new int[256]; 64 private int[] iGCbTab = new int[256]; 65 private int[] iGCrTab = new int[256]; 66 67 private int chromaSubsampleH = 2; 68 private int chromaSubsampleV = 2; 69 70 private boolean colorConvert; 71 72 private TIFFDecompressor decompressor; 73 74 private BufferedImage tmpImage; 75 76 // 77 // If 'decompressor' is not null then it reads the data from the 78 // actual stream first and passes the result on to YCrCr decompression 79 // and possibly color conversion. 80 // 81 TIFFYCbCrDecompressor(TIFFDecompressor decompressor, boolean colorConvert)82 public TIFFYCbCrDecompressor(TIFFDecompressor decompressor, 83 boolean colorConvert) { 84 this.decompressor = decompressor; 85 this.colorConvert = colorConvert; 86 } 87 warning(String message)88 private void warning(String message) { 89 if(this.reader instanceof TIFFImageReader) { 90 ((TIFFImageReader)reader).forwardWarningMessage(message); 91 } 92 } 93 94 // 95 // "Chained" decompressor methods. 96 // 97 setReader(ImageReader reader)98 public void setReader(ImageReader reader) { 99 if(decompressor != null) { 100 decompressor.setReader(reader); 101 } 102 super.setReader(reader); 103 } 104 setMetadata(IIOMetadata metadata)105 public void setMetadata(IIOMetadata metadata) { 106 if(decompressor != null) { 107 decompressor.setMetadata(metadata); 108 } 109 super.setMetadata(metadata); 110 } 111 setPhotometricInterpretation(int photometricInterpretation)112 public void setPhotometricInterpretation(int photometricInterpretation) { 113 if(decompressor != null) { 114 decompressor.setPhotometricInterpretation(photometricInterpretation); 115 } 116 super.setPhotometricInterpretation(photometricInterpretation); 117 } 118 setCompression(int compression)119 public void setCompression(int compression) { 120 if(decompressor != null) { 121 decompressor.setCompression(compression); 122 } 123 super.setCompression(compression); 124 } 125 setPlanar(boolean planar)126 public void setPlanar(boolean planar) { 127 if(decompressor != null) { 128 decompressor.setPlanar(planar); 129 } 130 super.setPlanar(planar); 131 } 132 setSamplesPerPixel(int samplesPerPixel)133 public void setSamplesPerPixel(int samplesPerPixel) { 134 if(decompressor != null) { 135 decompressor.setSamplesPerPixel(samplesPerPixel); 136 } 137 super.setSamplesPerPixel(samplesPerPixel); 138 } 139 setBitsPerSample(int[] bitsPerSample)140 public void setBitsPerSample(int[] bitsPerSample) { 141 if(decompressor != null) { 142 decompressor.setBitsPerSample(bitsPerSample); 143 } 144 super.setBitsPerSample(bitsPerSample); 145 } 146 setSampleFormat(int[] sampleFormat)147 public void setSampleFormat(int[] sampleFormat) { 148 if(decompressor != null) { 149 decompressor.setSampleFormat(sampleFormat); 150 } 151 super.setSampleFormat(sampleFormat); 152 } 153 setExtraSamples(int[] extraSamples)154 public void setExtraSamples(int[] extraSamples) { 155 if(decompressor != null) { 156 decompressor.setExtraSamples(extraSamples); 157 } 158 super.setExtraSamples(extraSamples); 159 } 160 setColorMap(char[] colorMap)161 public void setColorMap(char[] colorMap) { 162 if(decompressor != null) { 163 decompressor.setColorMap(colorMap); 164 } 165 super.setColorMap(colorMap); 166 } 167 setStream(ImageInputStream stream)168 public void setStream(ImageInputStream stream) { 169 if(decompressor != null) { 170 decompressor.setStream(stream); 171 } else { 172 super.setStream(stream); 173 } 174 } 175 setOffset(long offset)176 public void setOffset(long offset) { 177 if(decompressor != null) { 178 decompressor.setOffset(offset); 179 } 180 super.setOffset(offset); 181 } 182 setByteCount(int byteCount)183 public void setByteCount(int byteCount) { 184 if(decompressor != null) { 185 decompressor.setByteCount(byteCount); 186 } 187 super.setByteCount(byteCount); 188 } 189 setSrcMinX(int srcMinX)190 public void setSrcMinX(int srcMinX) { 191 if(decompressor != null) { 192 decompressor.setSrcMinX(srcMinX); 193 } 194 super.setSrcMinX(srcMinX); 195 } 196 setSrcMinY(int srcMinY)197 public void setSrcMinY(int srcMinY) { 198 if(decompressor != null) { 199 decompressor.setSrcMinY(srcMinY); 200 } 201 super.setSrcMinY(srcMinY); 202 } 203 setSrcWidth(int srcWidth)204 public void setSrcWidth(int srcWidth) { 205 if(decompressor != null) { 206 decompressor.setSrcWidth(srcWidth); 207 } 208 super.setSrcWidth(srcWidth); 209 } 210 setSrcHeight(int srcHeight)211 public void setSrcHeight(int srcHeight) { 212 if(decompressor != null) { 213 decompressor.setSrcHeight(srcHeight); 214 } 215 super.setSrcHeight(srcHeight); 216 } 217 setSourceXOffset(int sourceXOffset)218 public void setSourceXOffset(int sourceXOffset) { 219 if(decompressor != null) { 220 decompressor.setSourceXOffset(sourceXOffset); 221 } 222 super.setSourceXOffset(sourceXOffset); 223 } 224 setDstXOffset(int dstXOffset)225 public void setDstXOffset(int dstXOffset) { 226 if(decompressor != null) { 227 decompressor.setDstXOffset(dstXOffset); 228 } 229 super.setDstXOffset(dstXOffset); 230 } 231 setSourceYOffset(int sourceYOffset)232 public void setSourceYOffset(int sourceYOffset) { 233 if(decompressor != null) { 234 decompressor.setSourceYOffset(sourceYOffset); 235 } 236 super.setSourceYOffset(sourceYOffset); 237 } 238 setDstYOffset(int dstYOffset)239 public void setDstYOffset(int dstYOffset) { 240 if(decompressor != null) { 241 decompressor.setDstYOffset(dstYOffset); 242 } 243 super.setDstYOffset(dstYOffset); 244 } 245 246 /* Should not need to override these mutators as subsampling 247 should not be done by the wrapped decompressor. 248 public void setSubsampleX(int subsampleX) { 249 if(decompressor != null) { 250 decompressor.setSubsampleX(subsampleX); 251 } 252 super.setSubsampleX(subsampleX); 253 } 254 255 public void setSubsampleY(int subsampleY) { 256 if(decompressor != null) { 257 decompressor.setSubsampleY(subsampleY); 258 } 259 super.setSubsampleY(subsampleY); 260 } 261 */ 262 setSourceBands(int[] sourceBands)263 public void setSourceBands(int[] sourceBands) { 264 if(decompressor != null) { 265 decompressor.setSourceBands(sourceBands); 266 } 267 super.setSourceBands(sourceBands); 268 } 269 setDestinationBands(int[] destinationBands)270 public void setDestinationBands(int[] destinationBands) { 271 if(decompressor != null) { 272 decompressor.setDestinationBands(destinationBands); 273 } 274 super.setDestinationBands(destinationBands); 275 } 276 setImage(BufferedImage image)277 public void setImage(BufferedImage image) { 278 if(decompressor != null) { 279 ColorModel cm = image.getColorModel(); 280 tmpImage = 281 new BufferedImage(cm, 282 image.getRaster().createCompatibleWritableRaster(1, 1), 283 cm.isAlphaPremultiplied(), 284 null); 285 decompressor.setImage(tmpImage); 286 } 287 super.setImage(image); 288 } 289 setDstMinX(int dstMinX)290 public void setDstMinX(int dstMinX) { 291 if(decompressor != null) { 292 decompressor.setDstMinX(dstMinX); 293 } 294 super.setDstMinX(dstMinX); 295 } 296 setDstMinY(int dstMinY)297 public void setDstMinY(int dstMinY) { 298 if(decompressor != null) { 299 decompressor.setDstMinY(dstMinY); 300 } 301 super.setDstMinY(dstMinY); 302 } 303 setDstWidth(int dstWidth)304 public void setDstWidth(int dstWidth) { 305 if(decompressor != null) { 306 decompressor.setDstWidth(dstWidth); 307 } 308 super.setDstWidth(dstWidth); 309 } 310 setDstHeight(int dstHeight)311 public void setDstHeight(int dstHeight) { 312 if(decompressor != null) { 313 decompressor.setDstHeight(dstHeight); 314 } 315 super.setDstHeight(dstHeight); 316 } 317 setActiveSrcMinX(int activeSrcMinX)318 public void setActiveSrcMinX(int activeSrcMinX) { 319 if(decompressor != null) { 320 decompressor.setActiveSrcMinX(activeSrcMinX); 321 } 322 super.setActiveSrcMinX(activeSrcMinX); 323 } 324 setActiveSrcMinY(int activeSrcMinY)325 public void setActiveSrcMinY(int activeSrcMinY) { 326 if(decompressor != null) { 327 decompressor.setActiveSrcMinY(activeSrcMinY); 328 } 329 super.setActiveSrcMinY(activeSrcMinY); 330 } 331 setActiveSrcWidth(int activeSrcWidth)332 public void setActiveSrcWidth(int activeSrcWidth) { 333 if(decompressor != null) { 334 decompressor.setActiveSrcWidth(activeSrcWidth); 335 } 336 super.setActiveSrcWidth(activeSrcWidth); 337 } 338 setActiveSrcHeight(int activeSrcHeight)339 public void setActiveSrcHeight(int activeSrcHeight) { 340 if(decompressor != null) { 341 decompressor.setActiveSrcHeight(activeSrcHeight); 342 } 343 super.setActiveSrcHeight(activeSrcHeight); 344 } 345 clamp(int f)346 private byte clamp(int f) { 347 if (f < 0) { 348 return (byte)0; 349 } else if (f > 255*65536) { 350 return (byte)255; 351 } else { 352 return (byte)(f >> 16); 353 } 354 } 355 beginDecoding()356 public void beginDecoding() { 357 if(decompressor != null) { 358 decompressor.beginDecoding(); 359 } 360 361 TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata; 362 TIFFField f; 363 364 f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); 365 if (f != null) { 366 if (f.getCount() == 2) { 367 this.chromaSubsampleH = f.getAsInt(0); 368 this.chromaSubsampleV = f.getAsInt(1); 369 370 if (chromaSubsampleH != 1 && chromaSubsampleH != 2 && 371 chromaSubsampleH != 4) { 372 warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " + 373 chromaSubsampleH + 374 " (should be 1, 2, or 4), setting to 1"); 375 chromaSubsampleH = 1; 376 } 377 378 if (chromaSubsampleV != 1 && chromaSubsampleV != 2 && 379 chromaSubsampleV != 4) { 380 warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " + 381 chromaSubsampleV + 382 " (should be 1, 2, or 4), setting to 1"); 383 chromaSubsampleV = 1; 384 } 385 } else { 386 warning("Y_CB_CR_SUBSAMPLING count != 2, " + 387 "assuming no subsampling"); 388 } 389 } 390 391 f = 392 tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS); 393 if (f != null) { 394 if (f.getCount() == 3) { 395 this.lumaRed = f.getAsFloat(0); 396 this.lumaGreen = f.getAsFloat(1); 397 this.lumaBlue = f.getAsFloat(2); 398 } else { 399 warning("Y_CB_CR_COEFFICIENTS count != 3, " + 400 "assuming default values for CCIR 601-1"); 401 } 402 } 403 404 f = 405 tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE); 406 if (f != null) { 407 if (f.getCount() == 6) { 408 this.referenceBlackY = f.getAsFloat(0); 409 this.referenceWhiteY = f.getAsFloat(1); 410 this.referenceBlackCb = f.getAsFloat(2); 411 this.referenceWhiteCb = f.getAsFloat(3); 412 this.referenceBlackCr = f.getAsFloat(4); 413 this.referenceWhiteCr = f.getAsFloat(5); 414 } else { 415 warning("REFERENCE_BLACK_WHITE count != 6, ignoring it"); 416 } 417 } else { 418 warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255"); 419 } 420 421 this.colorConvert = true; 422 423 float BCb = (2.0f - 2.0f*lumaBlue); 424 float RCr = (2.0f - 2.0f*lumaRed); 425 426 float GY = (1.0f - lumaBlue - lumaRed)/lumaGreen; 427 float GCb = 2.0f*lumaBlue*(lumaBlue - 1.0f)/lumaGreen; 428 float GCr = 2.0f*lumaRed*(lumaRed - 1.0f)/lumaGreen; 429 430 for (int i = 0; i < 256; i++) { 431 float fY = (i - referenceBlackY)*codingRangeY/ 432 (referenceWhiteY - referenceBlackY); 433 float fCb = (i - referenceBlackCb)*127.0f/ 434 (referenceWhiteCb - referenceBlackCb); 435 float fCr = (i - referenceBlackCr)*127.0f/ 436 (referenceWhiteCr - referenceBlackCr); 437 438 iYTab[i] = (int)(fY*FRAC_SCALE); 439 iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE); 440 iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE); 441 442 iGYTab[i] = (int)(fY*GY*FRAC_SCALE); 443 iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE); 444 iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE); 445 } 446 } 447 decodeRaw(byte[] buf, int dstOffset, int bitsPerPixel, int scanlineStride)448 public void decodeRaw(byte[] buf, 449 int dstOffset, 450 int bitsPerPixel, 451 int scanlineStride) throws IOException { 452 int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2; 453 byte[] packet = new byte[elementsPerPacket]; 454 455 if(decompressor != null) { 456 int bytesPerRow = 3*srcWidth; 457 byte[] tmpBuf = new byte[bytesPerRow*srcHeight]; 458 decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel, 459 bytesPerRow); 460 ByteArrayInputStream byteStream = 461 new ByteArrayInputStream(tmpBuf); 462 stream = new MemoryCacheImageInputStream(byteStream); 463 } else { 464 stream.seek(offset); 465 } 466 467 for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) { 468 // Decode chromaSubsampleV rows 469 for (int x = srcMinX; x < srcMinX + srcWidth; 470 x += chromaSubsampleH) { 471 try { 472 stream.readFully(packet); 473 } catch (EOFException e) { 474 return; 475 } 476 477 byte Cb = packet[elementsPerPacket - 2]; 478 byte Cr = packet[elementsPerPacket - 1]; 479 480 int iCb = 0, iCr = 0, iGCb = 0, iGCr = 0; 481 482 if (colorConvert) { 483 int Cbp = Cb & 0xff; 484 int Crp = Cr & 0xff; 485 486 iCb = iCbTab[Cbp]; 487 iCr = iCrTab[Crp]; 488 489 iGCb = iGCbTab[Cbp]; 490 iGCr = iGCrTab[Crp]; 491 } 492 493 int yIndex = 0; 494 for (int v = 0; v < chromaSubsampleV; v++) { 495 int idx = dstOffset + 3*(x - srcMinX) + 496 scanlineStride*(y - srcMinY + v); 497 498 // Check if we reached the last scanline 499 if (y + v >= srcMinY + srcHeight) { 500 break; 501 } 502 503 for (int h = 0; h < chromaSubsampleH; h++) { 504 if (x + h >= srcMinX + srcWidth) { 505 break; 506 } 507 508 byte Y = packet[yIndex++]; 509 510 if (colorConvert) { 511 int Yp = Y & 0xff; 512 int iY = iYTab[Yp]; 513 int iGY = iGYTab[Yp]; 514 515 int iR = iY + iCr; 516 int iG = iGY + iGCb + iGCr; 517 int iB = iY + iCb; 518 519 byte r = clamp(iR); 520 byte g = clamp(iG); 521 byte b = clamp(iB); 522 523 buf[idx] = r; 524 buf[idx + 1] = g; 525 buf[idx + 2] = b; 526 } else { 527 buf[idx] = Y; 528 buf[idx + 1] = Cb; 529 buf[idx + 2] = Cr; 530 } 531 532 idx += 3; 533 } 534 } 535 } 536 } 537 } 538 } 539