/// <summary>
        /// Decodes the stream to the image.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="stream">The stream containing image data. </param>
        /// <exception cref="ImageFormatException">
        /// Thrown if the stream does not contain and end chunk.
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// Thrown if the image is larger than the maximum allowable size.
        /// </exception>
        /// <returns>The decoded image</returns>
        public Image <TPixel> Decode <TPixel>(Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            var metadata = new ImageMetaData();

            this.currentStream = stream;
            this.currentStream.Skip(8);
            Image <TPixel>         image  = null;
            PixelAccessor <TPixel> pixels = null;

            try
            {
                using (var deframeStream = new ZlibInflateStream(this.currentStream))
                {
                    PngChunk currentChunk;
                    while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null)
                    {
                        try
                        {
                            switch (currentChunk.Type)
                            {
                            case PngChunkTypes.Header:
                                this.ReadHeaderChunk(currentChunk.Data);
                                this.ValidateHeader();
                                break;

                            case PngChunkTypes.Physical:
                                this.ReadPhysicalChunk(metadata, currentChunk.Data);
                                break;

                            case PngChunkTypes.Data:
                                if (image == null)
                                {
                                    this.InitializeImage(metadata, out image, out pixels);
                                }

                                deframeStream.AllocateNewBytes(currentChunk.Length);
                                this.ReadScanlines(deframeStream.CompressedStream, pixels);
                                stream.Read(this.crcBuffer, 0, 4);
                                break;

                            case PngChunkTypes.Palette:
                                byte[] pal = new byte[currentChunk.Length];
                                Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length);
                                this.palette     = pal;
                                metadata.Quality = pal.Length / 3;
                                break;

                            case PngChunkTypes.PaletteAlpha:
                                byte[] alpha = new byte[currentChunk.Length];
                                Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length);
                                this.paletteAlpha = alpha;
                                break;

                            case PngChunkTypes.Text:
                                this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length);
                                break;

                            case PngChunkTypes.End:
                                this.isEndChunkReached = true;
                                break;
                            }
                        }
                        finally
                        {
                            // Data is rented in ReadChunkData()
                            if (currentChunk.Data != null)
                            {
                                ArrayPool <byte> .Shared.Return(currentChunk.Data);
                            }
                        }
                    }
                }

                return(image);
            }
            finally
            {
                pixels?.Dispose();
                this.scanline?.Dispose();
                this.previousScanline?.Dispose();
            }
        }