Exemple #1
0
        private int CalculateScanlineLenght(IhdrChunk ihdrChunk)
        {
            int scanlineLenght = ihdrChunk.Width;

            if (ihdrChunk.ColorType == E_ColorType.GRAYSCALE)
            {
                scanlineLenght *= 1;
            }
            else if (ihdrChunk.ColorType == E_ColorType.GRAYSCALE_ALPHA)
            {
                scanlineLenght *= 2;
            }
            else if (ihdrChunk.ColorType == E_ColorType.TRUECOLOR)
            {
                scanlineLenght *= 3;
            }
            else if (ihdrChunk.ColorType == E_ColorType.TRUECOLOR_ALPHA)
            {
                scanlineLenght *= 4;
            }
            else
            {
                scanlineLenght *= 1;
            }

            scanlineLenght = scanlineLenght + 1;  //Adding filter byte

            return(scanlineLenght);
        }
Exemple #2
0
    /// <summary>
    /// Attempts to read 25 bytes of the stream.
    /// </summary>
    internal static IhdrChunk Read(Stream stream)
    {
        var chunk = new IhdrChunk
        {
            Length    = BitHelper.ConvertEndian(stream.ReadUInt32()), //Chunk length, 4 bytes.
            ChunkType = Encoding.ASCII.GetString(stream.ReadBytes(4)) //Chunk type, 4 bytes.
        };

        if (chunk.ChunkType != "IHDR")
        {
            throw new Exception("Missing IHDR chunk.");
        }

        //var pos = stream.Position;
        //chunk.ChunkData = stream.ReadBytes(chunk.Length);
        //stream.Position = pos;

        //Chunk details + CRC, 13 bytes + 4 bytes.
        chunk.Width             = BitHelper.ConvertEndian(stream.ReadUInt32());
        chunk.Height            = BitHelper.ConvertEndian(stream.ReadUInt32());
        chunk.BitDepth          = (byte)stream.ReadByte();
        chunk.ColorType         = (byte)stream.ReadByte();
        chunk.CompressionMethod = (byte)stream.ReadByte();
        chunk.FilterMethod      = (byte)stream.ReadByte();
        chunk.InterlaceMethod   = (byte)stream.ReadByte();
        chunk.Crc = BitHelper.ConvertEndian(stream.ReadUInt32());

        return(chunk);
    }
Exemple #3
0
 private void _read()
 {
     _magic = m_io.ReadBytes(8);
     if (!((KaitaiStream.ByteArrayCompare(Magic, new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }) == 0)))
     {
         throw new ValidationNotEqualError(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }, Magic, M_Io, "/seq/0");
     }
     _ihdrLen = m_io.ReadU4be();
     if (!(IhdrLen == 13))
     {
         throw new ValidationNotEqualError(13, IhdrLen, M_Io, "/seq/1");
     }
     _ihdrType = m_io.ReadBytes(4);
     if (!((KaitaiStream.ByteArrayCompare(IhdrType, new byte[] { 73, 72, 68, 82 }) == 0)))
     {
         throw new ValidationNotEqualError(new byte[] { 73, 72, 68, 82 }, IhdrType, M_Io, "/seq/2");
     }
     _ihdr    = new IhdrChunk(m_io, this, m_root);
     _ihdrCrc = m_io.ReadBytes(4);
     _chunks  = new List <Chunk>();
     {
         var   i = 0;
         Chunk M_;
         do
         {
             M_ = new Chunk(m_io, this, m_root);
             _chunks.Add(M_);
             i++;
         } while (!(((M_.Type == "IEND") || (M_Io.IsEof))));
     }
 }
Exemple #4
0
 public DecodedPNGData(IdatChunk idatChunk, IhdrChunk ihdrChunk, PlteChunk plteChunk, string decodedIdat, string unfilteredDatastreamString, string filteredDatastreamString, byte[] unfilteredIdatDatastream, int scanlineLenght)
 {
     IdatChunk   = idatChunk;
     IhdrChunk   = ihdrChunk;
     PlteChunk   = plteChunk;
     DecodedIdat = decodedIdat;
     UnfilteredDatastreamString = unfilteredDatastreamString;
     FilteredDatastreamString   = filteredDatastreamString;
     UnfilteredIdatDatastream   = unfilteredIdatDatastream;
     ScanlineLenght             = scanlineLenght;
 }
Exemple #5
0
        internal bool ReadFrames()
        {
            //Png header, 8 bytes.
            if (!InternalStream.ReadBytes(8).SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }))
            {
                throw new Exception("Invalid file format, expected PNG signature not found.");
            }

            //IHDR chunk, 25 bytes.
            Ihdr = IhdrChunk.Read(InternalStream);

            //aCTl chunk, 16 bytes.
            Actl = ActlChunk.Read(InternalStream);

            //If there's no animation control chunk, it's a normal Png.
            if (Actl == null)
            {
                return(false);
            }

            var masterSequence = 0;
            var frameGroupId   = -1;

            //Read frames.
            while (InternalStream.CanRead)
            {
                //Tries to read any chunk, except IEND.
                var chunk = Chunk.Read(InternalStream, masterSequence++);

                //End reached, prematurely or not.
                if (chunk == null || chunk.ChunkType == "IEND")
                {
                    break;
                }

                //Chunks can be grouped into frames.
                if (new[] { "fcTL", "fdAT", "IDAT" }.Contains(chunk.ChunkType))
                {
                    if (chunk.ChunkType == "fcTL")
                    {
                        frameGroupId++;
                    }

                    chunk.FrameGroupId = frameGroupId;
                }

                Chunks.Add(chunk);
            }

            return(true);
        }
Exemple #6
0
        public async Task CreatePngFile()
        {
            var header = new IhdrChunk(752, 1334, 8, ColorType.TruecolorAlpha);
            var idat   = new IdatChunk(header, rawRgbaData, FilterType);
            var end    = new IendChunk();

            var pngFile = new PngFile()
            {
                header,
                idat,
                end
            };

            using (var ms = new MemoryStream())
                await pngFile.WriteFileAsync(ms);
        }
Exemple #7
0
        internal DecodedPNGData DecodeByteArray(byte[] arrayByte)
        {
            IdatChunk idatChunk = ReadIdatChunk(arrayByte);
            PlteChunk plteChunk = ReadPlteChunk(arrayByte);
            IhdrChunk ihdrChunk = ReadIhdrChunk(arrayByte);

            int scanlineLenght = CalculateScanlineLenght(ihdrChunk);

            byte[] unfilteredIdatDatastream = UnfilterImageDatastream(scanlineLenght, idatChunk, ihdrChunk);

            string decodedBytes = PrintBytesToString(arrayByte, 8);
            string decodedAscii = PrintBytesToAsciiString(arrayByte);

            string decodedIdat = PrintBytesToString(idatChunk.DatastreamBytes, scanlineLenght);
            string unfilteredDatastreamString = PrintBytesToString(unfilteredIdatDatastream, scanlineLenght - 1);
            string filteredDatastreamString   = PrintBytesToString(idatChunk.DatastreamBytes, scanlineLenght);

            return(new DecodedPNGData(idatChunk, ihdrChunk, plteChunk, decodedIdat, unfilteredDatastreamString, filteredDatastreamString, unfilteredIdatDatastream, scanlineLenght));
        }
Exemple #8
0
 private void _read()
 {
     _magic    = m_io.EnsureFixedContents(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 });
     _ihdrLen  = m_io.EnsureFixedContents(new byte[] { 0, 0, 0, 13 });
     _ihdrType = m_io.EnsureFixedContents(new byte[] { 73, 72, 68, 82 });
     _ihdr     = new IhdrChunk(m_io, this, m_root);
     _ihdrCrc  = m_io.ReadBytes(4);
     _chunks   = new List <Chunk>();
     {
         var   i = 0;
         Chunk M_;
         do
         {
             M_ = new Chunk(m_io, this, m_root);
             _chunks.Add(M_);
             i++;
         } while (!(((M_.Type == "IEND") || (M_Io.IsEof))));
     }
 }
