| 1 18 package org.apache.batik.ext.awt.image.codec.tiff; 19 20 import java.awt.Rectangle ; 21 import java.awt.color.ColorSpace ; 22 import java.awt.image.BufferedImage ; 23 import java.awt.image.ColorModel ; 24 import java.awt.image.ComponentSampleModel ; 25 import java.awt.image.DataBuffer ; 26 import java.awt.image.DataBufferByte ; 27 import java.awt.image.IndexColorModel ; 28 import java.awt.image.MultiPixelPackedSampleModel ; 29 import java.awt.image.Raster ; 30 import java.awt.image.RenderedImage ; 31 import java.awt.image.SampleModel ; 32 import java.awt.image.WritableRaster ; 33 import java.io.ByteArrayOutputStream ; 34 import java.io.File ; 35 import java.io.FileInputStream ; 36 import java.io.IOException ; 37 import java.io.OutputStream ; 38 import java.io.RandomAccessFile ; 39 import java.util.ArrayList ; 40 import java.util.Iterator ; 41 import java.util.SortedSet ; 42 import java.util.TreeSet ; 43 import java.util.zip.Deflater ; 44 45 import org.apache.batik.ext.awt.image.codec.ImageEncodeParam; 46 import org.apache.batik.ext.awt.image.codec.ImageEncoderImpl; 47 import org.apache.batik.ext.awt.image.codec.SeekableOutputStream; 48 49 import com.sun.image.codec.jpeg.JPEGEncodeParam; 50 import com.sun.image.codec.jpeg.JPEGQTable; 51 52 57 public class TIFFImageEncoder extends ImageEncoderImpl { 58 59 private static final int TIFF_UNSUPPORTED = -1; 61 private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0; 62 private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1; 63 private static final int TIFF_GRAY = 2; 64 private static final int TIFF_PALETTE = 3; 65 private static final int TIFF_RGB = 4; 66 private static final int TIFF_CMYK = 5; 67 private static final int TIFF_YCBCR = 6; 68 private static final int TIFF_CIELAB = 7; 69 private static final int TIFF_GENERIC = 8; 70 71 private static final int COMP_NONE = 1; 73 private static final int COMP_JPEG_TTN2 = 7; 74 private static final int COMP_PACKBITS = 32773; 75 private static final int COMP_DEFLATE = 32946; 76 77 private static final int TIFF_JPEG_TABLES = 347; 79 private static final int TIFF_YCBCR_SUBSAMPLING = 530; 80 private static final int TIFF_YCBCR_POSITIONING = 531; 81 private static final int TIFF_REF_BLACK_WHITE = 532; 82 83 private static final int EXTRA_SAMPLE_UNSPECIFIED = 0; 85 private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA = 1; 86 private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2; 87 88 private static final int DEFAULT_ROWS_PER_STRIP = 8; 90 91 public TIFFImageEncoder(OutputStream output, ImageEncodeParam param) { 92 super(output, param); 93 if (this.param == null) { 94 this.param = new TIFFEncodeParam(); 95 } 96 } 97 98 102 public void encode(RenderedImage im) throws IOException { 103 writeFileHeader(); 105 106 TIFFEncodeParam encodeParam = (TIFFEncodeParam)param; 108 109 Iterator iter = encodeParam.getExtraImages(); 110 if(iter != null) { 111 int ifdOffset = 8; 112 RenderedImage nextImage = im; 113 TIFFEncodeParam nextParam = encodeParam; 114 boolean hasNext; 115 do { 116 hasNext = iter.hasNext(); 117 ifdOffset = encode(nextImage, nextParam, ifdOffset, !hasNext); 118 if(hasNext) { 119 Object obj = iter.next(); 120 if(obj instanceof RenderedImage ) { 121 nextImage = (RenderedImage )obj; 122 nextParam = encodeParam; 123 } else if(obj instanceof Object []) { 124 Object [] o = (Object [])obj; 125 nextImage = (RenderedImage )o[0]; 126 nextParam = (TIFFEncodeParam)o[1]; 127 } 128 } 129 } while(hasNext); 130 } else { 131 encode(im, encodeParam, 8, true); 132 } 133 } 134 135 private int encode(RenderedImage im, TIFFEncodeParam encodeParam, 136 int ifdOffset, boolean isLast) throws IOException { 137 int compression = encodeParam.getCompression(); 139 140 boolean isTiled = encodeParam.getWriteTiled(); 142 143 int minX = im.getMinX(); 145 int minY = im.getMinY(); 146 int width = im.getWidth(); 147 int height = im.getHeight(); 148 149 SampleModel sampleModel = im.getSampleModel(); 151 152 int sampleSize[] = sampleModel.getSampleSize(); 154 for(int i = 1; i < sampleSize.length; i++) { 155 if(sampleSize[i] != sampleSize[0]) { 156 throw new Error ("TIFFImageEncoder0"); 157 } 158 } 159 160 int numBands = sampleModel.getNumBands(); 162 if((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) { 163 throw new Error ("TIFFImageEncoder1"); 164 } 165 166 int dataType = sampleModel.getDataType(); 168 switch(dataType) { 169 case DataBuffer.TYPE_BYTE: 170 if(sampleSize[0] != 1 && sampleSize[0] == 4 && 171 sampleSize[0] != 8) { 172 throw new Error ("TIFFImageEncoder2"); 173 } 174 break; 175 case DataBuffer.TYPE_SHORT: 176 case DataBuffer.TYPE_USHORT: 177 if(sampleSize[0] != 16) { 178 throw new Error ("TIFFImageEncoder3"); 179 } 180 break; 181 case DataBuffer.TYPE_INT: 182 case DataBuffer.TYPE_FLOAT: 183 if(sampleSize[0] != 32) { 184 throw new Error ("TIFFImageEncoder4"); 185 } 186 break; 187 default: 188 throw new Error ("TIFFImageEncoder5"); 189 } 190 191 boolean dataTypeIsShort = 192 dataType == DataBuffer.TYPE_SHORT || 193 dataType == DataBuffer.TYPE_USHORT; 194 195 ColorModel colorModel = im.getColorModel(); 196 if (colorModel != null && 197 colorModel instanceof IndexColorModel && 198 dataType != DataBuffer.TYPE_BYTE) { 199 throw new Error ("TIFFImageEncoder6"); 201 } 202 IndexColorModel icm = null; 203 int sizeOfColormap = 0; 204 char colormap[] = null; 205 206 int imageType = TIFF_UNSUPPORTED; 208 int numExtraSamples = 0; 209 int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED; 210 if(colorModel instanceof IndexColorModel ) { icm = (IndexColorModel )colorModel; 212 int mapSize = icm.getMapSize(); 213 214 if(sampleSize[0] == 1 && numBands == 1) { 216 if (mapSize != 2) { 217 throw new IllegalArgumentException ( 218 "TIFFImageEncoder7"); 219 } 220 221 byte r[] = new byte[mapSize]; 222 icm.getReds(r); 223 byte g[] = new byte[mapSize]; 224 icm.getGreens(g); 225 byte b[] = new byte[mapSize]; 226 icm.getBlues(b); 227 228 if ((r[0] & 0xff) == 0 && 229 (r[1] & 0xff) == 255 && 230 (g[0] & 0xff) == 0 && 231 (g[1] & 0xff) == 255 && 232 (b[0] & 0xff) == 0 && 233 (b[1] & 0xff) == 255) { 234 235 imageType = TIFF_BILEVEL_BLACK_IS_ZERO; 236 237 } else if ((r[0] & 0xff) == 255 && 238 (r[1] & 0xff) == 0 && 239 (g[0] & 0xff) == 255 && 240 (g[1] & 0xff) == 0 && 241 (b[0] & 0xff) == 255 && 242 (b[1] & 0xff) == 0) { 243 244 imageType = TIFF_BILEVEL_WHITE_IS_ZERO; 245 246 } else { 247 imageType = TIFF_PALETTE; 248 } 249 250 } else if(numBands == 1) { imageType = TIFF_PALETTE; 253 } 254 } else if(colorModel == null) { 255 256 if(sampleSize[0] == 1 && numBands == 1) { imageType = TIFF_BILEVEL_BLACK_IS_ZERO; 258 } else { imageType = TIFF_GENERIC; 260 if(numBands > 1) { 261 numExtraSamples = numBands - 1; 262 } 263 } 264 265 } else { ColorSpace colorSpace = colorModel.getColorSpace(); 267 268 switch(colorSpace.getType()) { 269 case ColorSpace.TYPE_CMYK: 270 imageType = TIFF_CMYK; 271 break; 272 case ColorSpace.TYPE_GRAY: 273 imageType = TIFF_GRAY; 274 break; 275 case ColorSpace.TYPE_Lab: 276 imageType = TIFF_CIELAB; 277 break; 278 case ColorSpace.TYPE_RGB: 279 if(compression == COMP_JPEG_TTN2 && 280 encodeParam.getJPEGCompressRGBToYCbCr()) { 281 imageType = TIFF_YCBCR; 282 } else { 283 imageType = TIFF_RGB; 284 } 285 break; 286 case ColorSpace.TYPE_YCbCr: 287 imageType = TIFF_YCBCR; 288 break; 289 default: 290 imageType = TIFF_GENERIC; break; 292 } 293 294 if(imageType == TIFF_GENERIC) { 295 numExtraSamples = numBands - 1; 296 } else if(numBands > 1) { 297 numExtraSamples = numBands - colorSpace.getNumComponents(); 298 } 299 300 if(numExtraSamples == 1 && colorModel.hasAlpha()) { 301 extraSampleType = colorModel.isAlphaPremultiplied() ? 302 EXTRA_SAMPLE_ASSOCIATED_ALPHA : 303 EXTRA_SAMPLE_UNASSOCIATED_ALPHA; 304 } 305 } 306 307 if(imageType == TIFF_UNSUPPORTED) { 308 throw new Error ("TIFFImageEncoder8"); 309 } 310 311 if(compression == COMP_JPEG_TTN2) { 313 if(imageType == TIFF_PALETTE) { 314 throw new Error ("TIFFImageEncoder11"); 315 } else if(!(sampleSize[0] == 8 && 316 (imageType == TIFF_GRAY || 317 imageType == TIFF_RGB || 318 imageType == TIFF_YCBCR))) { 319 throw new Error ("TIFFImageEncoder9"); 320 } 321 } 322 323 int photometricInterpretation = -1; 324 switch (imageType) { 325 326 case TIFF_BILEVEL_WHITE_IS_ZERO: 327 photometricInterpretation = 0; 328 break; 329 330 case TIFF_BILEVEL_BLACK_IS_ZERO: 331 photometricInterpretation = 1; 332 break; 333 334 case TIFF_GRAY: 335 case TIFF_GENERIC: 336 photometricInterpretation = 1; 338 break; 339 340 case TIFF_PALETTE: 341 photometricInterpretation = 3; 342 343 icm = (IndexColorModel )colorModel; 344 sizeOfColormap = icm.getMapSize(); 345 346 byte r[] = new byte[sizeOfColormap]; 347 icm.getReds(r); 348 byte g[] = new byte[sizeOfColormap]; 349 icm.getGreens(g); 350 byte b[] = new byte[sizeOfColormap]; 351 icm.getBlues(b); 352 353 int redIndex = 0, greenIndex = sizeOfColormap; 354 int blueIndex = 2 * sizeOfColormap; 355 colormap = new char[sizeOfColormap * 3]; 356 for (int i=0; i<sizeOfColormap; i++) { 357 colormap[redIndex++] = (char)(((r[i] << 8) | r[i]) & 0xffff); 358 colormap[greenIndex++] = (char)(((g[i] << 8) | g[i]) & 0xffff); 359 colormap[blueIndex++] = (char)(((b[i] << 8) | b[i]) & 0xffff); 360 } 361 362 sizeOfColormap *= 3; 363 364 break; 365 366 case TIFF_RGB: 367 photometricInterpretation = 2; 368 break; 369 370 case TIFF_CMYK: 371 photometricInterpretation = 5; 372 break; 373 374 case TIFF_YCBCR: 375 photometricInterpretation = 6; 376 break; 377 378 case TIFF_CIELAB: 379 photometricInterpretation = 8; 380 break; 381 382 default: 383 throw new Error ("TIFFImageEncoder8"); 384 } 385 386 int tileWidth; 388 int tileHeight; 389 if(isTiled) { 390 tileWidth = encodeParam.getTileWidth() > 0 ? 391 encodeParam.getTileWidth() : im.getTileWidth(); 392 tileHeight = encodeParam.getTileHeight() > 0 ? 393 encodeParam.getTileHeight() : im.getTileHeight(); 394 } else { 395 tileWidth = width; 396 397 tileHeight = encodeParam.getTileHeight() > 0 ? 398 encodeParam.getTileHeight() : DEFAULT_ROWS_PER_STRIP; 399 } 400 401 JPEGEncodeParam jep = null; 403 if(compression == COMP_JPEG_TTN2) { 404 jep = encodeParam.getJPEGEncodeParam(); 406 407 int maxSubH = jep.getHorizontalSubsampling(0); 409 int maxSubV = jep.getVerticalSubsampling(0); 410 for(int i = 1; i < numBands; i++) { 411 int subH = jep.getHorizontalSubsampling(i); 412 if(subH > maxSubH) { 413 maxSubH = subH; 414 } 415 int subV = jep.getVerticalSubsampling(i); 416 if(subV > maxSubV) { 417 maxSubV = subV; 418 } 419 } 420 421 int factorV = 8*maxSubV; 422 tileHeight = 423 (int)((float)tileHeight/(float)factorV + 0.5F)*factorV; 424 if(tileHeight < factorV) { 425 tileHeight = factorV; 426 } 427 428 if(isTiled) { 429 int factorH = 8*maxSubH; 430 tileWidth = 431 (int)((float)tileWidth/(float)factorH + 0.5F)*factorH; 432 if(tileWidth < factorH) { 433 tileWidth = factorH; 434 } 435 } 436 } 437 438 int numTiles; 439 if(isTiled) { 440 numTiles = 442 ((width + tileWidth - 1)/tileWidth) * 443 ((height + tileHeight - 1)/tileHeight); 444 } else { 445 numTiles = (int)Math.ceil((double)height/(double)tileHeight); 446 } 447 448 long tileByteCounts[] = new long[numTiles]; 449 450 long bytesPerRow = 451 (long)Math.ceil((sampleSize[0] / 8.0) * tileWidth * numBands); 452 453 long bytesPerTile = bytesPerRow * tileHeight; 454 455 for (int i=0; i<numTiles; i++) { 456 tileByteCounts[i] = bytesPerTile; 457 } 458 459 if(!isTiled) { 460 long lastStripRows = height - (tileHeight * (numTiles-1)); 462 tileByteCounts[numTiles-1] = lastStripRows * bytesPerRow; 463 } 464 465 long totalBytesOfData = bytesPerTile * (numTiles - 1) + 466 tileByteCounts[numTiles-1]; 467 468 long tileOffsets[] = new long[numTiles]; 471 472 485 SortedSet fields = new TreeSet (); 487 488 fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_WIDTH, 490 TIFFField.TIFF_LONG, 1, 491 new long[] {width})); 492 493 fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_LENGTH, 495 TIFFField.TIFF_LONG, 1, 496 new long[] {height})); 497 498 char [] shortSampleSize = new char[numBands]; 499 for (int i=0; i<numBands; i++) 500 shortSampleSize[i] = (char)sampleSize[i]; 501 fields.add(new TIFFField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE, 502 TIFFField.TIFF_SHORT, numBands, 503 shortSampleSize)); 504 505 fields.add(new TIFFField(TIFFImageDecoder.TIFF_COMPRESSION, 506 TIFFField.TIFF_SHORT, 1, 507 new char[] {(char)compression})); 508 509 fields.add( 510 new TIFFField(TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION, 511 TIFFField.TIFF_SHORT, 1, 512 new char[] {(char)photometricInterpretation})); 513 514 if(!isTiled) { 515 fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_OFFSETS, 516 TIFFField.TIFF_LONG, numTiles, 517 tileOffsets)); 518 } 519 520 fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLES_PER_PIXEL, 521 TIFFField.TIFF_SHORT, 1, 522 new char[] {(char)numBands})); 523 524 if(!isTiled) { 525 fields.add(new TIFFField(TIFFImageDecoder.TIFF_ROWS_PER_STRIP, 526 TIFFField.TIFF_LONG, 1, 527 new long[] {tileHeight})); 528 529 fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_BYTE_COUNTS, 530 TIFFField.TIFF_LONG, numTiles, 531 tileByteCounts)); 532 } 533 534 if (colormap != null) { 535 fields.add(new TIFFField(TIFFImageDecoder.TIFF_COLORMAP, 536 TIFFField.TIFF_SHORT, sizeOfColormap, 537 colormap)); 538 } 539 540 if(isTiled) { 541 fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_WIDTH, 542 TIFFField.TIFF_LONG, 1, 543 new long[] {tileWidth})); 544 545 fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_LENGTH, 546 TIFFField.TIFF_LONG, 1, 547 new long[] {tileHeight})); 548 549 fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_OFFSETS, 550 TIFFField.TIFF_LONG, numTiles, 551 tileOffsets)); 552 553 fields.add(new TIFFField(TIFFImageDecoder.TIFF_TILE_BYTE_COUNTS, 554 TIFFField.TIFF_LONG, numTiles, 555 tileByteCounts)); 556 } 557 558 if(numExtraSamples > 0) { 559 char[] extraSamples = new char[numExtraSamples]; 560 for(int i = 0; i < numExtraSamples; i++) { 561 extraSamples[i] = (char)extraSampleType; 562 } 563 fields.add(new TIFFField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES, 564 TIFFField.TIFF_SHORT, numExtraSamples, 565 extraSamples)); 566 } 567 568 if(dataType != DataBuffer.TYPE_BYTE) { 570 char[] sampleFormat = new char[numBands]; 572 if(dataType == DataBuffer.TYPE_FLOAT) { 573 sampleFormat[0] = 3; 574 } else if(dataType == DataBuffer.TYPE_USHORT) { 575 sampleFormat[0] = 1; 576 } else { 577 sampleFormat[0] = 2; 578 } 579 for(int b = 1; b < numBands; b++) { 580 sampleFormat[b] = sampleFormat[0]; 581 } 582 fields.add(new TIFFField(TIFFImageDecoder.TIFF_SAMPLE_FORMAT, 583 TIFFField.TIFF_SHORT, numBands, 584 sampleFormat)); 585 586 } 592 593 com.sun.image.codec.jpeg.JPEGEncodeParam jpegEncodeParam = null; 595 com.sun.image.codec.jpeg.JPEGImageEncoder jpegEncoder = null; 596 int jpegColorID = 0; 597 598 if(compression == COMP_JPEG_TTN2) { 599 600 jpegColorID = 602 com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_UNKNOWN; 603 switch(imageType) { 604 case TIFF_GRAY: 605 case TIFF_PALETTE: 606 jpegColorID = 607 com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_GRAY; 608 break; 609 case TIFF_RGB: 610 jpegColorID = 611 com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_RGB; 612 break; 613 case TIFF_YCBCR: 614 jpegColorID = 615 com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_YCbCr; 616 break; 617 } 618 619 Raster tile00 = im.getTile(0, 0); 621 jpegEncodeParam = 622 com.sun.image.codec.jpeg.JPEGCodec.getDefaultJPEGEncodeParam( 623 tile00, jpegColorID); 624 625 modifyEncodeParam(jep, jpegEncodeParam, numBands); 626 627 jpegEncodeParam.setImageInfoValid(false); 629 jpegEncodeParam.setTableInfoValid(true); 630 ByteArrayOutputStream tableStream = 631 new ByteArrayOutputStream (); 632 jpegEncoder = 633 com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder( 634 tableStream, 635 jpegEncodeParam); 636 jpegEncoder.encode(tile00); 637 byte[] tableData = tableStream.toByteArray(); 638 fields.add(new TIFFField(TIFF_JPEG_TABLES, 639 TIFFField.TIFF_UNDEFINED, 640 tableData.length, 641 tableData)); 642 643 jpegEncoder = null; 645 } 646 647 if(imageType == TIFF_YCBCR) { 648 char subsampleH = 1; 651 char subsampleV = 1; 652 653 if(compression == COMP_JPEG_TTN2) { 655 subsampleH = (char)jep.getHorizontalSubsampling(0); 657 subsampleV = (char)jep.getVerticalSubsampling(0); 658 for(int i = 1; i < numBands; i++) { 659 char subH = (char)jep.getHorizontalSubsampling(i); 660 if(subH > subsampleH) { 661 subsampleH = subH; 662 } 663 char subV = (char)jep.getVerticalSubsampling(i); 664 if(subV > subsampleV) { 665 subsampleV = subV; 666 } 667 } 668 } 669 670 fields.add(new TIFFField(TIFF_YCBCR_SUBSAMPLING, 671 TIFFField.TIFF_SHORT, 2, 672 new char[] {subsampleH, subsampleV})); 673 674 675 fields.add(new TIFFField(TIFF_YCBCR_POSITIONING, 677 TIFFField.TIFF_SHORT, 1, 678 new int[] {compression == COMP_JPEG_TTN2 ? 679 1 : 2})); 680 681 long[][] refbw; 683 if(compression == COMP_JPEG_TTN2) { 684 refbw = 685 new long[][] { {0, 1}, {255, 1}, {128, 1}, {255, 1}, {128, 1}, {255, 1} 687 }; 688 } else { 689 refbw = 690 new long[][] { {15, 1}, {235, 1}, {128, 1}, {240, 1}, {128, 1}, {240, 1} 692 }; 693 } 694 fields.add(new TIFFField(TIFF_REF_BLACK_WHITE, 695 TIFFField.TIFF_RATIONAL, 6, 696 refbw)); 697 } 698 699 702 TIFFField[] extraFields = encodeParam.getExtraFields(); 704 if(extraFields != null) { 705 ArrayList extantTags = new ArrayList (fields.size()); 706 Iterator fieldIter = fields.iterator();
|