587 lines
20 KiB
Java
587 lines
20 KiB
Java
|
package ar.com.hjg.pngj;
|
||
|
|
||
|
import java.io.File;
|
||
|
import java.io.InputStream;
|
||
|
import java.util.zip.Adler32;
|
||
|
import java.util.zip.CRC32;
|
||
|
|
||
|
import ar.com.hjg.pngj.chunks.ChunkLoadBehaviour;
|
||
|
import ar.com.hjg.pngj.chunks.ChunksList;
|
||
|
import ar.com.hjg.pngj.chunks.PngChunkFCTL;
|
||
|
import ar.com.hjg.pngj.chunks.PngChunkFDAT;
|
||
|
import ar.com.hjg.pngj.chunks.PngChunkIDAT;
|
||
|
import ar.com.hjg.pngj.chunks.PngMetadata;
|
||
|
|
||
|
/**
|
||
|
* Reads a PNG image (pixels and/or metadata) from a file or stream.
|
||
|
* <p>
|
||
|
* Each row is read as an {@link ImageLineInt} object (one int per sample), but this can be changed by setting a
|
||
|
* different ImageLineFactory
|
||
|
* <p>
|
||
|
* Internally, this wraps a {@link ChunkSeqReaderPng} with a {@link BufferedStreamFeeder}
|
||
|
* <p>
|
||
|
* The reading sequence is as follows: <br>
|
||
|
* 1. At construction time, the header and IHDR chunk are read (basic image info) <br>
|
||
|
* 2. Afterwards you can set some additional global options. Eg. {@link #setCrcCheckDisabled()}.<br>
|
||
|
* 3. Optional: If you call getMetadata() or getChunksLisk() before start reading the rows, all the chunks before IDAT
|
||
|
* are then loaded and available <br>
|
||
|
* 4a. The rows are read in order by calling {@link #readRow()}. You can also call {@link #readRow(int)} to skip rows
|
||
|
* -but you can't go backwards, at least not with this implementation. This method returns a {@link IImageLine} object
|
||
|
* which can be casted to the concrete class. This class returns by default a {@link ImageLineInt}, but this can be
|
||
|
* changed.<br>
|
||
|
* 4b. Alternatively, you can read all rows, or a subset, in a single call: {@link #readRows()},
|
||
|
* {@link #readRows(int, int, int)} ,etc. In general this consumes more memory, but for interlaced images this is
|
||
|
* equally efficient, and more so if reading a small subset of rows.<br>
|
||
|
* 5. Reading of the last row automatically loads the trailing chunks, and ends the reader.<br>
|
||
|
* 6. end() also loads the trailing chunks, if not done, and finishes cleanly the reading and closes the stream.
|
||
|
* <p>
|
||
|
* See also {@link PngReaderInt} (esentially the same as this, and slightly preferred) and {@link PngReaderByte} (uses
|
||
|
* byte instead of int to store the samples).
|
||
|
*/
|
||
|
public class PngReader {
|
||
|
|
||
|
// some performance/defensive limits
|
||
|
/**
|
||
|
* Defensive limit: refuse to read more than 900MB, can be changed with {@link #setMaxTotalBytesRead(long)}
|
||
|
*/
|
||
|
public static final long MAX_TOTAL_BYTES_READ_DEFAULT = 901001001L; // ~ 900MB
|
||
|
|
||
|
/**
|
||
|
* Defensive limit: refuse to load more than 5MB of ancillary metadata, see {@link #setMaxBytesMetadata(long)} and
|
||
|
* also {@link #addChunkToSkip(String)}
|
||
|
*/
|
||
|
public static final long MAX_BYTES_METADATA_DEFAULT = 5024024; // for ancillary chunks
|
||
|
|
||
|
/**
|
||
|
* Skip ancillary chunks greater than 2MB, see {@link #setSkipChunkMaxSize(long)}
|
||
|
*/
|
||
|
public static final long MAX_CHUNK_SIZE_SKIP = 2024024; // chunks exceeding this size will be skipped (nor even CRC
|
||
|
// checked)
|
||
|
|
||
|
/**
|
||
|
* Basic image info - final and inmutable.
|
||
|
*/
|
||
|
public final ImageInfo imgInfo; // People always told me: be careful what you do, and don't go around declaring public
|
||
|
// fields...
|
||
|
/**
|
||
|
* flag: image was in interlaced format
|
||
|
*/
|
||
|
public final boolean interlaced;
|
||
|
|
||
|
/**
|
||
|
* This object has most of the intelligence to parse the chunks and decompress the IDAT stream
|
||
|
*/
|
||
|
protected final ChunkSeqReaderPng chunkseq;
|
||
|
|
||
|
/**
|
||
|
* Takes bytes from the InputStream and passes it to the ChunkSeqReaderPng. Never null.
|
||
|
*/
|
||
|
protected final BufferedStreamFeeder streamFeeder;
|
||
|
|
||
|
/**
|
||
|
* @see #getMetadata()
|
||
|
*/
|
||
|
protected final PngMetadata metadata; // this a wrapper over chunks
|
||
|
|
||
|
/**
|
||
|
* Current row number (reading or read), numbered from 0
|
||
|
*/
|
||
|
protected int rowNum = -1;
|
||
|
|
||
|
/**
|
||
|
* Represents the set of lines (rows) being read. Normally this works as a cursor, storing only one (the current) row.
|
||
|
* This stores several (perhaps all) rows only if calling {@link #readRows()} or for interlaced images (this later is
|
||
|
* transparent to the user)
|
||
|
*/
|
||
|
protected IImageLineSet<? extends IImageLine> imlinesSet;
|
||
|
|
||
|
/**
|
||
|
* This factory decides the concrete type of the ImageLine that will be used. See {@link ImageLineSetDefault} for
|
||
|
* examples
|
||
|
*/
|
||
|
private IImageLineSetFactory<? extends IImageLine> imageLineSetFactory;
|
||
|
|
||
|
CRC32 idatCrca;// for internal testing
|
||
|
Adler32 idatCrcb;// for internal testing
|
||
|
|
||
|
/**
|
||
|
* Constructs a PngReader object from a stream, with default options. This reads the signature and the first IHDR
|
||
|
* chunk only.
|
||
|
* <p>
|
||
|
* Warning: In case of exception the stream is NOT closed.
|
||
|
* <p>
|
||
|
* Warning: By default the stream will be closed when this object is {@link #close()}d. See
|
||
|
* {@link #PngReader(InputStream,boolean)} or {@link #setShouldCloseStream(boolean)}
|
||
|
* <p>
|
||
|
*
|
||
|
* @param inputStream PNG stream
|
||
|
*/
|
||
|
public PngReader(InputStream inputStream) {
|
||
|
this(inputStream, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Same as {@link #PngReader(InputStream)} but allows to specify early if the stream must be closed
|
||
|
*
|
||
|
* @param inputStream
|
||
|
* @param shouldCloseStream The stream will be closed in case of exception (constructor included) or normal
|
||
|
* termination.
|
||
|
*/
|
||
|
public PngReader(InputStream inputStream, boolean shouldCloseStream) {
|
||
|
streamFeeder = new BufferedStreamFeeder(inputStream);
|
||
|
streamFeeder.setCloseStream(shouldCloseStream);
|
||
|
chunkseq = createChunkSeqReader();
|
||
|
try {
|
||
|
streamFeeder.setFailIfNoFeed(true);
|
||
|
if (!streamFeeder.feedFixed(chunkseq, 36)) // 8+13+12=36 PNG signature+IHDR chunk
|
||
|
throw new PngjInputException("error reading first 21 bytes");
|
||
|
imgInfo = chunkseq.getImageInfo();
|
||
|
interlaced = chunkseq.getDeinterlacer() != null;
|
||
|
setMaxBytesMetadata(MAX_BYTES_METADATA_DEFAULT);
|
||
|
setMaxTotalBytesRead(MAX_TOTAL_BYTES_READ_DEFAULT);
|
||
|
setSkipChunkMaxSize(MAX_CHUNK_SIZE_SKIP);
|
||
|
chunkseq.addChunkToSkip(PngChunkFDAT.ID);// default: skip fdAT chunks!
|
||
|
chunkseq.addChunkToSkip(PngChunkFCTL.ID);// default: skip fctl chunks!
|
||
|
this.metadata = new PngMetadata(chunkseq.chunksList);
|
||
|
// sets a default factory (with ImageLineInt),
|
||
|
// this can be overwriten by a extended constructor, or by a setter
|
||
|
setLineSetFactory(ImageLineSetDefault.getFactoryInt());
|
||
|
rowNum = -1;
|
||
|
} catch (RuntimeException e) {
|
||
|
streamFeeder.close();
|
||
|
chunkseq.close();
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Constructs a PngReader opening a file. Sets <tt>shouldCloseStream=true</tt>, so that the stream will be closed with
|
||
|
* this object.
|
||
|
*
|
||
|
* @param file PNG image file
|
||
|
*/
|
||
|
public PngReader(File file) {
|
||
|
this(PngHelperInternal.istreamFromFile(file), true);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Reads chunks before first IDAT. Normally this is called automatically
|
||
|
* <p>
|
||
|
* Position before: after IDHR (crc included) Position after: just after the first IDAT chunk id
|
||
|
* <P>
|
||
|
* This can be called several times (tentatively), it does nothing if already run
|
||
|
* <p>
|
||
|
* (Note: when should this be called? in the constructor? hardly, because we loose the opportunity to call
|
||
|
* setChunkLoadBehaviour() and perhaps other settings before reading the first row? but sometimes we want to access
|
||
|
* some metadata (plte, phys) before. Because of this, this method can be called explicitly but is also called
|
||
|
* implicititly in some methods (getMetatada(), getChunksList())
|
||
|
*/
|
||
|
protected void readFirstChunks() {
|
||
|
while (chunkseq.currentChunkGroup < ChunksList.CHUNK_GROUP_4_IDAT)
|
||
|
if (streamFeeder.feed(chunkseq) <= 0)
|
||
|
throw new PngjInputException("premature ending reading first chunks");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines which ancillary chunks (metadata) are to be loaded and which skipped.
|
||
|
* <p>
|
||
|
* Additional restrictions may apply. See also {@link #setChunksToSkip(String...)}, {@link #addChunkToSkip(String)},
|
||
|
* {@link #setMaxBytesMetadata(long)}, {@link #setSkipChunkMaxSize(long)}
|
||
|
*
|
||
|
* @param chunkLoadBehaviour {@link ChunkLoadBehaviour}
|
||
|
*/
|
||
|
public void setChunkLoadBehaviour(ChunkLoadBehaviour chunkLoadBehaviour) {
|
||
|
this.chunkseq.setChunkLoadBehaviour(chunkLoadBehaviour);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* All loaded chunks (metada). If we have not yet end reading the image, this will include only the chunks before the
|
||
|
* pixels data (IDAT)
|
||
|
* <p>
|
||
|
* Critical chunks are included, except that all IDAT chunks appearance are replaced by a single dummy-marker IDAT
|
||
|
* chunk. These might be copied to the PngWriter
|
||
|
* <p>
|
||
|
*
|
||
|
* @see #getMetadata()
|
||
|
*/
|
||
|
public ChunksList getChunksList() {
|
||
|
return getChunksList(true);
|
||
|
}
|
||
|
|
||
|
public ChunksList getChunksList(boolean forceLoadingOfFirstChunks) {
|
||
|
if (forceLoadingOfFirstChunks && chunkseq.firstChunksNotYetRead())
|
||
|
readFirstChunks();
|
||
|
return chunkseq.chunksList;
|
||
|
}
|
||
|
|
||
|
int getCurrentChunkGroup() {
|
||
|
return chunkseq.currentChunkGroup;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* High level wrapper over chunksList
|
||
|
*
|
||
|
* @see #getChunksList()
|
||
|
*/
|
||
|
public PngMetadata getMetadata() {
|
||
|
if (chunkseq.firstChunksNotYetRead())
|
||
|
readFirstChunks();
|
||
|
return metadata;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads next row.
|
||
|
*
|
||
|
* The caller must know that there are more rows to read.
|
||
|
*
|
||
|
* @return Never null. Throws PngInputException if no more
|
||
|
*/
|
||
|
public IImageLine readRow() {
|
||
|
return readRow(rowNum + 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* True if last row has not yet been read
|
||
|
*/
|
||
|
public boolean hasMoreRows() {
|
||
|
return rowNum < getCurImgInfo().rows - 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The row number is mostly meant as a check, the rows must be called in ascending order (not necessarily consecutive)
|
||
|
*/
|
||
|
public IImageLine readRow(int nrow) {
|
||
|
if (chunkseq.firstChunksNotYetRead())
|
||
|
readFirstChunks();
|
||
|
if (!interlaced) {
|
||
|
if (imlinesSet == null)
|
||
|
imlinesSet = createLineSet(true, -1, 0, 1);
|
||
|
IImageLine line = imlinesSet.getImageLine(nrow);
|
||
|
if (nrow == rowNum)
|
||
|
return line; // already read??
|
||
|
else if (nrow < rowNum)
|
||
|
throw new PngjInputException("rows must be read in increasing order: " + nrow);
|
||
|
while (rowNum < nrow) {
|
||
|
while (!chunkseq.getIdatSet().isRowReady())
|
||
|
if (streamFeeder.feed(chunkseq) < 1)
|
||
|
throw new PngjInputException("premature ending");
|
||
|
rowNum++;
|
||
|
chunkseq.getIdatSet().updateCrcs(idatCrca, idatCrcb);
|
||
|
if (rowNum == nrow) {
|
||
|
line.readFromPngRaw(chunkseq.getIdatSet().getUnfilteredRow(),
|
||
|
getCurImgInfo().bytesPerRow + 1, 0, 1);
|
||
|
line.endReadFromPngRaw();
|
||
|
}
|
||
|
chunkseq.getIdatSet().advanceToNextRow();
|
||
|
}
|
||
|
return line;
|
||
|
} else { // and now, for something completely different (interlaced!)
|
||
|
if (imlinesSet == null) {
|
||
|
imlinesSet = createLineSet(false, getCurImgInfo().rows, 0, 1);
|
||
|
loadAllInterlaced(getCurImgInfo().rows, 0, 1);
|
||
|
}
|
||
|
rowNum = nrow;
|
||
|
return imlinesSet.getImageLine(nrow);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads all rows in a ImageLineSet This is handy, but less memory-efficient (except for interlaced)
|
||
|
*/
|
||
|
public IImageLineSet<? extends IImageLine> readRows() {
|
||
|
return readRows(getCurImgInfo().rows, 0, 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads a subset of rows.
|
||
|
* <p>
|
||
|
* This method should called once, and not be mixed with {@link #readRow()}
|
||
|
*
|
||
|
* @param nRows how many rows to read (default: imageInfo.rows; negative: autocompute)
|
||
|
* @param rowOffset rows to skip (default:0)
|
||
|
* @param rowStep step between rows to load( default:1)
|
||
|
*/
|
||
|
public IImageLineSet<? extends IImageLine> readRows(int nRows, int rowOffset, int rowStep) {
|
||
|
if (chunkseq.firstChunksNotYetRead())
|
||
|
readFirstChunks();
|
||
|
if (nRows < 0)
|
||
|
nRows = (getCurImgInfo().rows - rowOffset) / rowStep;
|
||
|
if (rowStep < 1 || rowOffset < 0 || nRows == 0
|
||
|
|| nRows * rowStep + rowOffset > getCurImgInfo().rows)
|
||
|
throw new PngjInputException("bad args");
|
||
|
if (rowNum >= rowOffset)
|
||
|
throw new PngjInputException("readRows cannot be mixed with readRow");
|
||
|
imlinesSet = createLineSet(false, nRows, rowOffset, rowStep);
|
||
|
if (!interlaced) {
|
||
|
int m = -1; // last row already read in
|
||
|
while (m < nRows - 1) {
|
||
|
while (!chunkseq.getIdatSet().isRowReady())
|
||
|
if (streamFeeder.feed(chunkseq) < 1)
|
||
|
throw new PngjInputException("Premature ending");
|
||
|
rowNum++;
|
||
|
chunkseq.getIdatSet().updateCrcs(idatCrca, idatCrcb);
|
||
|
m = (rowNum - rowOffset) / rowStep;
|
||
|
if (rowNum >= rowOffset && rowStep * m + rowOffset == rowNum) {
|
||
|
IImageLine line = imlinesSet.getImageLine(rowNum);
|
||
|
line.readFromPngRaw(chunkseq.getIdatSet().getUnfilteredRow(),
|
||
|
getCurImgInfo().bytesPerRow + 1, 0, 1);
|
||
|
line.endReadFromPngRaw();
|
||
|
}
|
||
|
chunkseq.getIdatSet().advanceToNextRow();
|
||
|
}
|
||
|
} else { // and now, for something completely different (interlaced)
|
||
|
loadAllInterlaced(nRows, rowOffset, rowStep);
|
||
|
}
|
||
|
chunkseq.getIdatSet().done();
|
||
|
return imlinesSet;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the factory that creates the ImageLine. By default, this implementation uses ImageLineInt but this can be
|
||
|
* changed (at construction time or later) by calling this method.
|
||
|
* <p>
|
||
|
* See also {@link #createLineSet(boolean, int, int, int)}
|
||
|
*
|
||
|
* @param factory
|
||
|
*/
|
||
|
public void setLineSetFactory(IImageLineSetFactory<? extends IImageLine> factory) {
|
||
|
imageLineSetFactory = factory;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* By default this uses the factory (which, by default creates ImageLineInt). You should rarely override this.
|
||
|
* <p>
|
||
|
* See doc in {@link IImageLineSetFactory#create(ImageInfo, boolean, int, int, int)}
|
||
|
*/
|
||
|
protected IImageLineSet<? extends IImageLine> createLineSet(boolean singleCursor, int nlines,
|
||
|
int noffset, int step) {
|
||
|
return imageLineSetFactory.create(getCurImgInfo(), singleCursor, nlines, noffset, step);
|
||
|
}
|
||
|
|
||
|
protected void loadAllInterlaced(int nRows, int rowOffset, int rowStep) {
|
||
|
IdatSet idat = chunkseq.getIdatSet();
|
||
|
int nread = 0;
|
||
|
do {
|
||
|
while (!chunkseq.getIdatSet().isRowReady())
|
||
|
if (streamFeeder.feed(chunkseq) <= 0)
|
||
|
break;
|
||
|
if (!chunkseq.getIdatSet().isRowReady())
|
||
|
throw new PngjInputException("Premature ending?");
|
||
|
chunkseq.getIdatSet().updateCrcs(idatCrca, idatCrcb);
|
||
|
int rowNumreal = idat.rowinfo.rowNreal;
|
||
|
boolean inset = imlinesSet.hasImageLine(rowNumreal);
|
||
|
if (inset) {
|
||
|
imlinesSet.getImageLine(rowNumreal).readFromPngRaw(idat.getUnfilteredRow(),
|
||
|
idat.rowinfo.buflen, idat.rowinfo.oX, idat.rowinfo.dX);
|
||
|
nread++;
|
||
|
}
|
||
|
idat.advanceToNextRow();
|
||
|
} while (nread < nRows || !idat.isDone());
|
||
|
idat.done();
|
||
|
for (int i = 0, j = rowOffset; i < nRows; i++, j += rowStep) {
|
||
|
imlinesSet.getImageLine(j).endReadFromPngRaw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads all the (remaining) file, skipping the pixels data. This is much more efficient that calling
|
||
|
* {@link #readRow()}, specially for big files (about 10 times faster!), because it doesn't even decompress the IDAT
|
||
|
* stream and disables CRC check Use this if you are not interested in reading pixels,only metadata.
|
||
|
*/
|
||
|
public void readSkippingAllRows() {
|
||
|
chunkseq.addChunkToSkip(PngChunkIDAT.ID);
|
||
|
chunkseq.addChunkToSkip(PngChunkFDAT.ID);
|
||
|
if (chunkseq.firstChunksNotYetRead())
|
||
|
readFirstChunks();
|
||
|
end();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set total maximum bytes to read (0: unlimited; default: 200MB). <br>
|
||
|
* These are the bytes read (not loaded) in the input stream. If exceeded, an exception will be thrown.
|
||
|
*/
|
||
|
public void setMaxTotalBytesRead(long maxTotalBytesToRead) {
|
||
|
chunkseq.setMaxTotalBytesRead(maxTotalBytesToRead);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set total maximum bytes to load from ancillary chunks (0: unlimited; default: 5Mb).<br>
|
||
|
* If exceeded, some chunks will be skipped
|
||
|
*/
|
||
|
public void setMaxBytesMetadata(long maxBytesMetadata) {
|
||
|
chunkseq.setMaxBytesMetadata(maxBytesMetadata);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set maximum size in bytes for individual ancillary chunks (0: unlimited; default: 2MB). <br>
|
||
|
* Chunks exceeding this length will be skipped (the CRC will not be checked) and the chunk will be saved as a
|
||
|
* PngChunkSkipped object. See also setSkipChunkIds
|
||
|
*/
|
||
|
public void setSkipChunkMaxSize(long skipChunkMaxSize) {
|
||
|
chunkseq.setSkipChunkMaxSize(skipChunkMaxSize);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Chunks ids to be skipped. <br>
|
||
|
* These chunks will be skipped (the CRC will not be checked) and the chunk will be saved as a PngChunkSkipped object.
|
||
|
* See also setSkipChunkMaxSize
|
||
|
*/
|
||
|
public void setChunksToSkip(String... chunksToSkip) {
|
||
|
chunkseq.setChunksToSkip(chunksToSkip);
|
||
|
}
|
||
|
|
||
|
public void addChunkToSkip(String chunkToSkip) {
|
||
|
chunkseq.addChunkToSkip(chunkToSkip);
|
||
|
}
|
||
|
|
||
|
public void dontSkipChunk(String chunkToSkip) {
|
||
|
chunkseq.dontSkipChunk(chunkToSkip);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* if true, input stream will be closed after ending read
|
||
|
* <p>
|
||
|
* default=true
|
||
|
*/
|
||
|
public void setShouldCloseStream(boolean shouldCloseStream) {
|
||
|
streamFeeder.setCloseStream(shouldCloseStream);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads till end of PNG stream and call <tt>close()</tt>
|
||
|
*
|
||
|
* This should normally be called after reading the pixel data, to read the trailing chunks and close the stream. But
|
||
|
* it can be called at anytime. This will also read the first chunks if not still read, and skip pixels (IDAT) if
|
||
|
* still pending.
|
||
|
*
|
||
|
* If you want to read all metadata skipping pixels, readSkippingAllRows() is a little more efficient.
|
||
|
*
|
||
|
* If you want to abort immediately, call instead <tt>close()</tt>
|
||
|
*/
|
||
|
public void end() {
|
||
|
try {
|
||
|
if (chunkseq.firstChunksNotYetRead())
|
||
|
readFirstChunks();
|
||
|
if (chunkseq.getIdatSet() != null && !chunkseq.getIdatSet().isDone())
|
||
|
chunkseq.getIdatSet().done();
|
||
|
while (!chunkseq.isDone())
|
||
|
if (streamFeeder.feed(chunkseq) <= 0)
|
||
|
break;
|
||
|
} finally {
|
||
|
close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Releases resources, and closes stream if corresponds. Idempotent, secure, no exceptions.
|
||
|
*
|
||
|
* This can be also called for abort. It is recommended to call this in case of exceptions
|
||
|
*/
|
||
|
public void close() {
|
||
|
try {
|
||
|
if (chunkseq != null)
|
||
|
chunkseq.close();
|
||
|
} catch (Exception e) {
|
||
|
PngHelperInternal.LOGGER.warning("error closing chunk sequence:" + e.getMessage());
|
||
|
}
|
||
|
if (streamFeeder != null)
|
||
|
streamFeeder.close();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Interlaced PNG is accepted -though not welcomed- now...
|
||
|
*/
|
||
|
public boolean isInterlaced() {
|
||
|
return interlaced;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disables the CRC integrity check in IDAT chunks and ancillary chunks, this gives a slight increase in reading speed
|
||
|
* for big files
|
||
|
*/
|
||
|
public void setCrcCheckDisabled() {
|
||
|
chunkseq.setCheckCrc(false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets wrapped {@link ChunkSeqReaderPng} object
|
||
|
*/
|
||
|
public ChunkSeqReaderPng getChunkseq() {
|
||
|
return chunkseq;
|
||
|
}
|
||
|
|
||
|
/** called on construction time. Override if you want an alternative class */
|
||
|
protected ChunkSeqReaderPng createChunkSeqReader() {
|
||
|
return new ChunkSeqReaderPng(false);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Enables and prepare the simple digest computation. Must be called before reading the pixels. See
|
||
|
* {@link #getSimpleDigestHex()}
|
||
|
*/
|
||
|
public void prepareSimpleDigestComputation() {
|
||
|
if (idatCrca == null)
|
||
|
idatCrca = new CRC32();
|
||
|
else
|
||
|
idatCrca.reset();
|
||
|
if (idatCrcb == null)
|
||
|
idatCrcb = new Adler32();
|
||
|
else
|
||
|
idatCrcb.reset();
|
||
|
imgInfo.updateCrc(idatCrca);
|
||
|
idatCrcb.update((byte) imgInfo.rows); // not important
|
||
|
}
|
||
|
|
||
|
long getSimpleDigest() {
|
||
|
if (idatCrca == null)
|
||
|
return 0;
|
||
|
else
|
||
|
return (idatCrca.getValue() ^ (idatCrcb.getValue() << 31));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pseudo 64-bits digest computed over the basic image properties and the raw pixels data: it should coincide for
|
||
|
* equivalent images encoded with different filters and compressors; but will not coincide for
|
||
|
* interlaced/non-interlaced; also, this does not take into account the palette info. This will be valid only if
|
||
|
* {@link #prepareSimpleDigestComputation()} has been called, and all rows have been read. Not fool-proof, not
|
||
|
* cryptografically secure, only for informal testing and duplicates detection.
|
||
|
*
|
||
|
* @return A 64-digest in hexadecimal
|
||
|
*/
|
||
|
public String getSimpleDigestHex() {
|
||
|
return String.format("%016X", getSimpleDigest());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Basic info, for debugging.
|
||
|
*/
|
||
|
public String toString() { // basic info
|
||
|
return imgInfo.toString() + " interlaced=" + interlaced;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Basic info, in a compact format, apt for scripting COLSxROWS[dBITDEPTH][a][p][g][i] ( the default dBITDEPTH='d8' is
|
||
|
* ommited)
|
||
|
*
|
||
|
*/
|
||
|
public String toStringCompact() {
|
||
|
return imgInfo.toStringBrief() + (interlaced ? "i" : "");
|
||
|
}
|
||
|
|
||
|
public ImageInfo getImgInfo() {
|
||
|
return imgInfo;
|
||
|
}
|
||
|
|
||
|
public ImageInfo getCurImgInfo() {
|
||
|
return chunkseq.getCurImgInfo();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
}
|