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