Mercurial > hg > orthanc-imagej
view com/hjg/pngj/ChunkSeqReaderPng.java @ 4:3f418d4451d6 pngj
add pngj
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 15 Apr 2016 09:52:58 +0200 |
parents | |
children |
line wrap: on
line source
package ar.com.hjg.pngj; import java.util.HashSet; import java.util.List; import java.util.Set; import ar.com.hjg.pngj.ChunkReader.ChunkReaderMode; import ar.com.hjg.pngj.chunks.ChunkFactory; import ar.com.hjg.pngj.chunks.ChunkHelper; import ar.com.hjg.pngj.chunks.ChunkLoadBehaviour; import ar.com.hjg.pngj.chunks.ChunksList; import ar.com.hjg.pngj.chunks.PngChunk; import ar.com.hjg.pngj.chunks.PngChunkIDAT; import ar.com.hjg.pngj.chunks.PngChunkIEND; import ar.com.hjg.pngj.chunks.PngChunkIHDR; import ar.com.hjg.pngj.chunks.PngChunkPLTE; /** * Adds to ChunkSeqReader the storing of PngChunk, with a PngFactory, and imageInfo + deinterlacer. * <p> * Most usual PNG reading should use this class, or a {@link PngReader}, which is a thin wrapper over this. */ public class ChunkSeqReaderPng extends ChunkSeqReader { protected ImageInfo imageInfo; // initialized at parsing the IHDR protected ImageInfo curImageInfo; // can vary, for apng protected Deinterlacer deinterlacer; protected int currentChunkGroup = -1; /** * All chunks, but some of them can have the buffer empty (IDAT and skipped) */ protected ChunksList chunksList = null; protected final boolean callbackMode; private long bytesAncChunksLoaded = 0; // bytes loaded from buffered chunks non-critical chunks (data only) private boolean checkCrc = true; // --- parameters to be set prior to reading --- private boolean includeNonBufferedChunks = false; private Set<String> chunksToSkip = new HashSet<String>(); private long maxTotalBytesRead = 0; private long skipChunkMaxSize = 0; private long maxBytesMetadata = 0; private IChunkFactory chunkFactory; private ChunkLoadBehaviour chunkLoadBehaviour = ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS; public ChunkSeqReaderPng(boolean callbackMode) { super(); this.callbackMode = callbackMode; chunkFactory = new ChunkFactory(); // default factory } private void updateAndCheckChunkGroup(String id) { if (id.equals(PngChunkIHDR.ID)) { // IDHR if (currentChunkGroup < 0) currentChunkGroup = ChunksList.CHUNK_GROUP_0_IDHR; else throw new PngjInputException("unexpected chunk " + id); } else if (id.equals(PngChunkPLTE.ID)) { // PLTE if ((currentChunkGroup == ChunksList.CHUNK_GROUP_0_IDHR || currentChunkGroup == ChunksList.CHUNK_GROUP_1_AFTERIDHR)) currentChunkGroup = ChunksList.CHUNK_GROUP_2_PLTE; else throw new PngjInputException("unexpected chunk " + id); } else if (id.equals(PngChunkIDAT.ID)) { // IDAT (no necessarily the first) if ((currentChunkGroup >= ChunksList.CHUNK_GROUP_0_IDHR && currentChunkGroup <= ChunksList.CHUNK_GROUP_4_IDAT)) currentChunkGroup = ChunksList.CHUNK_GROUP_4_IDAT; else throw new PngjInputException("unexpected chunk " + id); } else if (id.equals(PngChunkIEND.ID)) { // END if ((currentChunkGroup >= ChunksList.CHUNK_GROUP_4_IDAT)) currentChunkGroup = ChunksList.CHUNK_GROUP_6_END; else throw new PngjInputException("unexpected chunk " + id); } else { // ancillary if (currentChunkGroup <= ChunksList.CHUNK_GROUP_1_AFTERIDHR) currentChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR; else if (currentChunkGroup <= ChunksList.CHUNK_GROUP_3_AFTERPLTE) currentChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE; else currentChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT; } } @Override public boolean shouldSkipContent(int len, String id) { if (super.shouldSkipContent(len, id)) return true; if (ChunkHelper.isCritical(id)) return false;// critical chunks are never skipped if (maxTotalBytesRead > 0 && len + getBytesCount() > maxTotalBytesRead) throw new PngjInputException("Maximum total bytes to read exceeeded: " + maxTotalBytesRead + " offset:" + getBytesCount() + " len=" + len); if (chunksToSkip.contains(id)) return true; // specific skip if (skipChunkMaxSize > 0 && len > skipChunkMaxSize) return true; // too big chunk if (maxBytesMetadata > 0 && len > maxBytesMetadata - bytesAncChunksLoaded) return true; // too much ancillary chunks loaded switch (chunkLoadBehaviour) { case LOAD_CHUNK_IF_SAFE: if (!ChunkHelper.isSafeToCopy(id)) return true; break; case LOAD_CHUNK_NEVER: return true; default: break; } return false; } public long getBytesChunksLoaded() { return bytesAncChunksLoaded; } public int getCurrentChunkGroup() { return currentChunkGroup; } public void setChunksToSkip(String... chunksToSkip) { this.chunksToSkip.clear(); for (String c : chunksToSkip) this.chunksToSkip.add(c); } public void addChunkToSkip(String chunkToSkip) { this.chunksToSkip.add(chunkToSkip); } public void dontSkipChunk(String chunkToSkip) { this.chunksToSkip.remove(chunkToSkip); } public boolean firstChunksNotYetRead() { return getCurrentChunkGroup() < ChunksList.CHUNK_GROUP_4_IDAT; } @Override protected void postProcessChunk(ChunkReader chunkR) { super.postProcessChunk(chunkR); if (chunkR.getChunkRaw().id.equals(PngChunkIHDR.ID)) { PngChunkIHDR ch = new PngChunkIHDR(null); ch.parseFromRaw(chunkR.getChunkRaw()); imageInfo = ch.createImageInfo(); curImageInfo = imageInfo; if (ch.isInterlaced()) deinterlacer = new Deinterlacer(curImageInfo); chunksList = new ChunksList(imageInfo); } if (chunkR.mode == ChunkReaderMode.BUFFER && countChunkTypeAsAncillary(chunkR.getChunkRaw().id)) { bytesAncChunksLoaded += chunkR.getChunkRaw().len; } if (chunkR.mode == ChunkReaderMode.BUFFER || includeNonBufferedChunks) { PngChunk chunk = chunkFactory.createChunk(chunkR.getChunkRaw(), getImageInfo()); chunksList.appendReadChunk(chunk, currentChunkGroup); } if (isDone()) { processEndPng(); } } protected boolean countChunkTypeAsAncillary(String id) { return !ChunkHelper.isCritical(id); } @Override protected DeflatedChunksSet createIdatSet(String id) { IdatSet ids = new IdatSet(id, getCurImgInfo(), deinterlacer); ids.setCallbackMode(callbackMode); return ids; } public IdatSet getIdatSet() { DeflatedChunksSet c = getCurReaderDeflatedSet(); return c instanceof IdatSet ? (IdatSet) c : null; } @Override protected boolean isIdatKind(String id) { return id.equals(PngChunkIDAT.ID); } @Override public int consume(byte[] buf, int off, int len) { return super.consume(buf, off, len); } /** * sets a custom chunk factory. This is typically called with a custom class extends ChunkFactory, to adds custom * chunks to the default well-know ones * * @param chunkFactory */ public void setChunkFactory(IChunkFactory chunkFactory) { this.chunkFactory = chunkFactory; } /** * Things to be done after IEND processing. This is not called if prematurely closed. */ protected void processEndPng() { // nothing to do } public ImageInfo getImageInfo() { return imageInfo; } public boolean isInterlaced() { return deinterlacer != null; } public Deinterlacer getDeinterlacer() { return deinterlacer; } @Override protected void startNewChunk(int len, String id, long offset) { updateAndCheckChunkGroup(id); super.startNewChunk(len, id, offset); } @Override public void close() { if (currentChunkGroup != ChunksList.CHUNK_GROUP_6_END)// this could only happen if forced close currentChunkGroup = ChunksList.CHUNK_GROUP_6_END; super.close(); } public List<PngChunk> getChunks() { return chunksList.getChunks(); } public void setMaxTotalBytesRead(long maxTotalBytesRead) { this.maxTotalBytesRead = maxTotalBytesRead; } public long getSkipChunkMaxSize() { return skipChunkMaxSize; } public void setSkipChunkMaxSize(long skipChunkMaxSize) { this.skipChunkMaxSize = skipChunkMaxSize; } public long getMaxBytesMetadata() { return maxBytesMetadata; } public void setMaxBytesMetadata(long maxBytesMetadata) { this.maxBytesMetadata = maxBytesMetadata; } public long getMaxTotalBytesRead() { return maxTotalBytesRead; } @Override protected boolean shouldCheckCrc(int len, String id) { return checkCrc; } public boolean isCheckCrc() { return checkCrc; } public void setCheckCrc(boolean checkCrc) { this.checkCrc = checkCrc; } public boolean isCallbackMode() { return callbackMode; } public Set<String> getChunksToSkip() { return chunksToSkip; } public void setChunkLoadBehaviour(ChunkLoadBehaviour chunkLoadBehaviour) { this.chunkLoadBehaviour = chunkLoadBehaviour; } public ImageInfo getCurImgInfo() { return curImageInfo; } public void updateCurImgInfo(ImageInfo iminfo) { if (!iminfo.equals(curImageInfo)) { curImageInfo = iminfo; } if (deinterlacer != null) deinterlacer = new Deinterlacer(curImageInfo); // we could reset it, but... } /** * If true, the chunks with no data (because skipped or because processed like IDAT-type) are still stored in the * PngChunks list, which might be more informative. * * Setting this to false saves a few bytes * * Default: false * * @param includeNonBufferedChunks */ public void setIncludeNonBufferedChunks(boolean includeNonBufferedChunks) { this.includeNonBufferedChunks = includeNonBufferedChunks; } }