All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ar.com.hjg.pngj.ChunkSeqReader Maven / Gradle / Ivy

package ar.com.hjg.pngj;

import java.util.Arrays;

import ar.com.hjg.pngj.ChunkReader.ChunkReaderMode;
import ar.com.hjg.pngj.chunks.ChunkHelper;

/**
 * Consumes a stream of bytes that consist of a series of PNG-like chunks.
 * 

* This has little intelligence, it's quite low-level and general (it could even * be used for a MNG stream, for example). It supports signature recognition. */ public class ChunkSeqReader implements IBytesConsumer { protected static final int SIGNATURE_LEN = 8; protected final boolean withSignature; private byte[] buf0 = new byte[8]; // for signature or chunk starts private int buf0len = 0; private boolean signatureDone = false; private boolean done = false; // for end chunk, error, or abort private int chunkCount = 0; private long bytesCount = 0; private ChunkReader curChunkReader; private DeflatedChunksSet curReaderDeflatedSet; /** * Creates a ChunkSeqReader (with signature) */ public ChunkSeqReader() { this(true); } /** * @param withSignature * If true, the stream is assumed be prepended by 8 bit signature */ public ChunkSeqReader(boolean withSignature) { this.withSignature = withSignature; signatureDone = !withSignature; } /** * Consumes (in general, partially) a number of bytes. A single call never * involves more than one chunk. * * When the signature is read, it calls checkSignature() * * When the start of a chunk is detected, it calls * {@link #startNewChunk(int, String, long)} * * When data from a chunk is being read, it delegates to * {@link ChunkReader#feedBytes(byte[], int, int)} * * The caller might want to call this method more than once in succesion * * This should rarely be overriden * * @param buffer * @param offset * Offset in buffer * @param len * Valid bytes that can be consumed * @return processed bytes, in the 1-len range. -1 if done. Only returns 0 * if len=0. **/ public int consume(byte[] buffer, int offset, int len) { if (done) return -1; if (len == 0) return 0; // nothing to do if (len < 0) throw new PngjInputException("Bad len: " + len); int processed = 0; if (signatureDone) { if (curChunkReader == null || curChunkReader.isDone()) { // new chunk: read first 8 bytes int read0 = 8 - buf0len; if (read0 > len) read0 = len; System.arraycopy(buffer, offset, buf0, buf0len, read0); buf0len += read0; processed += read0; bytesCount += read0; len -= read0; offset += read0; if (buf0len == 8) { // end reading chunk length and id chunkCount++; int clen = PngHelperInternal.readInt4fromBytes(buf0, 0); String cid = ChunkHelper.toString(buf0, 4, 4); startNewChunk(clen, cid, bytesCount - 8); buf0len = 0; } } else { // reading chunk, delegates to curChunkReader int read1 = curChunkReader.feedBytes(buffer, offset, len); processed += read1; bytesCount += read1; } } else { // reading signature int read = SIGNATURE_LEN - buf0len; if (read > len) read = len; System.arraycopy(buffer, offset, buf0, buf0len, read); buf0len += read; if (buf0len == SIGNATURE_LEN) { checkSignature(buf0); buf0len = 0; signatureDone = true; } processed += read; bytesCount += read; } return processed; } /** * Trys to feeds exactly len bytes, calling * {@link #consume(byte[], int, int)} retrying if necessary. * * This should only be used in callback mode * * @return true if succceded */ public boolean feedAll(byte[] buf, int off, int len) { while (len > 0) { int n = consume(buf, off, len); if (n < 1) return false; len -= n; off += n; } return true; } /** * Called when a chunk start has been read (id and length), before the chunk * data itself is read. It creates a new ChunkReader (field accesible via * {@link #getCurChunkReader()}) in the corresponding mode, and eventually a * curReaderDeflatedSet.(field accesible via * {@link #getCurReaderDeflatedSet()}) * * To decide the mode and options, it calls * {@link #shouldCheckCrc(int, String)}, * {@link #shouldSkipContent(int, String)}, {@link #isIdatKind(String)}. * Those methods should be overriden in preference to this. * * The respective {@link ChunkReader#chunkDone()} method is directed to this * {@link #postProcessChunk(ChunkReader)}. */ protected void startNewChunk(int len, String id, long offset) { boolean checkCrc = shouldCheckCrc(len, id); boolean skip = shouldSkipContent(len, id); boolean isIdatType = isIdatKind(id); // first see if we should terminate an active curReaderDeflatedSet if (curReaderDeflatedSet != null && (!curReaderDeflatedSet.chunkid.equals(id)) && !curReaderDeflatedSet.allowOtherChunksInBetween(id)) { if (!curReaderDeflatedSet.isDone()) throw new PngjInputException("unexpected chunk while reading IDAT(like) set " + id); curReaderDeflatedSet = null; } if (isIdatType && !skip) { // IDAT with HOT PROCESS mode if (curReaderDeflatedSet == null) curReaderDeflatedSet = createIdatSet(id); // new curChunkReader = new DeflatedChunkReader(len, id, checkCrc, offset, curReaderDeflatedSet) { @Override protected void chunkDone() { postProcessChunk(this); } }; } else { // BUFFER or SKIP (might include skipped Idat like chunks) curChunkReader = new ChunkReader(len, id, offset, skip ? ChunkReaderMode.SKIP : ChunkReaderMode.BUFFER) { @Override protected void chunkDone() { postProcessChunk(this); } @Override protected void processData(byte[] buf, int off, int len) { throw new PngjExceptionInternal("should never happen"); } }; if (!checkCrc) curChunkReader.setCrcCheck(false); } } /** * This is called after a chunk is read, in all modes. * * This implementation only chenks the id of the first chunk, and process * the IEND chunk (sets done=true) ** * Further processing should be overriden (call this first!) **/ protected void postProcessChunk(ChunkReader chunkR) { // called after chunk is read if (chunkCount == 1) { String cid = firstChunkId(); if (cid != null && !cid.equals(chunkR.getChunkRaw().id)) throw new PngjInputException("Bad first chunk: " + chunkR.getChunkRaw().id + " expected: " + firstChunkId()); } if (chunkR.getChunkRaw().id.equals(endChunkId())) done = true; } /** * DeflatedChunksSet factory. This implementation is quite dummy, it usually * should be overriden. */ protected DeflatedChunksSet createIdatSet(String id) { return new DeflatedChunksSet(id, 1024, 1024); // sizes: arbitrary This should normally be overriden } /** * Decides if this Chunk is of "IDAT" kind (in concrete: if it is, and if * it's not to be skiped, a DeflatedChunksSet will be created to deflate it * and process+ the deflated data) * * This implementation return false. * * @param id */ protected boolean isIdatKind(String id) { return false; // override in ChunkSequencePng } /** * Chunks can be skipped depending on id and/or length. Skipped chunks are * still processed, but their data will be null, and CRC will never checked * * @param len * @param id */ protected boolean shouldSkipContent(int len, String id) { return false; } protected boolean shouldCheckCrc(int len, String id) { return true; } /** * Throws PngjInputException if bad signature * * @param buf * Signature. Should be of length 8 */ protected void checkSignature(byte[] buf) { if (!Arrays.equals(buf, PngHelperInternal.getPngIdSignature())) throw new PngjInputException("Bad PNG signature"); } /** * If false, we are still reading the signature * * @return true if signature has been read (or if we don't have signature) */ public boolean isSignatureDone() { return signatureDone; } /** * If true, we either have processe the IEND chunk, or close() has been * called, or a fatal error has happened */ public boolean isDone() { return done; } /** * total of bytes read (buffered or not) */ public long getBytesCount() { return bytesCount; } /** * @return Chunks already read, including partial reading (currently * reading) */ public int getChunkCount() { return chunkCount; } /** * Currently reading chunk, or just ended reading * * @return null only if still reading signature */ public ChunkReader getCurChunkReader() { return curChunkReader; } /** * The current deflated set (typically IDAT chunks) reader. This is not null * only while reading that set. Notice that there could be several idat sets * (eg for APNG) */ public DeflatedChunksSet getCurReaderDeflatedSet() { return curReaderDeflatedSet; } /** * Forces a closing (abort) */ public void close() { // forced closing if (curReaderDeflatedSet != null) curReaderDeflatedSet.end(); done = true; } /** * Returns true if we are not in middle of a chunk: we have just ended * reading past chunk , or we are at the start, or end of signature, or we * are done */ public boolean isAtChunkBoundary() { return bytesCount == 0 || bytesCount == 8 || done || curChunkReader == null || curChunkReader.isDone(); } /** * Which should be the id of the first chunk * * @return null if you don't want to check it */ protected String firstChunkId() { return "IHDR"; } /** * Which should be the id of the last chunk * * @return "IEND" */ protected String endChunkId() { return "IEND"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy