/// <summary> /// Reads chunkd from input stream, adds to ChunksList, and returns it. /// If it's skipped, a PngChunkSkipped object is created /// </summary> PngChunk ReadChunk(byte[] chunkid, int clen, bool skipforced) { if (clen < 0) { throw new IO.IOException($"invalid chunk lenght: {clen}"); } // skipChunksByIdSet is created lazyly, if fist IHDR has already been read if (skipChunkIdsSet == null && CurrentChunkGroup > ChunksList.CHUNK_GROUP_0_IDHR) { skipChunkIdsSet = new Dictionary <string, int>(); if (SkipChunkIds != null) { foreach (string id in SkipChunkIds) { skipChunkIdsSet.Add(id, 1); } } } string chunkidstr = ChunkHelper.ToString(chunkid); PngChunk pngChunk = null; bool critical = ChunkHelper.IsCritical(chunkidstr); bool skip = skipforced; if (MaxTotalBytesRead > 0 && clen + offset > MaxTotalBytesRead) { throw new IO.IOException($"Maximum total bytes to read exceeeded: {MaxTotalBytesRead} offset:{offset} clen={clen}"); } // an ancillary chunks can be skipped because of several reasons: if (CurrentChunkGroup > ChunksList.CHUNK_GROUP_0_IDHR && !ChunkHelper.IsCritical(chunkidstr)) { skip = skip || (SkipChunkMaxSize > 0 && clen >= SkipChunkMaxSize) || skipChunkIdsSet.ContainsKey(chunkidstr) || (MaxBytesMetadata > 0 && clen > MaxBytesMetadata - bytesChunksLoaded) || !ChunkHelper.ShouldLoad(chunkidstr, ChunkLoadBehaviour); } if (skip) { PngHelperInternal.SkipBytes(inputStream, clen); PngHelperInternal.ReadInt4(inputStream); // skip - we dont call PngHelperInternal.skipBytes(inputStream, clen + 4) for risk of overflow pngChunk = new PngChunkSkipped(chunkidstr, ImgInfo, clen); } else { ChunkRaw chunk = new ChunkRaw(clen, chunkid, true); chunk.ReadChunkData(inputStream, crcEnabled || critical); pngChunk = PngChunk.Factory(chunk, ImgInfo); if (!pngChunk.Crit) { bytesChunksLoaded += chunk.Len; } } pngChunk.Offset = offset - 8L; chunksList.AppendReadChunk(pngChunk, CurrentChunkGroup); offset += clen + 4L; return(pngChunk); }
/// <summary> /// Reads chunks before first IDAT. Position before: after IDHR (crc included) /// Position after: just after the first IDAT chunk id Returns length of first /// IDAT chunk , -1 if not found /// </summary> /// void ReadFirstChunks() { if (!FirstChunksNotYetRead()) { return; } int clen = 0; bool found = false; byte[] chunkid = new byte[4]; // it's important to reallocate in each this.CurrentChunkGroup = ChunksList.CHUNK_GROUP_1_AFTERIDHR; while (!found) { clen = PngHelperInternal.ReadInt4(inputStream); offset += 4; if (clen < 0) { break; } PngHelperInternal.ReadBytes(inputStream, chunkid, 0, 4); offset += 4; if (PngCsUtils.arraysEqual4(chunkid, Pngcs.Chunks.ChunkHelper.b_IDAT)) { found = true; this.CurrentChunkGroup = ChunksList.CHUNK_GROUP_4_IDAT; // add dummy idat chunk to list chunksList.AppendReadChunk(new PngChunkIDAT(ImgInfo, clen, offset - 8), CurrentChunkGroup); break; } else if (PngCsUtils.arraysEqual4(chunkid, Pngcs.Chunks.ChunkHelper.b_IEND)) { throw new PngjInputException("END chunk found before image data (IDAT) at offset=" + offset); } string chunkids = ChunkHelper.ToString(chunkid); if (chunkids.Equals(ChunkHelper.PLTE)) { this.CurrentChunkGroup = ChunksList.CHUNK_GROUP_2_PLTE; } ReadChunk(chunkid, clen, false); if (chunkids.Equals(ChunkHelper.PLTE)) { this.CurrentChunkGroup = ChunksList.CHUNK_GROUP_3_AFTERPLTE; } } int idatLen = found ? clen : -1; if (idatLen < 0) { throw new PngjInputException("first idat chunk not found!"); } iIdatCstream = new PngIDatChunkInputStream(inputStream, idatLen, offset); idatIstream = ZlibStreamFactory.createZlibInputStream(iIdatCstream, true); if (!crcEnabled) { iIdatCstream.DisableCrcCheck(); } }
/// <summary> /// Reads (and processes ... up to a point) chunks after last IDAT. /// </summary> /// void ReadLastChunks() { CurrentChunkGroup = ChunksList.CHUNK_GROUP_5_AFTERIDAT; // PngHelper.logdebug("idat ended? " + iIdatCstream.isEnded()); if (!iIdatCstream.IsEnded()) { iIdatCstream.ForceChunkEnd(); } int clen = iIdatCstream.GetLenLastChunk(); byte[] chunkid = iIdatCstream.GetIdLastChunk(); bool endfound = false; bool first = true; bool skip = false; while (!endfound) { skip = false; if (!first) { clen = PngHelperInternal.ReadInt4(inputStream); offset += 4; if (clen < 0) { throw new PngjInputException("bad len " + clen); } PngHelperInternal.ReadBytes(inputStream, chunkid, 0, 4); offset += 4; } first = false; if (PngCsUtils.arraysEqual4(chunkid, ChunkHelper.b_IDAT)) { skip = true; // extra dummy (empty?) idat chunk, it can happen, ignore it } else if (PngCsUtils.arraysEqual4(chunkid, ChunkHelper.b_IEND)) { CurrentChunkGroup = ChunksList.CHUNK_GROUP_6_END; endfound = true; } ReadChunk(chunkid, clen, skip); } if (!endfound) { throw new PngjInputException("end chunk not found - offset=" + offset); } // PngHelper.logdebug("end chunk found ok offset=" + offset); }
/// <summary> /// Constructs a PNGReader objet from a opened Stream /// </summary> /// <remarks>The constructor reads the signature and first chunk (IDHR)<seealso cref="FileHelper.CreatePngReader(string)"/> /// </remarks> /// /// <param name="inputStream"></param> /// <param name="filename">Optional, can be the filename or a description.</param> public PngReader(Stream inputStream, string filename) { this.filename = filename != null ? filename : string.Empty; this.inputStream = inputStream; this.chunksList = new ChunksList(null); this.metadata = new PngMetadata(chunksList); this.offset = 0; // set default options this.CurrentChunkGroup = -1; this.ShouldCloseStream = true; this.MaxBytesMetadata = 5 * 1024 * 1024; this.MaxTotalBytesRead = 200 * 1024 * 1024; // 200MB this.SkipChunkMaxSize = 2 * 1024 * 1024; this.SkipChunkIds = new string[] { "fdAT" }; this.ChunkLoadBehaviour = Pngcs.Chunks.ChunkLoadBehaviour.LOAD_CHUNK_ALWAYS; // starts reading: signature byte[] pngid = new byte[8]; PngHelperInternal.ReadBytes(inputStream, pngid, 0, pngid.Length); offset += pngid.Length; if (!PngCsUtils.arraysEqual(pngid, PngHelperInternal.PNG_ID_SIGNATURE)) { throw new PngjInputException("Bad PNG signature"); } CurrentChunkGroup = ChunksList.CHUNK_GROUP_0_IDHR; // reads first chunk IDHR int clen = PngHelperInternal.ReadInt4(inputStream); offset += 4; if (clen != 13) { throw new System.Exception("IDHR chunk len != 13 ?? " + clen); } byte[] chunkid = new byte[4]; PngHelperInternal.ReadBytes(inputStream, chunkid, 0, 4); if (!PngCsUtils.arraysEqual4(chunkid, ChunkHelper.b_IHDR)) { throw new PngjInputException("IHDR not found as first chunk??? [" + ChunkHelper.ToString(chunkid) + "]"); } offset += 4; PngChunkIHDR ihdr = (PngChunkIHDR)ReadChunk(chunkid, clen, false); bool alpha = (ihdr.Colormodel & 0x04) != 0; bool palette = (ihdr.Colormodel & 0x01) != 0; bool grayscale = (ihdr.Colormodel == 0 || ihdr.Colormodel == 4); // creates ImgInfo and imgLine, and allocates buffers ImgInfo = new ImageInfo(ihdr.Cols, ihdr.Rows, ihdr.Bitspc, alpha, grayscale, palette); rowb = new byte[ImgInfo.BytesPerRow + 1]; rowbprev = new byte[rowb.Length]; rowbfilter = new byte[rowb.Length]; interlaced = ihdr.Interlaced == 1; deinterlacer = interlaced ? new PngDeinterlacer(ImgInfo) : null; // some checks if (ihdr.Filmeth != 0 || ihdr.Compmeth != 0 || (ihdr.Interlaced & 0xFFFE) != 0) { throw new PngjInputException("compmethod or filtermethod or interlaced unrecognized"); } if (ihdr.Colormodel < 0 || ihdr.Colormodel > 6 || ihdr.Colormodel == 1 || ihdr.Colormodel == 5) { throw new PngjInputException("Invalid colormodel " + ihdr.Colormodel); } if (ihdr.Bitspc != 1 && ihdr.Bitspc != 2 && ihdr.Bitspc != 4 && ihdr.Bitspc != 8 && ihdr.Bitspc != 16) { throw new PngjInputException("Invalid bit depth " + ihdr.Bitspc); } }