예제 #1
0
        /// <summary>
        /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
        /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
        public void Encode <TPixel>(Image <TPixel> image, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            this.width  = image.Width;
            this.height = image.Height;

            ImageMetadata metadata    = image.Metadata;
            PngMetadata   pngMetadata = metadata.GetPngMetadata();

            PngEncoderOptionsHelpers.AdjustOptions <TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
            IQuantizedFrame <TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);

            this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);

            stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);

            this.WriteHeaderChunk(stream);
            this.WritePaletteChunk(stream, quantized);
            this.WriteTransparencyChunk(stream, pngMetadata);
            this.WritePhysicalChunk(stream, metadata);
            this.WriteGammaChunk(stream);
            this.WriteExifChunk(stream, metadata);
            this.WriteTextChunks(stream, pngMetadata);
            this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
            this.WriteEndChunk(stream);
            stream.Flush();

            quantized?.Dispose();
        }
예제 #2
0
        /// <summary>
        /// Adjusts the options based upon the given metadata.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="pngMetadata">The PNG metadata.</param>
        /// <param name="use16Bit">if set to <c>true</c> [use16 bit].</param>
        /// <param name="bytesPerPixel">The bytes per pixel.</param>
        public static void AdjustOptions <TPixel>(
            PngEncoderOptions options,
            PngMetadata pngMetadata,
            out bool use16Bit,
            out int bytesPerPixel)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            // Always take the encoder options over the metadata values.
            options.Gamma ??= pngMetadata.Gamma;

            // Use options, then check metadata, if nothing set there then we suggest
            // a sensible default based upon the pixel format.
            options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType <TPixel>();
            options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth <TPixel>();

            // Ensure bit depth and color type are a supported combination.
            // Bit8 is the only bit depth supported by all color types.
            byte bits = (byte)options.BitDepth;

            byte[] validBitDepths = PngConstants.ColorTypes[options.ColorType.Value];
            if (Array.IndexOf(validBitDepths, bits) == -1)
            {
                options.BitDepth = PngBitDepth.Bit8;
            }

            options.InterlaceMethod ??= pngMetadata.InterlaceMethod;

            use16Bit      = options.BitDepth == PngBitDepth.Bit16;
            bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit);

            if (options.IgnoreMetadata)
            {
                options.ChunkFilter = PngChunkFilter.ExcludeAll;
            }
        }
예제 #3
0
        /// <summary>
        /// Adjusts the options based upon the given metadata.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="pngMetadata">The PNG metadata.</param>
        /// <param name="use16Bit">if set to <c>true</c> [use16 bit].</param>
        /// <param name="bytesPerPixel">The bytes per pixel.</param>
        public static void AdjustOptions <TPixel>(
            PngEncoderOptions options,
            PngMetadata pngMetadata,
            out bool use16Bit,
            out int bytesPerPixel)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            // Always take the encoder options over the metadata values.
            options.Gamma ??= pngMetadata.Gamma;

            // Use options, then check metadata, if nothing set there then we suggest
            // a sensible default based upon the pixel format.
            options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType <TPixel>();
            options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth <TPixel>();

            options.InterlaceMethod ??= pngMetadata.InterlaceMethod;

            use16Bit      = options.BitDepth == PngBitDepth.Bit16;
            bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit);

            if (options.IgnoreMetadata)
            {
                options.ChunkFilter = PngChunkFilter.ExcludeAll;
            }

            // Ensure we are not allowing impossible combinations.
            if (!PngConstants.ColorTypes.ContainsKey(options.ColorType.Value))
            {
                throw new NotSupportedException("Color type is not supported or not valid.");
            }
        }
예제 #4
0
        /// <summary>
        /// Reads the raw image information from the specified stream.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        public IImageInfo Identify(Stream stream)
        {
            var         metadata    = new ImageMetadata();
            PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);

            this.currentStream = stream;
            this.currentStream.Skip(8);
            try
            {
                while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
                {
                    try
                    {
                        switch (chunk.Type)
                        {
                        case PngChunkType.Header:
                            this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
                            break;

                        case PngChunkType.Physical:
                            this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Gamma:
                            this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Data:
                            this.SkipChunkDataAndCrc(chunk);
                            break;

                        case PngChunkType.Text:
                            this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.End:
                            this.isEndChunkReached = true;
                            break;
                        }
                    }
                    finally
                    {
                        chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
                    }
                }
            }
            finally
            {
                this.scanline?.Dispose();
                this.previousScanline?.Dispose();
            }

            if (this.header.Width == 0 && this.header.Height == 0)
            {
                PngThrowHelper.ThrowNoHeader();
            }

            return(new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata));
        }
예제 #5
0
 /// <summary>
 /// Initializes a new instance of the <see cref="PngMetadata"/> class.
 /// </summary>
 /// <param name="other">The metadata to create an instance from.</param>
 private PngMetadata(PngMetadata other)
 {
     this.BitDepth          = other.BitDepth;
     this.ColorType         = other.ColorType;
     this.Gamma             = other.Gamma;
     this.HasTrans          = other.HasTrans;
     this.TransparentGray8  = other.TransparentGray8;
     this.TransparentGray16 = other.TransparentGray16;
     this.TransparentRgb24  = other.TransparentRgb24;
     this.TransparentRgb48  = other.TransparentRgb48;
 }
예제 #6
0
        /// <inheritdoc />
        public async Task <Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
        {
            PngDecoderCore decoder = new(configuration, true);
            IImageInfo     info    = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false);

            stream.Position = 0;

            PngMetadata  meta  = info.Metadata.GetPngMetadata();
            PngColorType color = meta.ColorType.GetValueOrDefault();
            PngBitDepth  bits  = meta.BitDepth.GetValueOrDefault();

            switch (color)
            {
            case PngColorType.Grayscale:
                if (bits == PngBitDepth.Bit16)
                {
                    return(!meta.HasTransparency
                            ? await this.DecodeAsync <L16>(configuration, stream, cancellationToken).ConfigureAwait(false)
                            : await this.DecodeAsync <La32>(configuration, stream, cancellationToken).ConfigureAwait(false));
                }

                return(!meta.HasTransparency
                        ? await this.DecodeAsync <L8>(configuration, stream, cancellationToken).ConfigureAwait(false)
                        : await this.DecodeAsync <La16>(configuration, stream, cancellationToken).ConfigureAwait(false));

            case PngColorType.Rgb:
                if (bits == PngBitDepth.Bit16)
                {
                    return(!meta.HasTransparency
                            ? await this.DecodeAsync <Rgb48>(configuration, stream, cancellationToken).ConfigureAwait(false)
                            : await this.DecodeAsync <Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false));
                }

                return(!meta.HasTransparency
                        ? await this.DecodeAsync <Rgb24>(configuration, stream, cancellationToken).ConfigureAwait(false)
                        : await this.DecodeAsync <Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false));

            case PngColorType.Palette:
                return(await this.DecodeAsync <Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false));

            case PngColorType.GrayscaleWithAlpha:
                return((bits == PngBitDepth.Bit16)
                        ? await this.DecodeAsync <La32>(configuration, stream, cancellationToken).ConfigureAwait(false)
                        : await this.DecodeAsync <La16>(configuration, stream, cancellationToken).ConfigureAwait(false));

            case PngColorType.RgbWithAlpha:
                return((bits == PngBitDepth.Bit16)
                        ? await this.DecodeAsync <Rgba64>(configuration, stream, cancellationToken).ConfigureAwait(false)
                        : await this.DecodeAsync <Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false));

            default:
                return(await this.DecodeAsync <Rgba32>(configuration, stream, cancellationToken).ConfigureAwait(false));
            }
        }
