package org.eclipse.swt.internal.image; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved | |
*/ | |
import org.eclipse.swt.*; | |
import org.eclipse.swt.internal.Compatibility; | |
import java.io.*; | |
class PngChunk extends Object { | |
byte[] reference; | |
static final int LENGTH_OFFSET = 0; | |
static final int TYPE_OFFSET = 4; | |
static final int DATA_OFFSET = 8; | |
static final int TYPE_FIELD_LENGTH = 4; | |
static final int LENGTH_FIELD_LENGTH = 4; | |
static final int MIN_LENGTH = 12; | |
static final int CHUNK_UNKNOWN = -1; | |
// Critical chunks. | |
static final int CHUNK_IHDR = 0; | |
static final int CHUNK_PLTE = 1; | |
static final int CHUNK_IDAT = 2; | |
static final int CHUNK_IEND = 3; | |
// Non-critical chunks. | |
static final int CHUNK_tRNS = 5; | |
static final byte[] TYPE_IHDR = {(byte) 'I', (byte) 'H', (byte) 'D', (byte) 'R'}; | |
static final byte[] TYPE_PLTE = {(byte) 'P', (byte) 'L', (byte) 'T', (byte) 'E'}; | |
static final byte[] TYPE_IDAT = {(byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T'}; | |
static final byte[] TYPE_IEND = {(byte) 'I', (byte) 'E', (byte) 'N', (byte) 'D'}; | |
static final byte[] TYPE_tRNS = {(byte) 't', (byte) 'R', (byte) 'N', (byte) 'S'}; | |
static final int[] CRC_TABLE; | |
static { | |
CRC_TABLE = new int[256]; | |
for (int i = 0; i < 256; i++) { | |
CRC_TABLE[i] = i; | |
for (int j = 0; j < 8; j++) { | |
if ((CRC_TABLE[i] & 0x1) == 0) { | |
CRC_TABLE[i] = (CRC_TABLE[i] >> 1) & 0x7FFFFFFF; | |
} else { | |
CRC_TABLE[i] = 0xEDB88320 ^ ((CRC_TABLE[i] >> 1) & 0x7FFFFFFF); | |
} | |
} | |
} | |
} | |
/** | |
* Construct a PngChunk using the reference bytes | |
* given. | |
*/ | |
PngChunk(byte[] reference) { | |
super(); | |
setReference(reference); | |
} | |
/** | |
* Get the PngChunk's reference byteArray; | |
*/ | |
byte[] getReference() { | |
return reference; | |
} | |
/** | |
* Set the PngChunk's reference byteArray; | |
*/ | |
void setReference(byte[] reference) { | |
this.reference = reference; | |
} | |
/** | |
* Get the 32-bit integer from the reference byte | |
* array at the given offset. | |
*/ | |
int getInt32(int offset) { | |
int answer = 0; | |
answer |= (reference[offset] & 0xFF) << 24; | |
answer |= (reference[offset + 1] & 0xFF) << 16; | |
answer |= (reference[offset + 2] & 0xFF) << 8; | |
answer |= (reference[offset + 3] & 0xFF); | |
return answer; | |
} | |
/** | |
* Set the 32-bit integer in the reference byte | |
* array at the given offset. | |
*/ | |
void setInt32(int offset, int value) { | |
reference[offset] = (byte) ((value >> 24) & 0xFF); | |
reference[offset + 1] = (byte) ((value >> 16) & 0xFF); | |
reference[offset + 2] = (byte) ((value >> 8) & 0xFF); | |
reference[offset + 3] = (byte) (value & 0xFF); | |
} | |
/** | |
* Get the length of the data component of this chunk. | |
* This is not the length of the entire chunk. | |
*/ | |
int getLength() { | |
return getInt32(LENGTH_OFFSET); | |
} | |
/** | |
* Set the length of the data component of this chunk. | |
* This is not the length of the entire chunk. | |
*/ | |
void setLength(int value) { | |
setInt32(LENGTH_OFFSET, value); | |
} | |
/** | |
* Get the chunk type. This is a four byte value. | |
* Each byte should be an ASCII character. | |
* The first byte is upper case if the chunk is critical. | |
* The second byte is upper case if the chunk is publicly defined. | |
* The third byte must be upper case. | |
* The fourth byte is upper case if the chunk is unsafe to copy. | |
* Public chunk types are defined by the PNG Development Group. | |
*/ | |
byte[] getTypeBytes() { | |
byte[] type = new byte[4]; | |
System.arraycopy(reference, TYPE_OFFSET, type, 0, TYPE_FIELD_LENGTH); | |
return type; | |
} | |
/** | |
* Set the chunk type. This is a four byte value. | |
* Each byte should be an ASCII character. | |
* The first byte is upper case if the chunk is critical. | |
* The second byte is upper case if the chunk is publicly defined. | |
* The third byte must be upper case. | |
* The fourth byte is upper case if the chunk is unsafe to copy. | |
* Public chunk types are defined by the PNG Development Group. | |
*/ | |
void setType(byte[] value) { | |
if (value.length != TYPE_FIELD_LENGTH) { | |
throw new SWTException(SWT.ERROR_INVALID_ARGUMENT); | |
} | |
System.arraycopy(value, 0, reference, TYPE_OFFSET, TYPE_FIELD_LENGTH); | |
} | |
/** | |
* Get the chunk's data. | |
*/ | |
byte[] getData() { | |
int dataLength = getLength(); | |
if (reference.length < MIN_LENGTH + dataLength) { | |
throw new SWTException(SWT.ERROR_INVALID_RANGE); | |
} | |
byte[] data = new byte[dataLength]; | |
System.arraycopy(reference, DATA_OFFSET, data, 0, dataLength); | |
return data; | |
} | |
/** | |
* Set the chunk's data. | |
* This method has two side-effects. | |
* 1. It will set the length field to be the length | |
* of the data array given. | |
* 2. It will set the CRC field to the computed CRC | |
* value of the data array given. | |
*/ | |
void setData(byte[] data) { | |
setLength(data.length); | |
System.arraycopy(data, 0, reference, DATA_OFFSET, data.length); | |
setCRC(computeCRC()); | |
} | |
/** | |
* Get the CRC value for the chunk's data. | |
* Ensure that the length field has a good | |
* value before making this call. | |
*/ | |
int getCRC() { | |
int crcOffset = DATA_OFFSET + getLength(); | |
return getInt32(crcOffset); | |
} | |
/** | |
* Set the CRC value for the chunk's data. | |
* Ensure that the length field has a good | |
* value before making this call. | |
*/ | |
void setCRC(int value) { | |
int crcOffset = DATA_OFFSET + getLength(); | |
setInt32(crcOffset, value); | |
} | |
/** | |
* Get the chunk's total size including the length, type, and crc fields. | |
*/ | |
int getSize() { | |
return MIN_LENGTH + getLength(); | |
} | |
/** | |
* Compute the CRC value for the chunk's data. Answer | |
* whether this value matches the value stored in the | |
* chunk. | |
*/ | |
boolean checkCRC() { | |
int crc = computeCRC(); | |
int storedCRC = getCRC(); | |
return crc == storedCRC; | |
} | |
/** | |
* Answer the CRC value of chunk's data. | |
*/ | |
int computeCRC() { | |
int crc = 0xFFFFFFFF; | |
int start = TYPE_OFFSET; | |
int stop = DATA_OFFSET + getLength(); | |
for (int i = start; i < stop; i++) { | |
int index = (crc ^ reference[i]) & 0xFF; | |
crc = CRC_TABLE[index] ^ ((crc >> 8) & 0x00FFFFFF); | |
} | |
return ~crc; | |
} | |
boolean typeMatchesArray(byte[] array) { | |
for (int i = 0; i < TYPE_FIELD_LENGTH; i++) { | |
if (reference[TYPE_OFFSET + i] != array[i]){ | |
return false; | |
} | |
} | |
return true; | |
} | |
boolean isCritical() { | |
return Character.isUpperCase((char) getTypeBytes()[0]); | |
} | |
int getChunkType() { | |
if (typeMatchesArray(TYPE_IHDR)) return CHUNK_IHDR; | |
if (typeMatchesArray(TYPE_PLTE)) return CHUNK_PLTE; | |
if (typeMatchesArray(TYPE_IDAT)) return CHUNK_IDAT; | |
if (typeMatchesArray(TYPE_IEND)) return CHUNK_IEND; | |
if (typeMatchesArray(TYPE_tRNS)) return CHUNK_tRNS; | |
return CHUNK_UNKNOWN; | |
} | |
/** | |
* Read the next PNG chunk from the input stream given. | |
* If unable to read a chunk, return null. | |
*/ | |
static PngChunk readNextFromStream(LEDataInputStream stream) { | |
try { | |
int headerLength = LENGTH_FIELD_LENGTH + TYPE_FIELD_LENGTH; | |
byte[] headerBytes = new byte[headerLength]; | |
int result = stream.read(headerBytes, 0, headerLength); | |
stream.unread(headerBytes); | |
if (result != headerLength) return null; | |
PngChunk tempChunk = new PngChunk(headerBytes); | |
int chunkLength = tempChunk.getSize(); | |
byte[] chunk = new byte[chunkLength]; | |
result = stream.read(chunk, 0, chunkLength); | |
if (result != chunkLength) return null; | |
switch (tempChunk.getChunkType()) { | |
case CHUNK_IHDR: | |
return new PngIhdrChunk(chunk); | |
case CHUNK_PLTE: | |
return new PngPlteChunk(chunk); | |
case CHUNK_IDAT: | |
return new PngIdatChunk(chunk); | |
case CHUNK_IEND: | |
return new PngIendChunk(chunk); | |
case CHUNK_tRNS: | |
return new PngTrnsChunk(chunk); | |
default: | |
return new PngChunk(chunk); | |
} | |
} catch (IOException e) { | |
return null; | |
} | |
} | |
/** | |
* Answer whether the chunk is a valid PNG chunk. | |
*/ | |
void validate(PngFileReadState readState, PngIhdrChunk headerChunk) { | |
if (reference.length < MIN_LENGTH) SWT.error(SWT.ERROR_INVALID_IMAGE); | |
byte[] type = getTypeBytes(); | |
// The third character MUST be upper case. | |
if (!Character.isUpperCase((char) type[2])) SWT.error(SWT.ERROR_INVALID_IMAGE); | |
// All characters must be letters. | |
for (int i = 0; i < TYPE_FIELD_LENGTH; i++) { | |
if (!Compatibility.isLetter((char) type[i])) SWT.error(SWT.ERROR_INVALID_IMAGE); | |
} | |
// The stored CRC must match the data's computed CRC. | |
if (!checkCRC()) SWT.error(SWT.ERROR_INVALID_IMAGE); | |
} | |
/** | |
* Provided so that subclasses can override and add | |
* data to the toString() call. | |
*/ | |
void contributeToString(StringBuffer buffer) {} | |
public String toString() { | |
StringBuffer buffer = new StringBuffer(); | |
buffer.append("{"); | |
buffer.append("\n\tLength: "); | |
buffer.append(getLength()); | |
buffer.append("\n\tType: "); | |
byte[] type = getTypeBytes(); | |
for(int i = 0; i < type.length; i++) { | |
buffer.append((char) type[i]); | |
} | |
contributeToString(buffer); | |
buffer.append("\n\tCRC: "); | |
buffer.append(Integer.toHexString(getCRC())); | |
buffer.append("\n}"); | |
return buffer.toString(); | |
} | |
} |