From cf1ad8edc2144a479850b53a0a7a76b02a861386 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 20 Jul 2021 16:42:26 +0300 Subject: [PATCH] (WIP) quality --- .../Formats/Jpeg/IJpegEncoderOptions.cs | 13 +++++-- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 34 +++++++++++++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 35 +++++++++++++------ 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index cceed407c2..d2921ad4c0 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -9,11 +9,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg internal interface IJpegEncoderOptions { /// - /// Gets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). + /// Gets the quality, that will be used to encode the luminance data of the image. + /// Quality index must be between 0 and 100 (compression from max to min). /// /// The quality of the jpg image from 0 to 100. - int? Quality { get; } + int? LuminanceQuality { get; } + + /// + /// Gets the quality, that will be used to encode the chrominance data of the image. + /// Quality index must be between 0 and 100 (compression from max to min). + /// + /// The quality of the jpg image from 0 to 100. + int? ChrominanceQuality { get; } /// /// Gets the subsample ration, that will be used to encode the image. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 8131f74d26..27597a0f99 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -18,7 +19,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// index must be between 0 and 100 (compression from max to min). /// Defaults to 75. /// - public int? Quality { get; set; } + public int? Quality + { + [Obsolete("This accessor will soon be deprecated. Use LuminanceQuality and ChrominanceQuality getters instead.", error: false)] + get + { + const int defaultQuality = 75; + + int lumaQuality = this.LuminanceQuality ?? defaultQuality; + int chromaQuality = this.LuminanceQuality ?? lumaQuality; + return (int)Math.Round((lumaQuality + chromaQuality) / 2f); + } + + set + { + this.LuminanceQuality = value; + this.ChrominanceQuality = value; + } + } + + /// + /// Gets or sets the quality, that will be used to encode luminance image data. + /// Quality index must be between 0 and 100 (compression from max to min). + /// Defaults to 75. + /// + public int? LuminanceQuality { get; set; } + + /// + /// Gets or sets the quality, that will be used to encode chrominance image data. + /// Quality index must be between 0 and 100 (compression from max to min). + /// Defaults to 75. + /// + public int? ChrominanceQuality { get; set; } /// /// Gets or sets the subsample ration, that will be used to encode the image. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index c829e0972a..4c81c58dd3 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals { + /// + /// Default JPEG encoding quality for both luminance and chominance tables. + /// + private const int DefaultQualityValue = 75; + /// /// The number of quantization tables. /// @@ -41,7 +46,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// The quality, that will be used to encode the image. /// - private readonly int? quality; + private readonly int? luminanceQuality; + + /// + /// The quality, that will be used to encode the image. + /// + private readonly int? chrominanceQuality; /// /// Gets or sets the subsampling method to use. @@ -59,7 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The options public JpegEncoderCore(IJpegEncoderOptions options) { - this.quality = options.Quality; + this.luminanceQuality = options.LuminanceQuality; + this.chrominanceQuality = options.ChrominanceQuality; this.subsample = options.Subsample; this.colorType = options.ColorType; } @@ -86,19 +97,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.outputStream = stream; ImageMetadata metadata = image.Metadata; + JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); // Compute number of components based on color type in options. int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; - // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. - int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100); - this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420; - // Initialize the quantization tables. - // TODO: This looks ugly, should we write chrominance table for luminance-only images? - // If not - this can code can be simplified - Block8x8F luminanceQuantTable = Quantization.ScaleLuminanceTable(qlty); - Block8x8F chrominanceQuantTable = Quantization.ScaleChrominanceTable(qlty); + // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that + int lumaQuality = Numerics.Clamp(this.luminanceQuality ?? jpegMetadata.LuminanceQuality, 1, 100); + Block8x8F luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality); + Block8x8F chrominanceQuantTable = default; + if (componentCount > 1) + { + int chromaQuality = Numerics.Clamp(this.chrominanceQuality ?? jpegMetadata.ChrominanceQuality, 1, 100); + this.subsample ??= chromaQuality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420; + + chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality); + } // Write the Start Of Image marker. this.WriteApplicationHeader(metadata);