예제 #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="JpegMetadata"/> class.
        /// </summary>
        /// <param name="other">The metadata to create an instance from.</param>
        private JpegMetadata(JpegMetadata other)
        {
            this.ColorType = other.ColorType;

            this.luminanceQuality   = other.luminanceQuality;
            this.chrominanceQuality = other.chrominanceQuality;
        }
예제 #2
0
        /// <summary>
        /// If color type was not set, set it based on the given image.
        /// Note, if there is no metadata and the image has multiple components this method
        /// returns <see langword="null"/> defering the field assignment
        /// to <see cref="InitQuantizationTables(int, JpegMetadata, out Block8x8F, out Block8x8F)"/>.
        /// </summary>
        private static JpegColorType?GetFallbackColorType <TPixel>(Image <TPixel> image)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            // First inspect the image metadata.
            JpegColorType?colorType = null;
            JpegMetadata  metadata  = image.Metadata.GetJpegMetadata();

            if (IsSupportedColorType(metadata.ColorType))
            {
                return(metadata.ColorType);
            }

            // Secondly, inspect the pixel type.
            // TODO: PixelTypeInfo should contain a component count!
            bool isGrayscale =
                typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
                typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);

            // We don't set multi-component color types here since we can set it based upon
            // the quality in InitQuantizationTables.
            if (isGrayscale)
            {
                colorType = JpegColorType.Luminance;
            }

            return(colorType);
        }
예제 #3
0
        /// <summary>
        /// Initializes quantization tables.
        /// </summary>
        /// <remarks>
        /// We take quality values in a hierarchical order:
        /// 1. Check if encoder has set quality
        /// 2. Check if metadata has special table for encoding
        /// 3. Check if metadata has set quality
        /// 4. Take default quality value - 75
        /// </remarks>
        /// <param name="componentCount">Color components count.</param>
        /// <param name="metadata">Jpeg metadata instance.</param>
        /// <param name="luminanceQuantTable">Output luminance quantization table.</param>
        /// <param name="chrominanceQuantTable">Output chrominance quantization table.</param>
        private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable)
        {
            int lumaQuality;
            int chromaQuality;

            if (this.quality.HasValue)
            {
                lumaQuality   = this.quality.Value;
                chromaQuality = this.quality.Value;
            }
            else
            {
                lumaQuality   = metadata.LuminanceQuality;
                chromaQuality = metadata.ChrominanceQuality;
            }

            // Luminance
            lumaQuality         = Numerics.Clamp(lumaQuality, 1, 100);
            luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);

            // Chrominance
            chrominanceQuantTable = default;
            if (componentCount > 1)
            {
                chromaQuality         = Numerics.Clamp(chromaQuality, 1, 100);
                chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);

                if (!this.colorType.HasValue)
                {
                    this.colorType = chromaQuality >= 91 ? JpegColorType.YCbCrRatio444 : JpegColorType.YCbCrRatio420;
                }
            }
        }
예제 #4
0
        /// <summary>
        /// If ColorType was not set, set it based on the given image.
        /// </summary>
        private void InitializeColorType <TPixel>(Image <TPixel> image)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            // First inspect the image metadata.
            if (this.ColorType == null)
            {
                JpegMetadata metadata = image.Metadata.GetJpegMetadata();
                this.ColorType = metadata.ColorType;
            }

            // Secondly, inspect the pixel type.
            if (this.ColorType == null)
            {
                bool isGrayscale =
                    typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
                    typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
                this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
            }
        }
예제 #5
0
        /// <summary>
        /// Encode writes the image to the jpeg baseline format with the given options.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="image">The image to write from.</param>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="cancellationToken">The token to request cancellation.</param>
        public void Encode <TPixel>(Image <TPixel> image, Stream stream, CancellationToken cancellationToken)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength)
            {
                JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height);
            }

            cancellationToken.ThrowIfCancellationRequested();

            this.outputStream = stream;
            ImageMetadata metadata     = image.Metadata;
            JpegMetadata  jpegMetadata = metadata.GetJpegMetadata();

            // If the color type was not specified by the user, preserve the color type of the input image.
            if (!this.colorType.HasValue)
            {
                this.colorType = GetFallbackColorType(image);
            }

            // Compute number of components based on color type in options.
            int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
            ReadOnlySpan <byte> componentIds = this.GetComponentIds();

            // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
            // Initialize the quantization tables.
            this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);

            // Write the Start Of Image marker.
            this.WriteStartOfImage();

            // Do not write APP0 marker for RGB colorspace.
            if (this.colorType != JpegColorType.Rgb)
            {
                this.WriteJfifApplicationHeader(metadata);
            }

            // Write Exif, XMP, ICC and IPTC profiles
            this.WriteProfiles(metadata);

            if (this.colorType == JpegColorType.Rgb)
            {
                // Write App14 marker to indicate RGB color space.
                this.WriteApp14Marker();
            }

            // Write the quantization tables.
            this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);

            // Write the image dimensions.
            this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds);

            // Write the Huffman tables.
            this.WriteDefineHuffmanTables(componentCount);

            // Write the scan header.
            this.WriteStartOfScan(componentCount, componentIds);

            // Write the scan compressed data.
            switch (this.colorType)
            {
            case JpegColorType.YCbCrRatio444:
                new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
                break;

            case JpegColorType.YCbCrRatio420:
                new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
                break;

            case JpegColorType.Luminance:
                new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
                break;

            case JpegColorType.Rgb:
                new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
                break;

            default:
                // all other non-supported color types are checked at the start of this method
                break;
            }

            // Write the End Of Image marker.
            this.WriteEndOfImageMarker();

            stream.Flush();
        }