]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/internal/image/TIFFDirectory.java
Work around SWT 4.13 - 4.18 Win32 DnD bug 567422
[simantics/platform.git] / bundles / org.eclipse.swt.win32.win32.x86_64 / src / org / eclipse / swt / internal / image / TIFFDirectory.java
diff --git a/bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/internal/image/TIFFDirectory.java b/bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/internal/image/TIFFDirectory.java
new file mode 100644 (file)
index 0000000..0be7e94
--- /dev/null
@@ -0,0 +1,680 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2009 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.swt.internal.image;
+
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import java.io.*;
+
+final class TIFFDirectory {
+
+       TIFFRandomFileAccess file;
+       boolean isLittleEndian;
+       ImageLoader loader;
+       int depth;
+
+       /* Directory fields */
+       int subfileType;
+       int imageWidth;
+       int imageLength;
+       int[] bitsPerSample;
+       int compression;
+       int photometricInterpretation;
+       int[] stripOffsets;
+       int samplesPerPixel;
+       int rowsPerStrip;
+       int[] stripByteCounts;
+       int t4Options;
+       int colorMapOffset;
+
+       /* Encoder fields */
+       ImageData image;
+       LEDataOutputStream out;
+
+       static final int NO_VALUE = -1;
+
+       static final short TAG_NewSubfileType = 254;
+       static final short TAG_SubfileType = 255;
+       static final short TAG_ImageWidth = 256;
+       static final short TAG_ImageLength = 257;
+       static final short TAG_BitsPerSample = 258;
+       static final short TAG_Compression = 259;
+       static final short TAG_PhotometricInterpretation = 262;
+       static final short TAG_FillOrder = 266;
+       static final short TAG_ImageDescription = 270;
+       static final short TAG_StripOffsets = 273;
+       static final short TAG_Orientation = 274;
+       static final short TAG_SamplesPerPixel = 277;
+       static final short TAG_RowsPerStrip = 278;
+       static final short TAG_StripByteCounts = 279;
+       static final short TAG_XResolution = 282;
+       static final short TAG_YResolution = 283;
+       static final short TAG_PlanarConfiguration = 284;
+       static final short TAG_T4Options = 292;
+       static final short TAG_ResolutionUnit = 296;
+       static final short TAG_Software = 305;
+       static final short TAG_DateTime = 306;
+       static final short TAG_ColorMap = 320;
+
+       static final int TYPE_BYTE = 1;
+       static final int TYPE_ASCII = 2;
+       static final int TYPE_SHORT = 3;
+       static final int TYPE_LONG = 4;
+       static final int TYPE_RATIONAL = 5;
+
+       static final int FILETYPE_REDUCEDIMAGE = 1;
+       static final int FILETYPE_PAGE = 2;
+       static final int FILETYPE_MASK = 4;
+       static final int OFILETYPE_IMAGE = 1;
+       static final int OFILETYPE_REDUCEDIMAGE = 2;
+       static final int OFILETYPE_PAGE = 3;
+
+       /* Different compression schemes */
+       static final int COMPRESSION_NONE = 1;
+       static final int COMPRESSION_CCITT_3_1 = 2;
+       static final int COMPRESSION_PACKBITS = 32773;
+
+       static final int IFD_ENTRY_SIZE = 12;
+
+public TIFFDirectory(TIFFRandomFileAccess file, boolean isLittleEndian, ImageLoader loader) {
+       this.file = file;
+       this.isLittleEndian = isLittleEndian;
+       this.loader = loader;
+}
+
+public TIFFDirectory(ImageData image) {
+       this.image = image;
+}
+
+/* PackBits decoder */
+int decodePackBits(byte[] src, byte[] dest, int offsetDest) {
+       int destIndex = offsetDest;
+       int srcIndex = 0;
+       while (srcIndex < src.length) {
+               byte n = src[srcIndex];
+               if (n >= 0) {
+                       /* Copy next n+1 bytes literally */
+                       System.arraycopy(src, ++srcIndex, dest, destIndex, n + 1);
+                       srcIndex += n + 1;
+                       destIndex += n + 1;
+               } else if (n >= -127) {
+                       /* Copy next byte -n+1 times */
+                       byte value = src[++srcIndex];
+                       for (int j = 0; j < -n + 1; j++) {
+                               dest[destIndex++] = value;
+                       }
+                       srcIndex++;
+               } else {
+                       /* Noop when n == -128 */
+                       srcIndex++;
+               }
+       }
+       /* Number of bytes copied */
+       return destIndex - offsetDest;
+}
+
+int getEntryValue(int type, byte[] buffer, int index) {
+       return toInt(buffer, index + 8, type);
+}
+
+void getEntryValue(int type, byte[] buffer, int index, int[] values) throws IOException {
+       int start = index + 8;
+       int size;
+       int offset = toInt(buffer, start, TYPE_LONG);
+       switch (type) {
+               case TYPE_SHORT: size = 2; break;
+               case TYPE_LONG: size = 4; break;
+               case TYPE_RATIONAL: size = 8; break;
+               case TYPE_ASCII:
+               case TYPE_BYTE: size = 1; break;
+               default: SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT); return;
+       }
+       if (values.length * size > 4) {
+               buffer = new byte[values.length * size];
+               file.seek(offset);
+               file.read(buffer);
+               start = 0;
+       }
+       for (int i = 0; i < values.length; i++) {
+               values[i] = toInt(buffer, start + i * size, type);
+       }
+}
+
+void decodePixels(ImageData image) throws IOException {
+       /* Each row is byte aligned */
+       byte[] imageData = new byte[(imageWidth * depth + 7) / 8 * imageLength];
+       image.data = imageData;
+       int destIndex = 0;
+       int length = stripOffsets.length;
+       for (int i = 0; i < length; i++) {
+               /* Read a strip */
+               byte[] data = new byte[stripByteCounts[i]];
+               file.seek(stripOffsets[i]);
+               file.read(data);
+               if (compression == COMPRESSION_NONE) {
+                       System.arraycopy(data, 0, imageData, destIndex, data.length);
+                       destIndex += data.length;
+               } else if (compression == COMPRESSION_PACKBITS) {
+                       destIndex += decodePackBits(data, imageData, destIndex);
+               } else if (compression == COMPRESSION_CCITT_3_1 || compression == 3) {
+                       TIFFModifiedHuffmanCodec codec = new TIFFModifiedHuffmanCodec();
+                       int nRows = rowsPerStrip;
+                       if (i == length -1) {
+                               int n = imageLength % rowsPerStrip;
+                               if (n != 0) nRows = n;
+                       }
+                       destIndex += codec.decode(data, imageData, destIndex, imageWidth, nRows);
+               }
+               if (loader.hasListeners()) {
+                       loader.notifyListeners(new ImageLoaderEvent(loader, image, i, i == length - 1));
+               }
+       }
+}
+
+PaletteData getColorMap() throws IOException {
+       int numColors = 1 << bitsPerSample[0];
+       /* R, G, B entries are 16 bit wide (2 bytes) */
+       int numBytes = 3 * 2 * numColors;
+       byte[] buffer = new byte[numBytes];
+       file.seek(colorMapOffset);
+       file.read(buffer);
+       RGB[] colors = new RGB[numColors];
+       /**
+        * SWT does not support 16-bit depth color formats.
+        * Convert the 16-bit data to 8-bit data.
+        * The correct way to do this is to multiply each
+        * 16 bit value by the value:
+        * (2^8 - 1) / (2^16 - 1).
+        * The fast way to do this is just to drop the low
+        * byte of the 16-bit value.
+        */
+       int offset = isLittleEndian ? 1 : 0;
+       int startG = 2 * numColors;
+       int startB = startG + 2 * numColors;
+       for (int i = 0; i < numColors; i++) {
+               int r = buffer[offset] & 0xFF;
+               int g = buffer[startG + offset] & 0xFF;
+               int b = buffer[startB + offset] & 0xFF;
+               colors[i] = new RGB(r, g, b);
+               offset += 2;
+       }
+       return new PaletteData(colors);
+}
+
+PaletteData getGrayPalette() {
+       int numColors = 1 << bitsPerSample[0];
+       RGB[] rgbs = new RGB[numColors];
+       for (int i = 0; i < numColors; i++) {
+               int value = i * 0xFF / (numColors - 1);
+               if (photometricInterpretation == 0) value = 0xFF - value;
+               rgbs[i] = new RGB(value, value, value);
+       }
+       return new PaletteData(rgbs);
+}
+
+PaletteData getRGBPalette(int bitsR, int bitsG, int bitsB) {
+       int blueMask = 0;
+       for (int i = 0; i < bitsB; i++) {
+               blueMask |= 1 << i;
+       }
+       int greenMask = 0;
+       for (int i = bitsB; i < bitsB + bitsG; i++) {
+               greenMask |= 1 << i;
+       }
+       int redMask = 0;
+       for (int i = bitsB + bitsG; i < bitsB + bitsG + bitsR; i++) {
+               redMask |= 1 << i;
+       }
+       return new PaletteData(redMask, greenMask, blueMask);
+}
+
+int formatStrips(int rowByteSize, int nbrRows, byte[] data, int maxStripByteSize, int offsetPostIFD, int extraBytes, int[][] strips) {
+       /*
+       * Calculate the nbr of required strips given the following requirements:
+       * - each strip should, if possible, not be greater than maxStripByteSize
+       * - each strip should contain 1 or more entire rows
+       *
+       * Format the strip fields arrays so that the image data is stored in one
+       * contiguous block. This block is stored after the IFD and after any tag
+       * info described in the IFD.
+       */
+       int n, nbrRowsPerStrip;
+       if (rowByteSize > maxStripByteSize) {
+               /* Each strip contains 1 row */
+               n = data.length / rowByteSize;
+               nbrRowsPerStrip = 1;
+       } else {
+               int nbr = (data.length + maxStripByteSize - 1) / maxStripByteSize;
+               nbrRowsPerStrip = nbrRows / nbr;
+               n = (nbrRows + nbrRowsPerStrip - 1) / nbrRowsPerStrip;
+       }
+       int stripByteSize = rowByteSize * nbrRowsPerStrip;
+
+       int[] offsets = new int[n];
+       int[] counts = new int[n];
+       /*
+       * Nbr of bytes between the end of the IFD directory and the start of
+       * the image data. Keep space for at least the offsets and counts
+       * data, each field being TYPE_LONG (4 bytes). If other tags require
+       * space between the IFD and the image block, use the extraBytes
+       * parameter.
+       * If there is only one strip, the offsets and counts data is stored
+       * directly in the IFD and we need not reserve space for it.
+       */
+       int postIFDData = n == 1 ? 0 : n * 2 * 4;
+       int startOffset = offsetPostIFD + extraBytes + postIFDData; /* offset of image data */
+
+       int offset = startOffset;
+       for (int i = 0; i < n; i++) {
+               /*
+               * Store all strips sequentially to allow us
+               * to copy all pixels in one contiguous area.
+               */
+               offsets[i] = offset;
+               counts[i] = stripByteSize;
+               offset += stripByteSize;
+       }
+       /* The last strip may contain fewer rows */
+       int mod = data.length % stripByteSize;
+       if (mod != 0) counts[counts.length - 1] = mod;
+
+       strips[0] = offsets;
+       strips[1] = counts;
+       return nbrRowsPerStrip;
+}
+
+int[] formatColorMap(RGB[] rgbs) {
+       /*
+       * In a TIFF ColorMap, all red come first, followed by
+       * green and blue. All values must be converted from
+       * 8 bit to 16 bit.
+       */
+       int[] colorMap = new int[rgbs.length * 3];
+       int offsetGreen = rgbs.length;
+       int offsetBlue = rgbs.length * 2;
+       for (int i = 0; i < rgbs.length; i++) {
+               colorMap[i] = rgbs[i].red << 8 | rgbs[i].red;
+               colorMap[i + offsetGreen] = rgbs[i].green << 8 | rgbs[i].green;
+               colorMap[i + offsetBlue] = rgbs[i].blue << 8 | rgbs[i].blue;
+       }
+       return colorMap;
+}
+
+void parseEntries(byte[] buffer) throws IOException {
+       for (int offset = 0; offset < buffer.length; offset += IFD_ENTRY_SIZE) {
+               int tag = toInt(buffer, offset, TYPE_SHORT);
+               int type = toInt(buffer, offset + 2, TYPE_SHORT);
+               int count = toInt(buffer, offset + 4, TYPE_LONG);
+               switch (tag) {
+                       case TAG_NewSubfileType: {
+                               subfileType = getEntryValue(type, buffer, offset);
+                               break;
+                       }
+                       case TAG_SubfileType: {
+                               int oldSubfileType = getEntryValue(type, buffer, offset);
+                               subfileType = oldSubfileType == OFILETYPE_REDUCEDIMAGE ? FILETYPE_REDUCEDIMAGE : oldSubfileType == OFILETYPE_PAGE ? FILETYPE_PAGE : 0;
+                               break;
+                       }
+                       case TAG_ImageWidth: {
+                               imageWidth = getEntryValue(type, buffer, offset);
+                               break;
+                       }
+                       case TAG_ImageLength: {
+                               imageLength = getEntryValue(type, buffer, offset);
+                               break;
+                       }
+                       case TAG_BitsPerSample: {
+                               if (type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                               bitsPerSample = new int[count];
+                               getEntryValue(type, buffer, offset, bitsPerSample);
+                               break;
+                       }
+                       case TAG_Compression: {
+                               compression = getEntryValue(type, buffer, offset);
+                               break;
+                       }
+                       case TAG_FillOrder: {
+                               /* Ignored: baseline only requires default fill order. */
+                               break;
+                       }
+                       case TAG_ImageDescription: {
+                               /* Ignored */
+                               break;
+                       }
+                       case TAG_PhotometricInterpretation: {
+                               photometricInterpretation = getEntryValue(type, buffer, offset);
+                               break;
+                       }
+                       case TAG_StripOffsets: {
+                               if (type != TYPE_LONG && type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                               stripOffsets = new int[count];
+                               getEntryValue(type, buffer, offset, stripOffsets);
+                               break;
+                       }
+                       case TAG_Orientation: {
+                               /* Ignored: baseline only requires top left orientation. */
+                               break;
+                       }
+                       case TAG_SamplesPerPixel: {
+                               if (type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                               samplesPerPixel = getEntryValue(type, buffer, offset);
+                               /* Only the basic 1 and 3 values are supported */
+                               if (samplesPerPixel != 1 && samplesPerPixel != 3) SWT.error(SWT.ERROR_UNSUPPORTED_DEPTH);
+                               break;
+                       }
+                       case TAG_RowsPerStrip: {
+                               rowsPerStrip = getEntryValue(type, buffer, offset);
+                               break;
+                       }
+                       case TAG_StripByteCounts: {
+                               stripByteCounts = new int[count];
+                               getEntryValue(type, buffer, offset, stripByteCounts);
+                               break;
+                       }
+                       case TAG_XResolution: {
+                               /* Ignored */
+                               break;
+                       }
+                       case TAG_YResolution: {
+                               /* Ignored */
+                               break;
+                       }
+                       case TAG_PlanarConfiguration: {
+                               /* Ignored: baseline only requires default planar configuration. */
+                               break;
+                       }
+                       case TAG_T4Options: {
+                               if (type != TYPE_LONG) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                               t4Options = getEntryValue(type, buffer, offset);
+                               if ((t4Options & 0x1) == 1) {
+                                       /* 2-dimensional coding is not supported */
+                                       SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
+                               }
+                               break;
+                       }
+                       case TAG_ResolutionUnit: {
+                               /* Ignored */
+                               break;
+                       }
+                       case TAG_Software: {
+                               /* Ignored */
+                               break;
+                       }
+                       case TAG_DateTime: {
+                               /* Ignored */
+                               break;
+                       }
+                       case TAG_ColorMap: {
+                               if (type != TYPE_SHORT) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                               /* Get the offset of the colorMap (use TYPE_LONG) */
+                               colorMapOffset = getEntryValue(TYPE_LONG, buffer, offset);
+                               break;
+                       }
+               }
+       }
+}
+
+public ImageData read(int [] nextIFDOffset) throws IOException {
+       /* Set TIFF default values */
+       bitsPerSample = new int[] {1};
+       colorMapOffset = NO_VALUE;
+       compression = 1;
+       imageLength = NO_VALUE;
+       imageWidth = NO_VALUE;
+       photometricInterpretation = NO_VALUE;
+       rowsPerStrip = Integer.MAX_VALUE;
+       samplesPerPixel = 1;
+       stripByteCounts = null;
+       stripOffsets = null;
+
+       byte[] buffer = new byte[2];
+       file.read(buffer);
+       int numberEntries = toInt(buffer, 0, TYPE_SHORT);
+       buffer = new byte[IFD_ENTRY_SIZE * numberEntries];
+       file.read(buffer);
+       byte buffer2[] = new byte[4];
+       file.read(buffer2);
+       nextIFDOffset[0] = toInt(buffer2, 0, TYPE_LONG);
+       parseEntries(buffer);
+
+       PaletteData palette = null;
+       depth = 0;
+       switch (photometricInterpretation) {
+               case 0:
+               case 1: {
+                       /* Bilevel or Grayscale image */
+                       palette = getGrayPalette();
+                       depth = bitsPerSample[0];
+                       break;
+               }
+               case 2: {
+                       /* RGB image */
+                       if (colorMapOffset != NO_VALUE) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                       /* SamplesPerPixel 3 is the only value supported */
+                       palette = getRGBPalette(bitsPerSample[0], bitsPerSample[1], bitsPerSample[2]);
+                       depth = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2];
+                       break;
+               }
+               case 3: {
+                       /* Palette Color image */
+                       if (colorMapOffset == NO_VALUE) SWT.error(SWT.ERROR_INVALID_IMAGE);
+                       palette = getColorMap();
+                       depth = bitsPerSample[0];
+                       break;
+               }
+               default: {
+                       SWT.error(SWT.ERROR_INVALID_IMAGE);
+               }
+       }
+
+       ImageData image = ImageData.internal_new(
+                       imageWidth,
+                       imageLength,
+                       depth,
+                       palette,
+                       1,
+                       null,
+                       0,
+                       null,
+                       null,
+                       -1,
+                       -1,
+                       SWT.IMAGE_TIFF,
+                       0,
+                       0,
+                       0,
+                       0);
+       decodePixels(image);
+       return image;
+}
+
+int toInt(byte[] buffer, int i, int type) {
+       if (type == TYPE_LONG) {
+               return isLittleEndian ?
+               (buffer[i] & 0xFF) | ((buffer[i + 1] & 0xFF) << 8) | ((buffer[i + 2] & 0xFF) << 16) | ((buffer[i + 3] & 0xFF) << 24) :
+               (buffer[i + 3] & 0xFF) | ((buffer[i + 2] & 0xFF) << 8) | ((buffer[i + 1] & 0xFF) << 16) | ((buffer[i] & 0xFF) << 24);
+       }
+       if (type == TYPE_SHORT) {
+               return isLittleEndian ?
+               (buffer[i] & 0xFF) | ((buffer[i + 1] & 0xFF) << 8) :
+               (buffer[i + 1] & 0xFF) | ((buffer[i] & 0xFF) << 8);
+       }
+       /* Invalid type */
+       SWT.error(SWT.ERROR_INVALID_IMAGE);
+       return -1;
+}
+
+void write(int photometricInterpretation) throws IOException {
+       boolean isRGB = photometricInterpretation == 2;
+       boolean isColorMap = photometricInterpretation == 3;
+       boolean isBiLevel = photometricInterpretation == 0 || photometricInterpretation == 1;
+
+       int imageWidth = image.width;
+       int imageLength = image.height;
+       int rowByteSize = image.bytesPerLine;
+
+       int numberEntries = isBiLevel ? 9 : 11;
+       int lengthDirectory = 2 + 12 * numberEntries + 4;
+       /* Offset following the header and the directory */
+       int nextOffset = 8 + lengthDirectory;
+
+       /* Extra space used by XResolution and YResolution values */
+       int extraBytes = 16;
+
+       int[] colorMap = null;
+       if (isColorMap) {
+               PaletteData palette = image.palette;
+               RGB[] rgbs = palette.getRGBs();
+               colorMap = formatColorMap(rgbs);
+               /* The number of entries of the Color Map must match the bitsPerSample field */
+               if (colorMap.length != 3 * 1 << image.depth) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
+               /* Extra space used by ColorMap values */
+               extraBytes += colorMap.length * 2;
+       }
+       if (isRGB) {
+               /* Extra space used by BitsPerSample values */
+               extraBytes += 6;
+       }
+       /* TIFF recommends storing the data in strips of no more than 8 Ko */
+       byte[] data = image.data;
+       int[][] strips = new int[2][];
+       int nbrRowsPerStrip = formatStrips(rowByteSize, imageLength, data, 8192, nextOffset, extraBytes, strips);
+       int[] stripOffsets = strips[0];
+       int[] stripByteCounts = strips[1];
+
+       int bitsPerSampleOffset = NO_VALUE;
+       if (isRGB) {
+               bitsPerSampleOffset = nextOffset;
+               nextOffset += 6;
+       }
+       int stripOffsetsOffset = NO_VALUE, stripByteCountsOffset = NO_VALUE;
+       int xResolutionOffset, yResolutionOffset, colorMapOffset = NO_VALUE;
+       int cnt = stripOffsets.length;
+       if (cnt > 1) {
+               stripOffsetsOffset = nextOffset;
+               nextOffset += 4 * cnt;
+               stripByteCountsOffset = nextOffset;
+               nextOffset += 4 * cnt;
+       }
+       xResolutionOffset = nextOffset;
+       nextOffset += 8;
+       yResolutionOffset = nextOffset;
+       nextOffset += 8;
+       if (isColorMap) {
+               colorMapOffset = nextOffset;
+               nextOffset += colorMap.length * 2;
+       }
+       /* TIFF header */
+       writeHeader();
+
+       /* Image File Directory */
+       out.writeShort(numberEntries);
+       writeEntry(TAG_ImageWidth, TYPE_LONG, 1, imageWidth);
+       writeEntry(TAG_ImageLength, TYPE_LONG, 1, imageLength);
+       if (isColorMap) writeEntry(TAG_BitsPerSample, TYPE_SHORT, 1, image.depth);
+       if (isRGB) writeEntry(TAG_BitsPerSample, TYPE_SHORT, 3, bitsPerSampleOffset);
+       writeEntry(TAG_Compression, TYPE_SHORT, 1, COMPRESSION_NONE);
+       writeEntry(TAG_PhotometricInterpretation, TYPE_SHORT, 1, photometricInterpretation);
+       writeEntry(TAG_StripOffsets, TYPE_LONG, cnt, cnt > 1 ? stripOffsetsOffset : stripOffsets[0]);
+       if (isRGB) writeEntry(TAG_SamplesPerPixel, TYPE_SHORT, 1, 3);
+       writeEntry(TAG_RowsPerStrip, TYPE_LONG, 1, nbrRowsPerStrip);
+       writeEntry(TAG_StripByteCounts, TYPE_LONG, cnt, cnt > 1 ? stripByteCountsOffset : stripByteCounts[0]);
+       writeEntry(TAG_XResolution, TYPE_RATIONAL, 1, xResolutionOffset);
+       writeEntry(TAG_YResolution, TYPE_RATIONAL, 1, yResolutionOffset);
+       if (isColorMap) writeEntry(TAG_ColorMap, TYPE_SHORT, colorMap.length, colorMapOffset);
+       /* Offset of next IFD (0 for last IFD) */
+       out.writeInt(0);
+
+       /* Values longer than 4 bytes Section */
+
+       /* BitsPerSample 8,8,8 */
+       if (isRGB) for (int i = 0; i < 3; i++) out.writeShort(8);
+       if (cnt > 1) {
+               for (int i = 0; i < cnt; i++) out.writeInt(stripOffsets[i]);
+               for (int i = 0; i < cnt; i++) out.writeInt(stripByteCounts[i]);
+       }
+       /* XResolution and YResolution set to 300 dpi */
+       for (int i = 0; i < 2; i++) {
+               out.writeInt(300);
+               out.writeInt(1);
+       }
+       /* ColorMap */
+       if (isColorMap) for (int i = 0; i < colorMap.length; i++) out.writeShort(colorMap[i]);
+
+       /* Image Data */
+       out.write(data);
+}
+
+void writeEntry(short tag, int type, int count, int value) throws IOException {
+       out.writeShort(tag);
+       out.writeShort(type);
+       out.writeInt(count);
+       out.writeInt(value);
+}
+
+void writeHeader() throws IOException {
+       /* little endian */
+       out.write(0x49);
+       out.write(0x49);
+
+       /* TIFF identifier */
+       out.writeShort(42);
+       /*
+       * Offset of the first IFD is chosen to be 8.
+       * It is word aligned and immediately after this header.
+       */
+       out.writeInt(8);
+}
+
+void writeToStream(LEDataOutputStream byteStream) throws IOException {
+       out = byteStream;
+       int photometricInterpretation = -1;
+
+       /* Scanline pad must be 1 */
+       if (image.scanlinePad != 1) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
+       switch (image.depth) {
+               case 1: {
+                       /* Palette must be black and white or white and black */
+                       PaletteData palette = image.palette;
+                       RGB[] rgbs = palette.colors;
+                       if (palette.isDirect || rgbs == null || rgbs.length != 2) SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
+                       RGB rgb0 = rgbs[0];
+                       RGB rgb1 = rgbs[1];
+                       if (!(rgb0.red == rgb0.green && rgb0.green == rgb0.blue &&
+                               rgb1.red == rgb1.green && rgb1.green == rgb1.blue &&
+                               ((rgb0.red == 0x0 && rgb1.red == 0xFF) || (rgb0.red == 0xFF && rgb1.red == 0x0)))) {
+                               SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
+                       }
+                       /* 0 means a color index of 0 is imaged as white */
+                       photometricInterpretation = image.palette.colors[0].red == 0xFF ? 0 : 1;
+                       break;
+               }
+               case 4:
+               case 8: {
+                       photometricInterpretation = 3;
+                       break;
+               }
+               case 24: {
+                       photometricInterpretation = 2;
+                       break;
+               }
+               default: {
+                       SWT.error(SWT.ERROR_UNSUPPORTED_FORMAT);
+               }
+       }
+       write(photometricInterpretation);
+}
+
+}