From cad0ed017b40420b08c6a0d0dde3dd026345b4a3 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 8 May 2022 17:14:39 +0300 Subject: [PATCH] Phase 1: prepare new encoder options API --- .../JpegColorConverter.FromCmykVector.cs | 2 + .../JpegColorConverter.FromYccKVector.cs | 2 + .../Components/Encoder/HuffmanScanEncoder.cs | 7 +- .../Jpeg/Components/Encoder/JpegComponent.cs | 2 +- .../Jpeg/Components/Encoder/JpegFrame.cs | 26 +++---- src/ImageSharp/Formats/Jpeg/JpegColorType.cs | 9 ++- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 76 ++++++++++++++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 20 ++--- 8 files changed, 108 insertions(+), 36 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs index 50228c09a1..00ec1f8689 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -46,7 +46,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs index e90ff9438c..796685278f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -70,7 +70,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 8d404a3fd2..bc765742e9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } - public void EncodeScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + public void EncodeInterleavedScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP @@ -199,6 +199,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.FlushRemainingBytes(); } + public void EncodeSingleComponentScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + } + /// /// Encodes the image with no subsampling. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index 1c071c3352..65425c05c1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { private readonly MemoryAllocator memoryAllocator; - public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, byte quantizationTableIndex) + public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) { this.memoryAllocator = memoryAllocator; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 31e59acf8a..18a543dc33 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal sealed class JpegFrame : IDisposable { - public JpegFrame(MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) + public JpegFrame(Jpeg.JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) { this.ColorSpace = colorSpace; @@ -19,21 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.PixelHeight = image.Height; // int componentCount = 3; - this.Components = new JpegComponent[] + var componentConfigs = frameConfig.Components; + this.Components = new JpegComponent[componentConfigs.Length]; + for (int i = 0; i < this.Components.Length; i++) { - // RGB - new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - - // YCbCr - //new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - //new JpegComponent(allocator, 1, 1, 1) { DcTableId = 2, AcTableId = 3 }, - //new JpegComponent(allocator, 1, 1, 1) { DcTableId = 2, AcTableId = 3 }, - - // Luminance - //new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 } - }; + var componentConfig = componentConfigs[i]; + this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) + { + DcTableId = componentConfig.dcTableSelector, + AcTableId = componentConfig.acTableSelector, + }; + } } public Decoder.JpegColorSpace ColorSpace { get; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs index c15038c23b..ff4fa013df 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs @@ -41,8 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio410 = 4, @@ -58,9 +56,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. - /// - /// Note: Not supported by the encoder. /// Cmyk = 7, + + /// + /// YCCK colorspace. + /// + YccK = 8, } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index fc6e3189fc..7323a30baf 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; @@ -19,6 +20,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public JpegColorType? ColorType { get; set; } + public JpegFrameConfig JpegFrameConfig { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -28,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); encoder.Encode(image, stream); } @@ -43,8 +46,77 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); return encoder.EncodeAsync(image, stream, cancellationToken); } } + + public class JpegFrameConfig + { + public JpegFrameConfig(JpegColorType colorType, int precision) + { + this.ColorType = colorType; + this.Precision = precision; + + int componentCount = GetComponentCountFromColorType(colorType); + this.Components = new JpegComponentConfig[componentCount]; + + static int GetComponentCountFromColorType(JpegColorType colorType) + { + switch (colorType) + { + case JpegColorType.Luminance: + return 1; + case JpegColorType.YCbCrRatio444: + case JpegColorType.YCbCrRatio422: + case JpegColorType.YCbCrRatio420: + case JpegColorType.YCbCrRatio411: + case JpegColorType.YCbCrRatio410: + case JpegColorType.Rgb: + return 3; + case JpegColorType.Cmyk: + case JpegColorType.YccK: + return 4; + default: + throw new ArgumentException($"Unknown jpeg color space: {colorType}"); + } + } + } + + public JpegColorType ColorType { get; } + + public int Precision { get; } + + public JpegComponentConfig[] Components { get; } + + public JpegFrameConfig PopulateComponent(int index, int id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + { + this.Components[index] = new JpegComponentConfig + { + Id = id, + HorizontalSampleFactor = hsf, + VerticalSampleFactor = vsf, + QuantizatioTableIndex = quantIndex, + dcTableSelector = dcIndex, + acTableSelector = acIndex, + }; + + return this; + } + } + + public class JpegComponentConfig + { + public int Id { get; set; } + + public int HorizontalSampleFactor { get; set; } + + public int VerticalSampleFactor { get; set; } + + public int QuantizatioTableIndex { get; set; } + + public int dcTableSelector { get; set; } + + public int acTableSelector { get; set; } + } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 79106856d7..065d9c6019 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -44,6 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private JpegColorType? colorType; + private JpegFrameConfig frameConfig; + /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -53,14 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Initializes a new instance of the class. /// /// The options. - public JpegEncoderCore(IJpegEncoderOptions options) + public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) { this.quality = options.Quality; - if (IsSupportedColorType(options.ColorType)) - { - this.colorType = options.ColorType; - } + this.frameConfig = frameConfig; + this.colorType = frameConfig.ColorType; } /// @@ -87,12 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 componentIds = this.GetComponentIds(); @@ -131,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); - var frame = new Components.Encoder.JpegFrame(Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.colorType.Value)); + var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).EncodeScan(frame, image, quantTables, Configuration.Default, cancellationToken); + new HuffmanScanEncoder(3, stream).EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker();