/// <summary>
        /// Creates the quantized frame.
        /// </summary>
        /// <typeparam name="TPixel">The type of the pixel.</typeparam>
        /// <param name="options">The options.</param>
        /// <param name="image">The image.</param>
        public static IndexedImageFrame <TPixel> CreateQuantizedFrame <TPixel>(
            PngEncoderOptions options,
            Image <TPixel> image)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            if (options.ColorType != PngColorType.Palette)
            {
                return(null);
            }

            // Use the metadata to determine what quantization depth to use if no quantizer has been set.
            if (options.Quantizer is null)
            {
                byte bits      = (byte)options.BitDepth;
                var  maxColors = ImageMaths.GetColorCountForBitDepth(bits);
                options.Quantizer = new WuQuantizer(new QuantizerOptions {
                    MaxColors = maxColors
                });
            }

            // Create quantized frame returning the palette and set the bit depth.
            using (IQuantizer <TPixel> frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer <TPixel>(image.GetConfiguration()))
            {
                ImageFrame <TPixel> frame = image.Frames.RootFrame;
                return(frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()));
            }
        }
        /// <summary>
        /// Creates the quantized frame.
        /// </summary>
        /// <typeparam name="TPixel">The type of the pixel.</typeparam>
        /// <param name="options">The options.</param>
        /// <param name="image">The image.</param>
        public static IndexedImageFrame <TPixel> CreateQuantizedFrame <TPixel>(
            PngEncoderOptions options,
            Image <TPixel> image)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            if (options.ColorType != PngColorType.Palette)
            {
                return(null);
            }

            byte bits = (byte)options.BitDepth;

            if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bits) == -1)
            {
                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 (options.Quantizer is null)
            {
                var maxColors = ImageMaths.GetColorCountForBitDepth(bits);
                options.Quantizer = new WuQuantizer(new QuantizerOptions {
                    MaxColors = maxColors
                });
            }

            // Create quantized frame returning the palette and set the bit depth.
            using (IFrameQuantizer <TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration()))
            {
                ImageFrame <TPixel> frame = image.Frames.RootFrame;
                return(frameQuantizer.QuantizeFrame(frame, frame.Bounds()));
            }
        }
Example #3
0
        /// <summary>
        /// Reads the <see cref="BmpFileHeader"/> and <see cref="BmpInfoHeader"/> from the stream and sets the corresponding fields.
        /// </summary>
        /// <returns>Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
        /// the bytes per color palette entry's can be 3 bytes instead of 4.</returns>
        private int ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
        {
            this.stream = stream;

            this.ReadFileHeader();
            this.ReadInfoHeader();

            // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
            // If the height is negative, then this is a Windows bitmap whose origin
            // is the upper-left corner and not the lower-left. The inverted flag
            // indicates a lower-left origin.Our code will be outputting an
            // upper-left origin pixel array.
            inverted = false;
            if (this.infoHeader.Height < 0)
            {
                inverted = true;
                this.infoHeader.Height = -this.infoHeader.Height;
            }

            int colorMapSize          = -1;
            int bytesPerColorMapEntry = 4;

            if (this.infoHeader.ClrUsed == 0)
            {
                if (this.infoHeader.BitsPerPixel == 1 ||
                    this.infoHeader.BitsPerPixel == 4 ||
                    this.infoHeader.BitsPerPixel == 8)
                {
                    int colorMapSizeBytes     = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
                    int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
                    bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;
                    colorMapSize          = colorMapSizeBytes;
                }
            }
            else
            {
                colorMapSize = this.infoHeader.ClrUsed * bytesPerColorMapEntry;
            }

            palette = null;

            if (colorMapSize > 0)
            {
                // 256 * 4
                if (colorMapSize > 1024)
                {
                    throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
                }

                palette = new byte[colorMapSize];

                this.stream.Read(palette, 0, colorMapSize);
            }

            this.infoHeader.VerifyDimensions();

            return(bytesPerColorMapEntry);
        }
        /// <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;

            // 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.Equals(PngBitDepth.Bit16);

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

            QuantizedFrame <TPixel> quantized           = null;
            ReadOnlySpan <byte>     quantizedPixelsSpan = default;

            if (this.pngColorType == PngColorType.Palette)
            {
                byte bits;

                // Use the metadata to determine what quantization depth to use if no quantizer has been set.
                if (this.quantizer == null)
                {
                    bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
                    int colorSize = ImageMaths.GetColorCountForBitDepth(bits);
                    this.quantizer = new WuQuantizer(colorSize);
                }

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

                // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
                if (bits == 3)
                {
                    bits = 4;
                }
                else if (bits >= 5 && bits <= 7)
                {
                    bits = 8;
                }

                this.bitDepth = bits;
            }
            else
            {
                this.bitDepth = (byte)(this.use16Bit ? 16 : 8);
            }

            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, header, quantized);
            }

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

            quantized?.Dispose();
        }
Example #5
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.Equals(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 == 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);
            }

            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();
        }