public static Png Open(Stream stream, PngOpenerSettings settings) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (!stream.CanRead) { throw new ArgumentException($"The provided stream of type {stream.GetType().FullName} was not readable."); } var validHeader = HasValidHeader(stream); if (!validHeader.IsValid) { throw new ArgumentException($"The provided stream did not start with the PNG header. Got {validHeader}."); } var crc = new byte[4]; var imageHeader = ReadImageHeader(stream, crc); var hasEncounteredImageEnd = false; Palette palette = null; using (var output = new MemoryStream()) { using (var memoryStream = new MemoryStream()) { while (TryReadChunkHeader(stream, out var header)) { if (hasEncounteredImageEnd) { if (settings?.DisallowTrailingData == true) { throw new InvalidOperationException($"Found another chunk {header} after already reading the IEND chunk."); } break; } var bytes = new byte[header.Length]; var read = stream.Read(bytes, 0, bytes.Length); if (read != bytes.Length) { throw new InvalidOperationException($"Did not read {header.Length} bytes for the {header} header, only found: {read}."); } if (header.IsCritical) { switch (header.Name) { case "PLTE": if (header.Length % 3 != 0) { throw new InvalidOperationException($"Palette data must be multiple of 3, got {header.Length}."); } // Ignore palette data unless the header.ColorType indicates that the image is paletted. if (imageHeader.ColorType.HasFlag(ColorType.PaletteUsed)) { palette = new Palette(bytes); } break; case "IDAT": memoryStream.Write(bytes, 0, bytes.Length); break; case "IEND": hasEncounteredImageEnd = true; break; default: throw new NotSupportedException($"Encountered critical header {header} which was not recognised."); } } else { switch (header.Name) { case "tRNS": // Add transparency to palette, if the PLTE chunk has been read. if (palette != null) { palette.SetAlphaValues(bytes); } break; } } read = stream.Read(crc, 0, crc.Length); if (read != 4) { throw new InvalidOperationException($"Did not read 4 bytes for the CRC, only found: {read}."); } var result = (int)Crc32.Calculate(Encoding.ASCII.GetBytes(header.Name), bytes); var crcActual = (crc[0] << 24) + (crc[1] << 16) + (crc[2] << 8) + crc[3]; if (result != crcActual) { throw new InvalidOperationException($"CRC calculated {result} did not match file {crcActual} for chunk: {header.Name}."); } settings?.ChunkVisitor?.Visit(stream, imageHeader, header, bytes, crc); } memoryStream.Flush(); memoryStream.Seek(2, SeekOrigin.Begin); using (var deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress)) { deflateStream.CopyTo(output); deflateStream.Close(); } } var bytesOut = output.ToArray(); var(bytesPerPixel, samplesPerPixel) = Decoder.GetBytesAndSamplesPerPixel(imageHeader); bytesOut = Decoder.Decode(bytesOut, imageHeader, bytesPerPixel, samplesPerPixel); return(new Png(imageHeader, new RawPngData(bytesOut, bytesPerPixel, palette, imageHeader), palette?.HasAlphaValues ?? false)); } }
/// <summary> /// Create a new <see cref="RawPngData"/>. /// </summary> /// <param name="data">The decoded pixel data as bytes.</param> /// <param name="bytesPerPixel">The number of bytes in each pixel.</param> /// <param name="width">The width of the image in pixels.</param> /// <param name="interlaceMethod">The interlace method used.</param> /// <param name="palette">The palette for images using indexed colors.</param> /// <param name="colorType">The color type.</param> public RawPngData(byte[] data, int bytesPerPixel, int width, InterlaceMethod interlaceMethod, Palette palette, ColorType colorType) { if (width < 0) { throw new ArgumentOutOfRangeException($"Width must be greater than or equal to 0, got {width}."); } this.data = data ?? throw new ArgumentNullException(nameof(data)); this.bytesPerPixel = bytesPerPixel; this.width = width; this.palette = palette; this.colorType = colorType; rowOffset = interlaceMethod == InterlaceMethod.Adam7 ? 0 : 1; }