package ar.com.hjg.pngj.chunks; import java.io.OutputStream; import ar.com.hjg.pngj.ImageInfo; import ar.com.hjg.pngj.PngjExceptionInternal; /** * Represents a instance of a PNG chunk. *
* See http://www * .libpng.org/pub/png/spec/1.2/PNG-Chunks .html *
* Concrete classes should extend {@link PngChunkSingle} or {@link PngChunkMultiple} *
* Note that some methods/fields are type-specific (getOrderingConstraint(), allowsMultiple()),
* some are 'almost' type-specific (id,crit,pub,safe; the exception is PngUKNOWN),
* and the rest are instance-specific
*/
public abstract class PngChunk {
/**
* Chunk-id: 4 letters
*/
public final String id;
/**
* Autocomputed at creation time
*/
public final boolean crit, pub, safe;
protected final ImageInfo imgInfo;
protected ChunkRaw raw;
private boolean priority = false; // For writing. Queued chunks with high priority will be written
// as soon as
// possible
protected int chunkGroup = -1; // chunk group where it was read or writen
/**
* Possible ordering constraint for a PngChunk type -only relevant for ancillary chunks. Theoretically, there could be
* more general constraints, but these cover the constraints for standard chunks.
*/
public enum ChunkOrderingConstraint {
/**
* no ordering constraint
*/
NONE,
/**
* Must go before PLTE (and hence, also before IDAT)
*/
BEFORE_PLTE_AND_IDAT,
/**
* Must go after PLTE (if exists) but before IDAT
*/
AFTER_PLTE_BEFORE_IDAT,
/**
* Must go after PLTE (and it must exist) but before IDAT
*/
AFTER_PLTE_BEFORE_IDAT_PLTE_REQUIRED,
/**
* Must before IDAT (before or after PLTE)
*/
BEFORE_IDAT,
/**
* After IDAT (this restriction does not apply to the standard PNG chunks)
*/
AFTER_IDAT,
/**
* Does not apply
*/
NA;
public boolean mustGoBeforePLTE() {
return this == BEFORE_PLTE_AND_IDAT;
}
public boolean mustGoBeforeIDAT() {
return this == BEFORE_IDAT || this == BEFORE_PLTE_AND_IDAT || this == AFTER_PLTE_BEFORE_IDAT;
}
/**
* after pallete, if exists
*/
public boolean mustGoAfterPLTE() {
return this == AFTER_PLTE_BEFORE_IDAT || this == AFTER_PLTE_BEFORE_IDAT_PLTE_REQUIRED;
}
public boolean mustGoAfterIDAT() {
return this == AFTER_IDAT;
}
public boolean isOk(int currentChunkGroup, boolean hasplte) {
if (this == NONE)
return true;
else if (this == BEFORE_IDAT)
return currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT;
else if (this == BEFORE_PLTE_AND_IDAT)
return currentChunkGroup < ChunksList.CHUNK_GROUP_2_PLTE;
else if (this == AFTER_PLTE_BEFORE_IDAT)
return hasplte ? currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT
: (currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT && currentChunkGroup > ChunksList.CHUNK_GROUP_2_PLTE);
else if (this == AFTER_IDAT)
return currentChunkGroup > ChunksList.CHUNK_GROUP_4_IDAT;
return false;
}
}
public PngChunk(String id, ImageInfo imgInfo) {
this.id = id;
this.imgInfo = imgInfo;
this.crit = ChunkHelper.isCritical(id);
this.pub = ChunkHelper.isPublic(id);
this.safe = ChunkHelper.isSafeToCopy(id);
}
protected final ChunkRaw createEmptyChunk(int len, boolean alloc) {
ChunkRaw c = new ChunkRaw(len, ChunkHelper.toBytes(id), alloc);
return c;
}
/**
* In which "chunkGroup" (see {@link ChunksList}for definition) this chunks instance was read or written.
*
* -1 if not read or written (eg, queued) */ final public int getChunkGroup() { return chunkGroup; } /** * @see #getChunkGroup() */ final void setChunkGroup(int chunkGroup) { this.chunkGroup = chunkGroup; } public boolean hasPriority() { return priority; } public void setPriority(boolean priority) { this.priority = priority; } final void write(OutputStream os) { if (raw == null || raw.data == null) raw = createRawChunk(); if (raw == null) throw new PngjExceptionInternal("null chunk ! creation failed for " + this); raw.writeChunk(os); } /** * Creates the physical chunk. This is used when writing (serialization). Each particular chunk class implements its * own logic. * * @return A newly allocated and filled raw chunk */ public abstract ChunkRaw createRawChunk(); /** * Parses raw chunk and fill inside data. This is used when reading (deserialization). Each particular chunk class * implements its own logic. */ protected abstract void parseFromRaw(ChunkRaw c); /** * See {@link PngChunkMultiple} and {@link PngChunkSingle} * * @return true if PNG accepts multiple chunks of this class */ protected abstract boolean allowsMultiple(); public ChunkRaw getRaw() { return raw; } void setRaw(ChunkRaw raw) { this.raw = raw; } /** * @see ChunkRaw#len */ public int getLen() { return raw != null ? raw.len : -1; } /** * @see ChunkRaw#getOffset() */ public long getOffset() { return raw != null ? raw.getOffset() : -1; } /** * This signals that the raw chunk (serialized data) as invalid, so that it's regenerated on write. This should be * called for the (infrequent) case of chunks that were copied from a PngReader and we want to manually modify it. */ public void invalidateRawData() { raw = null; } /** * see {@link ChunkOrderingConstraint} */ public abstract ChunkOrderingConstraint getOrderingConstraint(); @Override public String toString() { return "chunk id= " + id + " (len=" + getLen() + " offset=" + getOffset() + ")"; } }