Exemple #9
0
        public async Task Can_write_PNG_file(string imageName, FilterType type, int bitDepth)
        {
            var asm      = typeof(PngFileTests).GetTypeInfo().Assembly;
            var resource = asm.GetManifestResourceStream(imageName);

            byte[] rawRgbaData;

            using (var ms = new MemoryStream()) {
                await resource.CopyToAsync(ms);

                rawRgbaData = ms.ToArray();
            }

            var header = new IhdrChunk(752, 1334, bitDepth, ColorType.TruecolorAlpha);
            var idat   = new IdatChunk(header, rawRgbaData, type);
            var end    = new IendChunk();

            var pngFile = new PngFile()
            {
                header,
                idat,
                end
            };

            var path = Path.Combine(Path.GetDirectoryName(asm.Location), $"Zooey-{bitDepth}-{type}.png");

            using (var fs = new FileStream(path, FileMode.Create))
                await pngFile.WriteFileAsync(fs);

            Assert.True(File.Exists(path));
            Assert.Equal(3, pngFile.ChunkCount);

            var result = ToolHelper.RunPngCheck(path);

            Assert.Equal(0, result.ExitCode);
            System.Console.Write(result.StandardOutput);
        }
Exemple #10
0
        private byte[] UnfilterImageDatastream(int scanlineLenght, IdatChunk idatChunk, IhdrChunk ihdrChunk)
        {
            //int datastreamLenght = chunkIhdr.height * chunkIhdr.width * scanlineLenght;
            byte[] datastreamIdat   = idatChunk.DatastreamBytes;
            int    datastreamLenght = datastreamIdat.Length;
            // Datastream after unfitering = size - amount of filter bytes
            int amountOfScanLines = ihdrChunk.Height;

            byte[] refilteredDatastream = new byte[datastreamLenght];

            // Lenght of the scanLine without filter byte
            int scanLineLenWithoutFilter = scanlineLenght - 1;

            // pixel size (in bytes)
            int pixelbytesLenght = 4;

            E_ColorType colorType = ihdrChunk.ColorType;

            if (colorType == E_ColorType.GRAYSCALE)
            {
                pixelbytesLenght = 1;
            }
            else if (colorType == E_ColorType.GRAYSCALE_ALPHA)
            {
                pixelbytesLenght = 2;
            }
            else if (colorType == E_ColorType.TRUECOLOR)
            {
                pixelbytesLenght = 3;
            }
            else if (colorType == E_ColorType.TRUECOLOR_ALPHA)
            {
                pixelbytesLenght = 4;
            }

            //Actual used scanLine and unfilteredScanLine
            byte[] tempScanline;          // Actual filtered scanline
            byte[] previousReconScanline; // Previous scanline which was reconstructed;
            byte[] unfilteredScanline = new byte[scanlineLenght - 1];

            for (int datastreamScanlineIter = 0; datastreamScanlineIter < amountOfScanLines; datastreamScanlineIter++)
            {
                int datastreamIter = (datastreamScanlineIter * (scanlineLenght));
                // Each iteration is on another scanline
                tempScanline = CutFromDatastream(datastreamIdat, datastreamIter, scanlineLenght);

                //Checking tempScanine FILTER TYPE:
                if (tempScanline[0] == 0)
                {
                    //  Console.WriteLine("--- Filter method 0: Non ---");
                    //NOTHING TO DO - FILTER 0
                    for (int scanLineIter = 1; scanLineIter < tempScanline.Length; scanLineIter++)
                    {
                        unfilteredScanline[scanLineIter - 1] = tempScanline[scanLineIter];
                    }
                }
                else if (tempScanline[0] == 1)
                {
                    //   Console.WriteLine("--- Filter method 1: Sub ---");

                    //Actual and previous used pixel (in bytes)
                    byte[] tempPixel   = new byte[pixelbytesLenght];
                    byte[] reconAPixel = new byte[pixelbytesLenght]; // recon(a) - pixel on the left to actual pixel (tempPiexl)
                    byte[] reconXPixel = new byte[pixelbytesLenght]; // recon(x) - reconstructed actual pixel;

                    // Pixel on the left from the first pixel in scanline stores 0 values
                    for (int k = 0; k < pixelbytesLenght; k++)
                    {
                        reconAPixel[k] = 0;
                    }

                    for (int scanLineIter = 1; scanLineIter < tempScanline.Length; scanLineIter += pixelbytesLenght)    // scanLineIter=1 becouse of filter byte
                    {
                        //Update pixel data
                        for (int k = 0; k < pixelbytesLenght; k++)
                        {
                            tempPixel[k] = tempScanline[scanLineIter + k];
                        }

                        for (int pixelIter = 0; pixelIter < pixelbytesLenght; pixelIter++)
                        {
                            reconXPixel[pixelIter] = (byte)(reconAPixel[pixelIter] + tempPixel[pixelIter]);
                            unfilteredScanline[scanLineIter + pixelIter - 1] = reconXPixel[pixelIter];
                            reconAPixel[pixelIter] = reconXPixel[pixelIter];
                        }
                    }
                }
                else if (tempScanline[0] == 2)
                {
                    //   Console.WriteLine("--- Filter method 2: Up ---");

                    //Actual and previous used pixel (in bytes)
                    byte[] tempPixel   = new byte[pixelbytesLenght];
                    byte[] reconBPixel = new byte[pixelbytesLenght]; // recon(b) - pixel above actual pixel (tempPiexl)


                    if (datastreamScanlineIter == 0)
                    {
                        previousReconScanline = new byte[scanlineLenght];
                    }
                    else
                    {
                        previousReconScanline = CutFromDatastream(refilteredDatastream, (datastreamIter - datastreamScanlineIter) - (scanlineLenght - 1), scanlineLenght - 1);
                    }

                    for (int scanLineIter = 1; scanLineIter < tempScanline.Length; scanLineIter += pixelbytesLenght)    // scanLineIter=1 becouse of filter byte
                    {
                        //Update pixel data
                        for (int k = 0; k < pixelbytesLenght; k++)
                        {
                            tempPixel[k] = tempScanline[scanLineIter + k];
                        }
                        for (int k = 0; k < pixelbytesLenght; k++)
                        {
                            reconBPixel[k] = previousReconScanline[scanLineIter - 1 + k];
                        }

                        for (int pixelIter = 0; pixelIter < pixelbytesLenght; pixelIter++)
                        {
                            unfilteredScanline[scanLineIter + pixelIter - 1] = (byte)(reconBPixel[pixelIter] + tempPixel[pixelIter]);
                        }
                    }
                }
                else if (tempScanline[0] == 3)
                {
                    //   Console.WriteLine("--- Filter method 3: Average ---");


                    byte[] tempPixel   = new byte[pixelbytesLenght]; //Actual and previous used pixel (in bytes)
                    byte[] reconBPixel = new byte[pixelbytesLenght]; // recon(b) - pixel above actual pixel (tempPiexl)
                    byte[] reconAPixel = new byte[pixelbytesLenght]; // recon(a) - pixel on the left to actual pixel (tempPiexl)
                    byte[] reconXPixel = new byte[pixelbytesLenght]; // recon(x) - reconstructed actual pixel;

                    if (datastreamScanlineIter == 0)
                    {
                        previousReconScanline = new byte[scanlineLenght]; // stores 0 if there is no previously reconstructed scanline
                    }
                    else
                    {
                        previousReconScanline = CutFromDatastream(refilteredDatastream, (datastreamIter - datastreamScanlineIter) - (scanlineLenght - 1), scanlineLenght - 1);
                    }

                    // Pixel on the left from the first pixel in scanline stores 0 values
                    for (int k = 0; k < pixelbytesLenght; k++)
                    {
                        reconAPixel[k] = 0;
                    }

                    for (int scanLineIter = 1; scanLineIter < tempScanline.Length; scanLineIter += pixelbytesLenght)    // scanLineIter=1 becouse of filter byte
                    {
                        //Update pixel data
                        for (int k = 0; k < pixelbytesLenght; k++)
                        {
                            tempPixel[k] = tempScanline[scanLineIter + k];
                        }
                        for (int k = 0; k < pixelbytesLenght; k++)
                        {
                            reconBPixel[k] = previousReconScanline[scanLineIter - 1 + k];
                        }

                        for (int pixelIter = 0; pixelIter < pixelbytesLenght; pixelIter++)
                        {
                            reconXPixel[pixelIter] = (byte)(tempPixel[pixelIter] + (reconBPixel[pixelIter] + reconAPixel[pixelIter]) / 2);
                            unfilteredScanline[scanLineIter + pixelIter - 1] = reconXPixel[pixelIter];
                            reconAPixel[pixelIter] = reconXPixel[pixelIter];
                        }
                    }
                }
                else if (tempScanline[0] == 4)
                {
                    //   Console.WriteLine("--- Filter method 4: PaethPredictor ---");

                    byte[] tempPixel    = new byte[pixelbytesLenght];
                    byte[] reconAPixel  = new byte[pixelbytesLenght]; // aPixel - pixel on the left to actual pixel
                    byte[] reconAPixel2 = new byte[pixelbytesLenght];
                    byte[] reconBPixel  = new byte[pixelbytesLenght]; // bPixel - pixel above actual pixel
                    byte[] reconCPixel  = new byte[pixelbytesLenght]; // cPixel - pixel on the left to bPixel pixel
                    byte[] reconXPixel  = new byte[pixelbytesLenght]; // recon(x) - reconstructed actual pixel;
                    byte[] reconPPixel  = new byte[pixelbytesLenght];

                    if (datastreamScanlineIter == 0)
                    {
                        previousReconScanline = new byte[scanlineLenght]; // stores 0 if there is no previously reconstructed scanline
                    }
                    else
                    {
                        previousReconScanline = CutFromDatastream(refilteredDatastream, (datastreamIter - datastreamScanlineIter) - (scanlineLenght - 1), scanlineLenght - 1);
                    }

                    for (int k = 0; k < pixelbytesLenght; k++)
                    {
                        // Pixel on the left from the first pixel in scanline stores 0 values
                        reconAPixel[k] = 0;
                        // Pixel on the above-left from the first pixel in scanline stores 0 values
                        reconCPixel[k] = 0;
                        // Pixel on the above from the first pixel in scanline stores 0 values
                        reconBPixel[k] = 0;
                    }

                    for (int scanLineIter = 1; scanLineIter < tempScanline.Length; scanLineIter += pixelbytesLenght)    // scanLineIter=1 becouse of filter byte
                    {
                        //Update pixel data
                        if (datastreamScanlineIter != 0)
                        {
                            for (int k = 0; k < pixelbytesLenght; k++)
                            {
                                reconBPixel[k] = previousReconScanline[scanLineIter - 1 + k];
                            }
                        }

                        if (scanLineIter != 1)
                        {
                            for (int k = 0; k < pixelbytesLenght; k++)
                            {
                                reconCPixel[k] = previousReconScanline[scanLineIter - 1 - pixelbytesLenght + k];
                            }
                        }

                        for (int k = 0; k < pixelbytesLenght; k++)
                        {
                            tempPixel[k]    = tempScanline[scanLineIter + k];
                            reconAPixel2[k] = reconAPixel[k];
                            reconXPixel[k]  = 0;
                            reconPPixel[k]  = 0;
                        }

                        for (int pixelIter = 0; pixelIter < pixelbytesLenght; pixelIter++)
                        {
                            int p, pa, pb, pc = 0;

                            p  = (reconAPixel2[pixelIter] + reconBPixel[pixelIter] - reconCPixel[pixelIter]);
                            pa = (Math.Abs(p - reconAPixel[pixelIter]));
                            pb = (Math.Abs(p - reconBPixel[pixelIter]));
                            pc = (Math.Abs(p - reconCPixel[pixelIter]));

                            if (pa <= pb && pa <= pc)
                            {
                                reconPPixel[pixelIter] = reconAPixel2[pixelIter];
                            }
                            else if (pb <= pc)
                            {
                                reconPPixel[pixelIter] = reconBPixel[pixelIter];
                            }
                            else
                            {
                                reconPPixel[pixelIter] = reconCPixel[pixelIter];
                            }

                            int x = (tempPixel[pixelIter] + reconPPixel[pixelIter]);
                            reconXPixel[pixelIter] = (byte)(tempPixel[pixelIter] + reconPPixel[pixelIter]);
                            unfilteredScanline[scanLineIter + pixelIter - 1] = reconXPixel[pixelIter];
                            reconAPixel[pixelIter] = reconXPixel[pixelIter];
                        }
                    }
                }
                else
                {
                    Console.WriteLine(" WRONG METHOD CODE !!!:  " + tempScanline[0] + "  Scanline: " + datastreamScanlineIter);
                    //NOTHING TO DO - FILTER 0
                    //for (int scanLineIter = 1; scanLineIter < tempScanline.Length; scanLineIter++)
                    //{
                    //    unfilteredScanline[scanLineIter - 1] = tempScanline[scanLineIter];
                    //}
                }

                // Fill refilteredDatastream with refiltered scanLine
                int refilteredDatastreamIter = datastreamScanlineIter * (scanlineLenght - 1);
                for (int k = 0; k < scanlineLenght - 1; k++) // k=1 becouse of filter byte
                {
                    refilteredDatastream[refilteredDatastreamIter + k] = unfilteredScanline[k];
                }
            }
            return(refilteredDatastream);
        }
