org.eclipse.swt.internal.image.TIFFDirectory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.swt.win32.win32.x86 Show documentation
Show all versions of org.eclipse.swt.win32.win32.x86 Show documentation
SWT is an open source widget toolkit for Java designed to provide efficient, portable access to the user-interface facilities of the operating systems on which it is implemented.
The newest version!
/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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);
}
}