예제 #7
0
        /// <inheritdoc />
        public Image Decode(Configuration configuration, Stream stream)
        {
            PngDecoderCore decoder = new(configuration, true);
            IImageInfo     info    = decoder.Identify(configuration, stream);

            stream.Position = 0;

            PngMetadata  meta  = info.Metadata.GetPngMetadata();
            PngColorType color = meta.ColorType.GetValueOrDefault();
            PngBitDepth  bits  = meta.BitDepth.GetValueOrDefault();

            switch (color)
            {
            case PngColorType.Grayscale:
                if (bits == PngBitDepth.Bit16)
                {
                    return(!meta.HasTransparency
                            ? this.Decode <L16>(configuration, stream)
                            : this.Decode <La32>(configuration, stream));
                }

                return(!meta.HasTransparency
                        ? this.Decode <L8>(configuration, stream)
                        : this.Decode <La16>(configuration, stream));

            case PngColorType.Rgb:
                if (bits == PngBitDepth.Bit16)
                {
                    return(!meta.HasTransparency
                            ? this.Decode <Rgb48>(configuration, stream)
                            : this.Decode <Rgba64>(configuration, stream));
                }

                return(!meta.HasTransparency
                        ? this.Decode <Rgb24>(configuration, stream)
                        : this.Decode <Rgba32>(configuration, stream));

            case PngColorType.Palette:
                return(this.Decode <Rgba32>(configuration, stream));

            case PngColorType.GrayscaleWithAlpha:
                return((bits == PngBitDepth.Bit16)
                    ? this.Decode <La32>(configuration, stream)
                    : this.Decode <La16>(configuration, stream));

            case PngColorType.RgbWithAlpha:
                return((bits == PngBitDepth.Bit16)
                    ? this.Decode <Rgba64>(configuration, stream)
                    : this.Decode <Rgba32>(configuration, stream));

            default:
                return(this.Decode <Rgba32>(configuration, stream));
            }
        }
예제 #8
0
        /// <summary>
        /// Initializes a new instance of the <see cref="PngMetadata"/> class.
        /// </summary>
        /// <param name="other">The metadata to create an instance from.</param>
        private PngMetadata(PngMetadata other)
        {
            this.BitDepth         = other.BitDepth;
            this.ColorType        = other.ColorType;
            this.Gamma            = other.Gamma;
            this.InterlaceMethod  = other.InterlaceMethod;
            this.HasTransparency  = other.HasTransparency;
            this.TransparentL8    = other.TransparentL8;
            this.TransparentL16   = other.TransparentL16;
            this.TransparentRgb24 = other.TransparentRgb24;
            this.TransparentRgb48 = other.TransparentRgb48;

            for (int i = 0; i < other.TextData.Count; i++)
            {
                this.TextData.Add(other.TextData[i]);
            }
        }
예제 #9
0
        /// <summary>
        /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
        /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
        public void Encode <TPixel>(Image <TPixel> image, Stream stream)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            this.width  = image.Width;
            this.height = image.Height;

            ImageMetadata metadata = image.Metadata;

            PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);

            PngEncoderOptionsHelpers.AdjustOptions <TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
            Image <TPixel> clonedImage       = null;
            bool           clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear;

            if (clearTransparency)
            {
                clonedImage = image.Clone();
                ClearTransparentPixels(clonedImage);
            }

            IndexedImageFrame <TPixel> quantized = this.CreateQuantizedImage(image, clonedImage);

            stream.Write(PngConstants.HeaderBytes);

            this.WriteHeaderChunk(stream);
            this.WriteGammaChunk(stream);
            this.WritePaletteChunk(stream, quantized);
            this.WriteTransparencyChunk(stream, pngMetadata);
            this.WritePhysicalChunk(stream, metadata);
            this.WriteExifChunk(stream, metadata);
            this.WriteTextChunks(stream, pngMetadata);
            this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream);
            this.WriteEndChunk(stream);

            stream.Flush();

            quantized?.Dispose();
            clonedImage?.Dispose();
        }
