1 /*******************************************************************************
2 * Copyright (c) 2000, 2009 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.internal.image;
15
16
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.graphics.*;
19 import java.io.*;
20
21 final class TIFFDirectory {
22
23 TIFFRandomFileAccess file;
24 boolean isLittleEndian;
25 ImageLoader loader;
26 int depth;
27
28 /* Directory fields */
29 int subfileType;
30 int imageWidth;
31 int imageLength;
32 int[] bitsPerSample;
33 int compression;
34 int photometricInterpretation;
35 int[] stripOffsets;
36 int samplesPerPixel;
37 int rowsPerStrip;
38 int[] stripByteCounts;
39 int t4Options;
40 int colorMapOffset;
41
42 /* Encoder fields */
43 ImageData image;
44 LEDataOutputStream out;
45
46 static final int NO_VALUE = -1;
47
48 static final short TAG_NewSubfileType = 254;
49 static final short TAG_SubfileType = 255;
50 static final short TAG_ImageWidth = 256;
51 static final short TAG_ImageLength = 257;
52 static final short TAG_BitsPerSample = 258;
53 static final short TAG_Compression = 259;
54 static final short TAG_PhotometricInterpretation = 262;
55 static final short TAG_FillOrder = 266;
56 static final short TAG_ImageDescription = 270;
57 static final short TAG_StripOffsets = 273;
58 static final short TAG_Orientation = 274;
59 static final short TAG_SamplesPerPixel = 277;
60 static final short TAG_RowsPerStrip = 278;
61 static final short TAG_StripByteCounts = 279;
62 static final short TAG_XResolution = 282;
63 static final short TAG_YResolution = 283;
64 static final short TAG_PlanarConfiguration = 284;
65 static final short TAG_T4Options = 292;
66 static final short TAG_ResolutionUnit = 296;
67 static final short TAG_Software = 305;
68 static final short TAG_DateTime = 306;
69 static final short TAG_ColorMap = 320;
70
71 static final int TYPE_BYTE = 1;
72 static final int TYPE_ASCII = 2;
73 static final int TYPE_SHORT = 3;
74 static final int TYPE_LONG = 4;
75 static final int TYPE_RATIONAL = 5;
76
77 static final int FILETYPE_REDUCEDIMAGE = 1;
78 static final int FILETYPE_PAGE = 2;
79 static final int FILETYPE_MASK = 4;
80 static final int OFILETYPE_IMAGE = 1;
81 static final int OFILETYPE_REDUCEDIMAGE = 2;
82 static final int OFILETYPE_PAGE = 3;
83
84 /* Different compression schemes */
85 static final int COMPRESSION_NONE = 1;
86 static final int COMPRESSION_CCITT_3_1 = 2;
87 static final int COMPRESSION_PACKBITS = 32773;
88
89 static final int IFD_ENTRY_SIZE = 12;
90
TIFFDirectory(TIFFRandomFileAccess file, boolean isLittleEndian, ImageLoader loader)91 public TIFFDirectory(TIFFRandomFileAccess file, boolean isLittleEndian, ImageLoader loader) {
92 this.file = file;
93 this.isLittleEndian = isLittleEndian;
94 this.loader = loader;
95 }
96
TIFFDirectory(ImageData image)97 public TIFFDirectory(ImageData image) {
98 this.image = image;
99 }
100
101 /* PackBits decoder */
decodePackBits(byte[] src, byte[] dest, int offsetDest)102 int decodePackBits(byte[] src, byte[] dest, int offsetDest) {
103 int destIndex = offsetDest;
104 int srcIndex = 0;
105 while (srcIndex < src.length) {
106 byte n = src[srcIndex];
107 if (n >= 0) {
108 /* Copy next n+1 bytes literally */
109 System.arraycopy(src, ++srcIndex, dest, destIndex, n + 1);
110 srcIndex += n + 1;
111 destIndex += n + 1;
112 } else if (n >= -127) {
113 /* Copy next byte -n+1 times */
114 byte value = src[++srcIndex];
115 for (int j = 0; j < -n + 1; j++) {
116 dest[destIndex++] = value;
117 }
118 srcIndex++;
119 } else {
120 /* Noop when n == -128 */
121 srcIndex++;
122 }
123 }
124 /* Number of bytes copied */
125 return destIndex - offsetDest;
126 }
127
getEntryValue(int type, byte[] buffer, int index)128 int getEntryValue(int type, byte[] buffer, int index) {
129 return toInt(buffer, index + 8, type);
130 }
131
getEntryValue(int type, byte[] buffer, int index, int[] values)132 void getEntryValue(int type, byte[] buffer, int index, int[] values) throws IOException {
133 int start = index + 8;
134 int size;
135 int offset = toInt(buffer, start, TYPE_LONG);
136 switch (type) {
137 case TYPE_SHORT: size = 2; break;
138 case TYPE_LONG: size = 4; break;
139 case TYPE_RATIONAL: size = 8; break;
140 case TYPE_ASCII:
141 case TYPE_BYTE: size = 1; break;
142 default: SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT); return;
143 }
144 if (values.length * size > 4) {
145 buffer = new byte[values.length * size];
146 file.seek(offset);
147 file.read(buffer);
148 start = 0;
149 }
150 for (int i = 0; i < values.length; i++) {
151 values[i] = toInt(buffer, start + i * size, type);
152 }
153 }
154
decodePixels(ImageData image)155 void decodePixels(ImageData image) throws IOException {
156 /* Each row is byte aligned */
157 byte[] imageData = new byte[(imageWidth * depth + 7) / 8 * imageLength];
158 image.data = imageData;
159 int destIndex = 0;
160 int length = stripOffsets.length;
161 for (int i = 0; i < length; i++) {
162 /* Read a strip */
163 byte[] data = new byte[stripByteCounts[i]];
164 file.seek(stripOffsets[i]);
165 file.read(data);
166 if (compression == COMPRESSION_NONE) {
167 System.arraycopy(data, 0, imageData, destIndex, data.length);
168 destIndex += data.length;
169 } else if (compression == COMPRESSION_PACKBITS) {
170 destIndex += decodePackBits(data, imageData, destIndex);
171 } else if (compression == COMPRESSION_CCITT_3_1 || compression == 3) {
172 TIFFModifiedHuffmanCodec codec = new TIFFModifiedHuffmanCodec();
173 int nRows = rowsPerStrip;
174 if (i == length -1) {
175 int n = imageLength % rowsPerStrip;
176 if (n != 0) nRows = n;
177 }
178 destIndex += codec.decode(data, imageData, destIndex, imageWidth, nRows);
179 }
180 if (loader.hasListeners()) {
181 loader.notifyListeners(new ImageLoaderEvent(loader, image, i, i == length - 1));
182 }
183 }
184 }
185
getColorMap()186 PaletteData getColorMap() throws IOException {
187 int numColors = 1 << bitsPerSample[0];
188 /* R, G, B entries are 16 bit wide (2 bytes) */
189 int numBytes = 3 * 2 * numColors;
190 byte[] buffer = new byte[numBytes];
191 file.seek(colorMapOffset);
192 file.read(buffer);
193 RGB[] colors = new RGB[numColors];
194 /**
195 * SWT does not support 16-bit depth color formats.
196 * Convert the 16-bit data to 8-bit data.
197 * The correct way to do this is to multiply each
198 * 16 bit value by the value:
199 * (2^8 - 1) / (2^16 - 1).
200 * The fast way to do this is just to drop the low
201 * byte of the 16-bit value.
202 */
203 int offset = isLittleEndian ? 1 : 0;
204 int startG = 2 * numColors;
205 int startB = startG + 2 * numColors;
206 for (int i = 0; i < numColors; i++) {
207 int r = buffer[offset] & 0xFF;
208 int g = buffer[startG + offset] & 0xFF;
209 int b = buffer[startB + offset] & 0xFF;
210 colors[i] = new RGB(r, g, b);
211 offset += 2;
212 }
213 return new PaletteData(colors);
214 }
215
getGrayPalette()216 PaletteData getGrayPalette() {
217 int numColors = 1 << bitsPerSample[0];
218 RGB[] rgbs = new RGB[numColors];
219 for (int i = 0; i < numColors; i++) {
220 int value = i * 0xFF / (numColors - 1);
221 if (photometricInterpretation == 0) value = 0xFF - value;
222 rgbs[i] = new RGB(value, value, value);
223 }
224 return new PaletteData(rgbs);
225 }
226
getRGBPalette(int bitsR, int bitsG, int bitsB)227 PaletteData getRGBPalette(int bitsR, int bitsG, int bitsB) {
228 int blueMask = 0;
229 for (int i = 0; i < bitsB; i++) {
230 blueMask |= 1 << i;
231 }
232 int greenMask = 0;
233 for (int i = bitsB; i < bitsB + bitsG; i++) {
234 greenMask |= 1 << i;
235 }
236 int redMask = 0;
237 for (int i = bitsB + bitsG; i < bitsB + bitsG + bitsR; i++) {
238 redMask |= 1 << i;
239 }
240 return new PaletteData(redMask, greenMask, blueMask);
241 }
242
formatStrips(int rowByteSize, int nbrRows, byte[] data, int maxStripByteSize, int offsetPostIFD, int extraBytes, int[][] strips)243 int formatStrips(int rowByteSize, int nbrRows, byte[] data, int maxStripByteSize, int offsetPostIFD, int extraBytes, int[][] strips) {
244 /*
245 * Calculate the nbr of required strips given the following requirements:
246 * - each strip should, if possible, not be greater than maxStripByteSize
247 * - each strip should contain 1 or more entire rows
248 *
249 * Format the strip fields arrays so that the image data is stored in one
250 * contiguous block. This block is stored after the IFD and after any tag
251 * info described in the IFD.
252 */
253 int n, nbrRowsPerStrip;
254 if (rowByteSize > maxStripByteSize) {
255 /* Each strip contains 1 row */
256 n = data.length / rowByteSize;
257 nbrRowsPerStrip = 1;
258 } else {
259 int nbr = (data.length + maxStripByteSize - 1) / maxStripByteSize;
260 nbrRowsPerStrip = nbrRows / nbr;
261 n = (nbrRows + nbrRowsPerStrip - 1) / nbrRowsPerStrip;
262 }
263 int stripByteSize = rowByteSize * nbrRowsPerStrip;
264
265 int[] offsets = new int[n];
266 int[] counts = new int[n];
267 /*
268 * Nbr of bytes between the end of the IFD directory and the start of
269 * the image data. Keep space for at least the offsets and counts
270 * data, each field being TYPE_LONG (4 bytes). If other tags require
271 * space between the IFD and the image block, use the extraBytes
272 * parameter.
273 * If there is only one strip, the offsets and counts data is stored
274 * directly in the IFD and we need not reserve space for it.
275 */
276 int postIFDData = n == 1 ? 0 : n * 2 * 4;
277 int startOffset = offsetPostIFD + extraBytes + postIFDData; /* offset of image data */
278
279 int offset = startOffset;
280 for (int i = 0; i < n; i++) {
281 /*
282 * Store all strips sequentially to allow us
283 * to copy all pixels in one contiguous area.
284 */
285 offsets[i] = offset;
286 counts[i] = stripByteSize;
287 offset += stripByteSize;
288 }
289 /* The last strip may contain fewer rows */
290 int mod = data.length % stripByteSize;
291 if (mod != 0) counts[counts.length - 1] = mod;
292
293 strips[0] = offsets;
294 strips[1] = counts;
295 return nbrRowsPerStrip;
296 }
297
formatColorMap(RGB[] rgbs)298 int[] formatColorMap(RGB[] rgbs) {
299 /*
300 * In a TIFF ColorMap, all red come first, followed by
301 * green and blue. All values must be converted from
302 * 8 bit to 16 bit.
303 */
304 int[] colorMap = new int[rgbs.length * 3];
305 int offsetGreen = rgbs.length;
306 int offsetBlue = rgbs.length * 2;
307 for (int i = 0; i < rgbs.length; i++) {
308 colorMap[i] = rgbs[i].red << 8 | rgbs[i].red;
309 colorMap[i + offsetGreen] = rgbs[i].green << 8 | rgbs[i].green;
310 colorMap[i + offsetBlue] = rgbs[i].blue << 8 | rgbs[i].blue;
311 }
312 return colorMap;
313 }
314
parseEntries(byte[] buffer)315 void parseEntries(byte[] buffer) throws IOException {
316 for (int offset = 0; offset < buffer.length; offset += IFD_ENTRY_SIZE) {
317 int tag = toInt(buffer, offset, TYPE_SHORT);
318 int type = toInt(buffer, offset + 2, TYPE_SHORT);
319 int count = toInt(buffer, offset + 4, TYPE_LONG);
320 switch (tag) {
321 case TAG_NewSubfileType: {
322 subfileType = getEntryValue(type, buffer, offset);
323 break;
324 }
325 case TAG_SubfileType: {
326 int oldSubfileType = getEntryValue(type, buffer, offset);
327 subfileType = oldSubfileType == OFILETYPE_REDUCEDIMAGE ? FILETYPE_REDUCEDIMAGE : oldSubfileType == OFILETYPE_PAGE ? FILETYPE_PAGE : 0;
328 break;
329 }
330 case TAG_ImageWidth: {
331 imageWidth = getEntryValue(type, buffer, offset);
332 break;
333 }
334 case TAG_ImageLength: {
335 imageLength = getEntryValue(type, buffer, offset);
336 break;
337 }
338 case TAG_BitsPerSample: {
339 if (type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
340 bitsPerSample = new int[count];
341 getEntryValue(type, buffer, offset, bitsPerSample);
342 break;
343 }
344 case TAG_Compression: {
345 compression = getEntryValue(type, buffer, offset);
346 break;
347 }
348 case TAG_FillOrder: {
349 /* Ignored: baseline only requires default fill order. */
350 break;
351 }
352 case TAG_ImageDescription: {
353 /* Ignored */
354 break;
355 }
356 case TAG_PhotometricInterpretation: {
357 photometricInterpretation = getEntryValue(type, buffer, offset);
358 break;
359 }
360 case TAG_StripOffsets: {
361 if (type != TYPE_LONG && type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
362 stripOffsets = new int[count];
363 getEntryValue(type, buffer, offset, stripOffsets);
364 break;
365 }
366 case TAG_Orientation: {
367 /* Ignored: baseline only requires top left orientation. */
368 break;
369 }
370 case TAG_SamplesPerPixel: {
371 if (type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
372 samplesPerPixel = getEntryValue(type, buffer, offset);
373 /* Only the basic 1 and 3 values are supported */
374 if (samplesPerPixel != 1 && samplesPerPixel != 3) SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
375 break;
376 }
377 case TAG_RowsPerStrip: {
378 rowsPerStrip = getEntryValue(type, buffer, offset);
379 break;
380 }
381 case TAG_StripByteCounts: {
382 stripByteCounts = new int[count];
383 getEntryValue(type, buffer, offset, stripByteCounts);
384 break;
385 }
386 case TAG_XResolution: {
387 /* Ignored */
388 break;
389 }
390 case TAG_YResolution: {
391 /* Ignored */
392 break;
393 }
394 case TAG_PlanarConfiguration: {
395 /* Ignored: baseline only requires default planar configuration. */
396 break;
397 }
398 case TAG_T4Options: {
399 if (type != TYPE_LONG) SWT.error(SWT.ERROR_INVALID_IMAGE);
400 t4Options = getEntryValue(type, buffer, offset);
401 if ((t4Options & 0x1) == 1) {
402 /* 2-dimensional coding is not supported */
403 SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
404 }
405 break;
406 }
407 case TAG_ResolutionUnit: {
408 /* Ignored */
409 break;
410 }
411 case TAG_Software: {
412 /* Ignored */
413 break;
414 }
415 case TAG_DateTime: {
416 /* Ignored */
417 break;
418 }
419 case TAG_ColorMap: {
420 if (type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
421 /* Get the offset of the colorMap (use TYPE_LONG) */
422 colorMapOffset = getEntryValue(TYPE_LONG, buffer, offset);
423 break;
424 }
425 }
426 }
427 }
428
read(int [] nextIFDOffset)429 public ImageData read(int [] nextIFDOffset) throws IOException {
430 /* Set TIFF default values */
431 bitsPerSample = new int[] {1};
432 colorMapOffset = NO_VALUE;
433 compression = 1;
434 imageLength = NO_VALUE;
435 imageWidth = NO_VALUE;
436 photometricInterpretation = NO_VALUE;
437 rowsPerStrip = Integer.MAX_VALUE;
438 samplesPerPixel = 1;
439 stripByteCounts = null;
440 stripOffsets = null;
441
442 byte[] buffer = new byte[2];
443 file.read(buffer);
444 int numberEntries = toInt(buffer, 0, TYPE_SHORT);
445 buffer = new byte[IFD_ENTRY_SIZE * numberEntries];
446 file.read(buffer);
447 byte buffer2[] = new byte[4];
448 file.read(buffer2);
449 nextIFDOffset[0] = toInt(buffer2, 0, TYPE_LONG);
450 parseEntries(buffer);
451
452 PaletteData palette = null;
453 depth = 0;
454 switch (photometricInterpretation) {
455 case 0:
456 case 1: {
457 /* Bilevel or Grayscale image */
458 palette = getGrayPalette();
459 depth = bitsPerSample[0];
460 break;
461 }
462 case 2: {
463 /* RGB image */
464 if (colorMapOffset != NO_VALUE) SWT.error(SWT.ERROR_INVALID_IMAGE);
465 /* SamplesPerPixel 3 is the only value supported */
466 palette = getRGBPalette(bitsPerSample[0], bitsPerSample[1], bitsPerSample[2]);
467 depth = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2];
468 break;
469 }
470 case 3: {
471 /* Palette Color image */
472 if (colorMapOffset == NO_VALUE) SWT.error(SWT.ERROR_INVALID_IMAGE);
473 palette = getColorMap();
474 depth = bitsPerSample[0];
475 break;
476 }
477 default: {
478 SWT.error(SWT.ERROR_INVALID_IMAGE);
479 }
480 }
481
482 ImageData image = ImageData.internal_new(
483 imageWidth,
484 imageLength,
485 depth,
486 palette,
487 1,
488 null,
489 0,
490 null,
491 null,
492 -1,
493 -1,
494 SWT.IMAGE_TIFF,
495 0,
496 0,
497 0,
498 0);
499 decodePixels(image);
500 return image;
501 }
502
toInt(byte[] buffer, int i, int type)503 int toInt(byte[] buffer, int i, int type) {
504 if (type == TYPE_LONG) {
505 return isLittleEndian ?
506 (buffer[i] & 0xFF) | ((buffer[i + 1] & 0xFF) << 8) | ((buffer[i + 2] & 0xFF) << 16) | ((buffer[i + 3] & 0xFF) << 24) :
507 (buffer[i + 3] & 0xFF) | ((buffer[i + 2] & 0xFF) << 8) | ((buffer[i + 1] & 0xFF) << 16) | ((buffer[i] & 0xFF) << 24);
508 }
509 if (type == TYPE_SHORT) {
510 return isLittleEndian ?
511 (buffer[i] & 0xFF) | ((buffer[i + 1] & 0xFF) << 8) :
512 (buffer[i + 1] & 0xFF) | ((buffer[i] & 0xFF) << 8);
513 }
514 /* Invalid type */
515 SWT.error(SWT.ERROR_INVALID_IMAGE);
516 return -1;
517 }
518
write(int photometricInterpretation)519 void write(int photometricInterpretation) throws IOException {
520 boolean isRGB = photometricInterpretation == 2;
521 boolean isColorMap = photometricInterpretation == 3;
522 boolean isBiLevel = photometricInterpretation == 0 || photometricInterpretation == 1;
523
524 int imageWidth = image.width;
525 int imageLength = image.height;
526 int rowByteSize = image.bytesPerLine;
527
528 int numberEntries = isBiLevel ? 9 : 11;
529 int lengthDirectory = 2 + 12 * numberEntries + 4;
530 /* Offset following the header and the directory */
531 int nextOffset = 8 + lengthDirectory;
532
533 /* Extra space used by XResolution and YResolution values */
534 int extraBytes = 16;
535
536 int[] colorMap = null;
537 if (isColorMap) {
538 PaletteData palette = image.palette;
539 RGB[] rgbs = palette.getRGBs();
540 colorMap = formatColorMap(rgbs);
541 /* The number of entries of the Color Map must match the bitsPerSample field */
542 if (colorMap.length != 3 * 1 << image.depth) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
543 /* Extra space used by ColorMap values */
544 extraBytes += colorMap.length * 2;
545 }
546 if (isRGB) {
547 /* Extra space used by BitsPerSample values */
548 extraBytes += 6;
549 }
550 /* TIFF recommends storing the data in strips of no more than 8 Ko */
551 byte[] data = image.data;
552 int[][] strips = new int[2][];
553 int nbrRowsPerStrip = formatStrips(rowByteSize, imageLength, data, 8192, nextOffset, extraBytes, strips);
554 int[] stripOffsets = strips[0];
555 int[] stripByteCounts = strips[1];
556
557 int bitsPerSampleOffset = NO_VALUE;
558 if (isRGB) {
559 bitsPerSampleOffset = nextOffset;
560 nextOffset += 6;
561 }
562 int stripOffsetsOffset = NO_VALUE, stripByteCountsOffset = NO_VALUE;
563 int xResolutionOffset, yResolutionOffset, colorMapOffset = NO_VALUE;
564 int cnt = stripOffsets.length;
565 if (cnt > 1) {
566 stripOffsetsOffset = nextOffset;
567 nextOffset += 4 * cnt;
568 stripByteCountsOffset = nextOffset;
569 nextOffset += 4 * cnt;
570 }
571 xResolutionOffset = nextOffset;
572 nextOffset += 8;
573 yResolutionOffset = nextOffset;
574 nextOffset += 8;
575 if (isColorMap) {
576 colorMapOffset = nextOffset;
577 nextOffset += colorMap.length * 2;
578 }
579 /* TIFF header */
580 writeHeader();
581
582 /* Image File Directory */
583 out.writeShort(numberEntries);
584 writeEntry(TAG_ImageWidth, TYPE_LONG, 1, imageWidth);
585 writeEntry(TAG_ImageLength, TYPE_LONG, 1, imageLength);
586 if (isColorMap) writeEntry(TAG_BitsPerSample, TYPE_SHORT, 1, image.depth);
587 if (isRGB) writeEntry(TAG_BitsPerSample, TYPE_SHORT, 3, bitsPerSampleOffset);
588 writeEntry(TAG_Compression, TYPE_SHORT, 1, COMPRESSION_NONE);
589 writeEntry(TAG_PhotometricInterpretation, TYPE_SHORT, 1, photometricInterpretation);
590 writeEntry(TAG_StripOffsets, TYPE_LONG, cnt, cnt > 1 ? stripOffsetsOffset : stripOffsets[0]);
591 if (isRGB) writeEntry(TAG_SamplesPerPixel, TYPE_SHORT, 1, 3);
592 writeEntry(TAG_RowsPerStrip, TYPE_LONG, 1, nbrRowsPerStrip);
593 writeEntry(TAG_StripByteCounts, TYPE_LONG, cnt, cnt > 1 ? stripByteCountsOffset : stripByteCounts[0]);
594 writeEntry(TAG_XResolution, TYPE_RATIONAL, 1, xResolutionOffset);
595 writeEntry(TAG_YResolution, TYPE_RATIONAL, 1, yResolutionOffset);
596 if (isColorMap) writeEntry(TAG_ColorMap, TYPE_SHORT, colorMap.length, colorMapOffset);
597 /* Offset of next IFD (0 for last IFD) */
598 out.writeInt(0);
599
600 /* Values longer than 4 bytes Section */
601
602 /* BitsPerSample 8,8,8 */
603 if (isRGB) for (int i = 0; i < 3; i++) out.writeShort(8);
604 if (cnt > 1) {
605 for (int i = 0; i < cnt; i++) out.writeInt(stripOffsets[i]);
606 for (int i = 0; i < cnt; i++) out.writeInt(stripByteCounts[i]);
607 }
608 /* XResolution and YResolution set to 300 dpi */
609 for (int i = 0; i < 2; i++) {
610 out.writeInt(300);
611 out.writeInt(1);
612 }
613 /* ColorMap */
614 if (isColorMap)
615 for (int element : colorMap)
616 out.writeShort(element);
617
618 /* Image Data */
619 out.write(data);
620 }
621
writeEntry(short tag, int type, int count, int value)622 void writeEntry(short tag, int type, int count, int value) throws IOException {
623 out.writeShort(tag);
624 out.writeShort(type);
625 out.writeInt(count);
626 out.writeInt(value);
627 }
628
writeHeader()629 void writeHeader() throws IOException {
630 /* little endian */
631 out.write(0x49);
632 out.write(0x49);
633
634 /* TIFF identifier */
635 out.writeShort(42);
636 /*
637 * Offset of the first IFD is chosen to be 8.
638 * It is word aligned and immediately after this header.
639 */
640 out.writeInt(8);
641 }
642
writeToStream(LEDataOutputStream byteStream)643 void writeToStream(LEDataOutputStream byteStream) throws IOException {
644 out = byteStream;
645 int photometricInterpretation = -1;
646
647 /* Scanline pad must be 1 */
648 if (image.scanlinePad != 1) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
649 switch (image.depth) {
650 case 1: {
651 /* Palette must be black and white or white and black */
652 PaletteData palette = image.palette;
653 RGB[] rgbs = palette.colors;
654 if (palette.isDirect || rgbs == null || rgbs.length != 2) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
655 RGB rgb0 = rgbs[0];
656 RGB rgb1 = rgbs[1];
657 if (!(rgb0.red == rgb0.green && rgb0.green == rgb0.blue &&
658 rgb1.red == rgb1.green && rgb1.green == rgb1.blue &&
659 ((rgb0.red == 0x0 && rgb1.red == 0xFF) || (rgb0.red == 0xFF && rgb1.red == 0x0)))) {
660 SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
661 }
662 /* 0 means a color index of 0 is imaged as white */
663 photometricInterpretation = image.palette.colors[0].red == 0xFF ? 0 : 1;
664 break;
665 }
666 case 4:
667 case 8: {
668 photometricInterpretation = 3;
669 break;
670 }
671 case 24: {
672 photometricInterpretation = 2;
673 break;
674 }
675 default: {
676 SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
677 }
678 }
679 write(photometricInterpretation);
680 }
681
682 }
683