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