예제 #10
0
        /// <summary>
        /// Adjusts the options.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <param name="pngMetadata">The PNG metadata.</param>
        /// <param name="use16Bit">if set to <c>true</c> [use16 bit].</param>
        /// <param name="bytesPerPixel">The bytes per pixel.</param>
        public static void AdjustOptions(
            PngEncoderOptions options,
            PngMetadata pngMetadata,
            out bool use16Bit,
            out int bytesPerPixel)
        {
            // Always take the encoder options over the metadata values.
            options.Gamma           = options.Gamma ?? pngMetadata.Gamma;
            options.ColorType       = options.ColorType ?? pngMetadata.ColorType;
            options.BitDepth        = options.BitDepth ?? pngMetadata.BitDepth;
            options.InterlaceMethod = options.InterlaceMethod ?? pngMetadata.InterlaceMethod;

            use16Bit      = options.BitDepth == PngBitDepth.Bit16;
            bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit);

            // Ensure we are not allowing impossible combinations.
            if (!PngConstants.ColorTypes.ContainsKey(options.ColorType.Value))
            {
                throw new NotSupportedException("Color type is not supported or not valid.");
            }
        }
예제 #11
0
        /// <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="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();
            PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);

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

            try
            {
                while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
                {
                    try
                    {
                        switch (chunk.Type)
                        {
                        case PngChunkType.Header:
                            this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
                            break;

                        case PngChunkType.Physical:
                            this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Gamma:
                            this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Data:
                            if (image is null)
                            {
                                this.InitializeImage(metadata, out image);
                            }

                            this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata);

                            break;

                        case PngChunkType.Palette:
                            var pal = new byte[chunk.Length];
                            Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
                            this.palette = pal;
                            break;

                        case PngChunkType.Transparency:
                            var alpha = new byte[chunk.Length];
                            Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
                            this.paletteAlpha = alpha;
                            this.AssignTransparentMarkers(alpha, pngMetadata);
                            break;

                        case PngChunkType.Text:
                            this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.CompressedText:
                            this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.InternationalText:
                            this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.Exif:
                            if (!this.ignoreMetadata)
                            {
                                var exifData = new byte[chunk.Length];
                                Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
                                metadata.ExifProfile = new ExifProfile(exifData);
                            }

                            break;

                        case PngChunkType.End:
                            this.isEndChunkReached = true;
                            break;
                        }
                    }
                    finally
                    {
                        chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
                    }
                }

                if (image is null)
                {
                    PngThrowHelper.ThrowNoData();
                }

                return(image);
            }
            finally
            {
                this.scanline?.Dispose();
                this.previousScanline?.Dispose();
            }
        }
예제 #12
0
        /// <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="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();
            PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);

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

            try
            {
                while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
                {
                    try
                    {
                        switch (chunk.Type)
                        {
                        case PngChunkType.Header:
                            this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
                            break;

                        case PngChunkType.Physical:
                            this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Gamma:
                            this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Data:
                            if (image is null)
                            {
                                this.InitializeImage(metadata, out image);
                            }

                            using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
                            {
                                deframeStream.AllocateNewBytes(chunk.Length);
                                this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata);
                            }

                            break;

                        case PngChunkType.Palette:
                            byte[] pal = new byte[chunk.Length];
                            Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
                            this.palette = pal;
                            break;

                        case PngChunkType.Transparency:
                            byte[] alpha = new byte[chunk.Length];
                            Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
                            this.paletteAlpha = alpha;
                            this.AssignTransparentMarkers(alpha, pngMetadata);
                            break;

                        case PngChunkType.Text:
                            this.ReadTextChunk(metadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.Exif:
                            if (!this.ignoreMetadata)
                            {
                                byte[] exifData = new byte[chunk.Length];
                                Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
                                metadata.ExifProfile = new ExifProfile(exifData);
                            }

                            break;

                        case PngChunkType.End:
                            this.isEndChunkReached = true;
                            break;
                        }
                    }
                    finally
                    {
                        chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
                    }
                }

                if (image is null)
                {
                    throw new ImageFormatException("PNG Image does not contain a data chunk");
                }

                return(image);
            }
            finally
            {
                this.scanline?.Dispose();
                this.previousScanline?.Dispose();
            }
        }
