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