176 lines
4.6 KiB
Java
176 lines
4.6 KiB
Java
package ar.com.hjg.pngj.chunks;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.zip.CRC32;
|
|
|
|
import ar.com.hjg.pngj.PngHelperInternal;
|
|
import ar.com.hjg.pngj.PngjBadCrcException;
|
|
import ar.com.hjg.pngj.PngjException;
|
|
import ar.com.hjg.pngj.PngjOutputException;
|
|
|
|
/**
|
|
* Raw (physical) chunk.
|
|
* <p>
|
|
* Short lived object, to be created while serialing/deserializing Do not reuse
|
|
* it for different chunks. <br>
|
|
* See http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
|
|
*/
|
|
public class ChunkRaw {
|
|
/**
|
|
* The length counts only the data field, not itself, the chunk type code,
|
|
* or the CRC. Zero is a valid length.
|
|
* Although encoders and decoders should treat the length as unsigned, its
|
|
* value must not exceed 231-1 bytes.
|
|
*/
|
|
public final int len;
|
|
|
|
/**
|
|
* A 4-byte chunk type code. uppercase and lowercase ASCII letters
|
|
*/
|
|
public final byte[] idbytes;
|
|
public final String id;
|
|
|
|
/**
|
|
* The data bytes appropriate to the chunk type, if any. This field can be
|
|
* of zero length. Does not include crc. If
|
|
* it's null, it means that the data is ot available
|
|
*/
|
|
public byte[] data = null;
|
|
/**
|
|
* @see ChunkRaw#getOffset()
|
|
*/
|
|
private long offset = 0;
|
|
|
|
/**
|
|
* A 4-byte CRC (Cyclic Redundancy Check) calculated on the preceding bytes
|
|
* in the chunk, including the chunk type
|
|
* code and chunk data fields, but not including the length field.
|
|
*/
|
|
public byte[] crcval = new byte[4];
|
|
|
|
private CRC32 crcengine; // lazily instantiated
|
|
|
|
public ChunkRaw(int len, String id, boolean alloc) {
|
|
this.len = len;
|
|
this.id = id;
|
|
this.idbytes = ChunkHelper.toBytes(id);
|
|
for (int i = 0; i < 4; i++) {
|
|
if (idbytes[i] < 65 || idbytes[i] > 122 || (idbytes[i] > 90 && idbytes[i] < 97))
|
|
throw new PngjException("Bad id chunk: must be ascii letters " + id);
|
|
}
|
|
if (alloc)
|
|
allocData();
|
|
}
|
|
|
|
public ChunkRaw(int len, byte[] idbytes, boolean alloc) {
|
|
this(len, ChunkHelper.toString(idbytes), alloc);
|
|
}
|
|
|
|
public void allocData() { // TODO: not public
|
|
if (data == null || data.length < len)
|
|
data = new byte[len];
|
|
}
|
|
|
|
/**
|
|
* this is called after setting data, before writing to os
|
|
*/
|
|
private void computeCrcForWriting() {
|
|
crcengine = new CRC32();
|
|
crcengine.update(idbytes, 0, 4);
|
|
if (len > 0)
|
|
crcengine.update(data, 0, len); //
|
|
PngHelperInternal.writeInt4tobytes((int) crcengine.getValue(), crcval, 0);
|
|
}
|
|
|
|
/**
|
|
* Computes the CRC and writes to the stream. If error, a
|
|
* PngjOutputException is thrown
|
|
*
|
|
* Note that this is only used for non idat chunks
|
|
*/
|
|
public void writeChunk(OutputStream os) {
|
|
writeChunkHeader(os);
|
|
if (len > 0) {
|
|
if (data == null)
|
|
throw new PngjOutputException("cannot write chunk, raw chunk data is null [" + id + "]");
|
|
PngHelperInternal.writeBytes(os, data, 0, len);
|
|
}
|
|
computeCrcForWriting();
|
|
writeChunkCrc(os);
|
|
}
|
|
|
|
public void writeChunkHeader(OutputStream os) {
|
|
if (idbytes.length != 4)
|
|
throw new PngjOutputException("bad chunkid [" + id + "]");
|
|
PngHelperInternal.writeInt4(os, len);
|
|
PngHelperInternal.writeBytes(os, idbytes);
|
|
}
|
|
|
|
public void writeChunkCrc(OutputStream os) {
|
|
PngHelperInternal.writeBytes(os, crcval, 0, 4);
|
|
}
|
|
|
|
public void checkCrc() {
|
|
int crcComputed = (int) crcengine.getValue();
|
|
int crcExpected = PngHelperInternal.readInt4fromBytes(crcval, 0);
|
|
if (crcComputed != crcExpected)
|
|
throw new PngjBadCrcException("chunk: " + this.toString() + " expected=" + crcExpected + " read=" + crcComputed);
|
|
}
|
|
|
|
public void updateCrc(byte[] buf, int off, int len) {
|
|
if (crcengine == null)
|
|
crcengine = new CRC32();
|
|
crcengine.update(buf, off, len);
|
|
}
|
|
|
|
ByteArrayInputStream getAsByteStream() { // only the data
|
|
return new ByteArrayInputStream(data);
|
|
}
|
|
|
|
/**
|
|
* offset in the full PNG stream, in bytes. only informational, for read
|
|
* chunks (0=NA)
|
|
*/
|
|
public long getOffset() {
|
|
return offset;
|
|
}
|
|
|
|
public void setOffset(long offset) {
|
|
this.offset = offset;
|
|
}
|
|
|
|
public String toString() {
|
|
return "chunkid=" + ChunkHelper.toString(idbytes) + " len=" + len;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
|
result = prime * result + (int) (offset ^ (offset >>> 32));
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj)
|
|
return true;
|
|
if (obj == null)
|
|
return false;
|
|
if (getClass() != obj.getClass())
|
|
return false;
|
|
ChunkRaw other = (ChunkRaw) obj;
|
|
if (id == null) {
|
|
if (other.id != null)
|
|
return false;
|
|
} else if (!id.equals(other.id))
|
|
return false;
|
|
if (offset != other.offset)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
}
|