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