예제 #13
0
        /// <inheritdoc/>
        public IImageInfo Identify(BufferedReadStream stream)
        {
            var         metadata    = new ImageMetadata();
            PngMetadata pngMetadata = metadata.GetPngMetadata();

            this.currentStream = stream;
            this.currentStream.Skip(8);
            try
            {
                while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk))
                {
                    try
                    {
                        switch (chunk.Type)
                        {
                        case PngChunkType.Header:
                            this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
                            break;

                        case PngChunkType.Physical:
                            this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Gamma:
                            this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
                            break;

                        case PngChunkType.Data:
                            this.SkipChunkDataAndCrc(chunk);
                            break;

                        case PngChunkType.Text:
                            this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.CompressedText:
                            this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.InternationalText:
                            this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
                            break;

                        case PngChunkType.Exif:
                            if (!this.ignoreMetadata)
                            {
                                var exifData = new byte[chunk.Length];
                                Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
                                metadata.ExifProfile = new ExifProfile(exifData);
                            }

                            break;

                        case PngChunkType.End:
                            this.isEndChunkReached = true;
                            break;
                        }
                    }
                    finally
                    {
                        chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
                    }
                }
            }
            finally
            {
                this.scanline?.Dispose();
                this.previousScanline?.Dispose();
            }

            if (this.header.Width == 0 && this.header.Height == 0)
            {
                PngThrowHelper.ThrowNoHeader();
            }

            return(new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata));
        }
예제 #14
0
        /// <summary>
        /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
        /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
        public void Encode <TPixel>(Image <TPixel> image, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            this.configuration = image.GetConfiguration();
            this.width         = image.Width;
            this.height        = image.Height;

            // Always take the encoder options over the metadata values.
            ImageMetadata metadata    = image.Metadata;
            PngMetadata   pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance);

            this.gamma        = this.gamma ?? pngMetadata.Gamma;
            this.writeGamma   = this.gamma > 0;
            this.pngColorType = this.pngColorType ?? pngMetadata.ColorType;
            this.pngBitDepth  = this.pngBitDepth ?? pngMetadata.BitDepth;
            this.use16Bit     = this.pngBitDepth == PngBitDepth.Bit16;

            // Ensure we are not allowing impossible combinations.
            if (!ColorTypes.ContainsKey(this.pngColorType.Value))
            {
                throw new NotSupportedException("Color type is not supported or not valid.");
            }

            stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);

            QuantizedFrame <TPixel> quantized = null;

            if (this.pngColorType == PngColorType.Palette)
            {
                byte bits = (byte)this.pngBitDepth;
                if (!ColorTypes[this.pngColorType.Value].Contains(bits))
                {
                    throw new NotSupportedException("Bit depth is not supported or not valid.");
                }

                // Use the metadata to determine what quantization depth to use if no quantizer has been set.
                if (this.quantizer is null)
                {
                    this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits));
                }

                // Create quantized frame returning the palette and set the bit depth.
                quantized = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration())
                            .QuantizeFrame(image.Frames.RootFrame);
                byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
                bits = Math.Max(bits, quantizedBits);

                // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
                // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not
                // be within the acceptable range.
                if (bits == 3)
                {
                    bits = 4;
                }
                else if (bits >= 5 && bits <= 7)
                {
                    bits = 8;
                }

                this.bitDepth = bits;
            }
            else
            {
                this.bitDepth = (byte)this.pngBitDepth;
                if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth))
                {
                    throw new NotSupportedException("Bit depth is not supported or not valid.");
                }
            }

            this.bytesPerPixel = this.CalculateBytesPerPixel();

            var header = new PngHeader(
                width: image.Width,
                height: image.Height,
                bitDepth: this.bitDepth,
                colorType: this.pngColorType.Value,
                compressionMethod: 0, // None
                filterMethod: 0,
                interlaceMethod: 0);  // TODO: Can't write interlaced yet.

            this.WriteHeaderChunk(stream, header);

            // Collect the indexed pixel data
            if (quantized != null)
            {
                this.WritePaletteChunk(stream, quantized);
            }

            if (pngMetadata.HasTrans)
            {
                this.WriteTransparencyChunk(stream, pngMetadata);
            }

            this.WritePhysicalChunk(stream, metadata);
            this.WriteGammaChunk(stream);
            this.WriteExifChunk(stream, metadata);
            this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
            this.WriteEndChunk(stream);
            stream.Flush();

            quantized?.Dispose();
        }