Exemple #11
0
        private ApngImage(byte[] fileBytes)
        {
            // first we try to identify the type of stream
            MemoryStream ms  = new MemoryStream(fileBytes);
            Bitmap       img = new Bitmap(ms);

            ms.Seek(0, SeekOrigin.Begin);

            // if GIF (animated or not )
            if (ImageFormat.Gif.Equals(img.RawFormat))
            {
                try
                {
                    NumFrames           = img.GetFrameCount(FrameDimension.Time);
                    IsSimplePng         = false;
                    DefaultImage._image = new Bitmap(img);
                    for (int i = 0; i < NumFrames; i++)
                    {
                        Frame  f     = new Frame();
                        byte[] times = img.GetPropertyItem(0x5100).Value;
                        int    dur   = BitConverter.ToInt32(times, 4 * i);
                        img.SelectActiveFrame(FrameDimension.Time, i);
                        f._image = img.Clone(new Rectangle(Point.Empty, img.Size), PixelFormat.Format32bppArgb);
                        f._delay = dur / 100.0;
                        _frames.Add(f);
                    }
                }
                catch
                {
                    IsSimplePng         = true;
                    DefaultImage._image = img;
                    DefaultImage._delay = 0;
                    NumFrames           = 0;
                    _frames.Insert(0, DefaultImage);
                }
                img.Dispose();
                return;
            }

            // if JPEG,... it is a static image
            if (!ImageFormat.Png.Equals(img.RawFormat))
            {
                IsSimplePng         = true;
                DefaultImage._image = img;
                DefaultImage._delay = 0;
                NumFrames           = 0;
                _frames.Insert(0, DefaultImage);
                return;
            }

            // Finally process APNG files

            // check file signature.
            if (!Helper.IsBytesEqual(ms.ReadBytes(Frame.Signature.Length), Frame.Signature))
            {
                throw new Exception("File signature incorrect.");
            }

            // Read IHDR chunk.
            IhdrChunk = new IhdrChunk(ms);
            if (IhdrChunk.ChunkType != "IHDR")
            {
                throw new Exception("IHDR chunk must located before any other chunks.");
            }

            Bitmap   canvas   = new Bitmap(IhdrChunk.Width, IhdrChunk.Height, PixelFormat.Format32bppArgb);
            Graphics graphics = Graphics.FromImage(canvas);

            // Now let's loop in chunks
            try
            {
                Chunk chunk;
                Frame frame               = null;
                var   otherChunks         = new List <OtherChunk>();
                var   isIdatAlreadyParsed = false;
                do
                {
                    if (ms.Position == ms.Length)
                    {
                        throw new Exception("IEND chunk expected.");
                    }

                    chunk = new Chunk(ms);

                    switch (chunk.ChunkType)
                    {
                    case "IHDR":
                        throw new Exception("Only single IHDR is allowed.");
                    // break;

                    // case "bKGD": is not processed

                    case "acTL":
                        if (IsSimplePng)
                        {
                            throw new Exception("acTL chunk must located before any IDAT and fdAT");
                        }

                        AcTlChunk = new AcTlChunk(chunk);
                        break;

                    case "IDAT":
                        // To be an ApngImage, acTL must located before any IDAT and fdAT.
                        if (AcTlChunk == null)
                        {
                            IsSimplePng = true;
                        }

                        // Only default image has IDAT.
                        DefaultImage.IhdrChunk = IhdrChunk;
                        DefaultImage.AddIdatChunk(new IdatChunk(chunk));
                        isIdatAlreadyParsed = true;

                        if (DefaultImage.FcTlChunk != null)
                        {
                            _frames.Insert(0, DefaultImage);
                            DefaultImageIsAnimated = true;
                            //graphics.DrawImage(Image.FromStream(DefaultImage.GetStream()), DefaultImage.FcTlChunk.XOffset, DefaultImage.FcTlChunk.YOffset);
                            //DefaultImage._image = (Bitmap)(canvas.Clone());
                        }
                        break;

                    case "fcTL":
                        // Simple PNG should ignore this.
                        if (IsSimplePng)
                        {
                            continue;
                        }

                        if (frame != null && frame.IdatChunks.Count == 0)
                        {
                            throw new Exception("One frame must have only one fcTL chunk.");
                        }

                        // IDAT already parsed means this fcTL is used by FRAME IMAGE.
                        if (isIdatAlreadyParsed)
                        {
                            // register current frame object and build a new frame object
                            // for next use
                            if (frame != null)
                            {
                                _frames.Add(frame);
                            }
                            frame = new Frame
                            {
                                IhdrChunk = IhdrChunk,
                                FcTlChunk = new FcTlChunk(chunk)
                            };
                        }
                        // Otherwise this fcTL is used by the DEFAULT IMAGE.
                        else
                        {
                            DefaultImage.FcTlChunk = new FcTlChunk(chunk);
                        }
                        break;

                    case "fdAT":
                        // Simple PNG should ignore this.
                        if (IsSimplePng)
                        {
                            continue;
                        }

                        // fdAT is only used by frame image.
                        if (frame == null || frame.FcTlChunk == null)
                        {
                            throw new Exception("fcTL chunk expected.");
                        }

                        frame.AddIdatChunk(new FdAtChunk(chunk).ToIdatChunk());
                        //graphics.DrawImage(Image.FromStream(frame.GetStream()), DefaultImage.FcTlChunk.XOffset, DefaultImage.FcTlChunk.YOffset);
                        //DefaultImage._image = (Bitmap)(canvas.Clone());
                        break;

                    case "IEND":
                        // register last frame object
                        if (frame != null)
                        {
                            _frames.Add(frame);
                        }

                        if (DefaultImage.IdatChunks.Count != 0)
                        {
                            DefaultImage.IendChunk = new IendChunk(chunk);
                        }
                        foreach (var f in _frames)
                        {
                            f.IendChunk = new IendChunk(chunk);
                        }
                        break;

                    default:
                        otherChunks.Add(new OtherChunk(chunk));
                        break;
                    }
                } while (chunk.ChunkType != "IEND");

                DefaultImage.GetImage(); // to force Default._image generation

                // Now we should apply every chunk in otherChunks to every frame.
                //_frames.ForEach(f => otherChunks.ForEach(f.AddOtherChunk));

                NumFrames = _frames.Count;
                for (int j = 0; j < NumFrames; j++)
                {
                    otherChunks.ForEach(_frames[j].AddOtherChunk);

                    if (_frames[j].FcTlChunk.BlendOp == BlendOps.ApngBlendOpSource)
                    {
                        graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                    }
                    else
                    {
                        graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
                    }

                    graphics.DrawImage(Image.FromStream(_frames[j].GetStream()), _frames[j].FcTlChunk.XOffset, _frames[j].FcTlChunk.YOffset);
                    _frames[j]._image = (Bitmap)(canvas.Clone());
                    if (_frames[j].FcTlChunk.DisposeOp == DisposeOps.ApngDisposeOpBackground)
                    {
                        graphics.Clear(Color.Transparent);
                    }
                    else if (_frames[j].FcTlChunk.DisposeOp == DisposeOps.ApngDisposeOpPrevious && j > 0)
                    {
                        graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                        graphics.DrawImage(_frames[j - 1]._image, Point.Empty);
                    }
                }
            }
            finally
            {
                canvas?.Dispose();
                graphics?.Dispose();
            }
        }