--- /dev/null
+/*******************************************************************************
+ * 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);
+}
+
+}