From 96a3c1903f38b872e0b9a8d71290e9e859f02e1d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 6 May 2022 22:30:50 +0300 Subject: [PATCH 01/39] Setup --- .../Components/Decoder/SpectralConverter.cs | 2 +- .../Components/Encoder/HuffmanScanEncoder.cs | 116 ++++++++++++++++ .../Jpeg/Components/Encoder/JpegComponent.cs | 117 ++++++++++++++++ .../Encoder/JpegComponentPostProcessor.cs | 25 ++++ .../Jpeg/Components/Encoder/JpegFrame.cs | 97 +++++++++++++ .../Components/Encoder/SpectralConverter.cs | 12 ++ .../Encoder/SpectralConverter{TPixel}.cs | 128 ++++++++++++++++++ .../Formats/Jpeg/JpegEncoderCore.cs | 39 +++--- 8 files changed, 517 insertions(+), 19 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index acd98bcfcd..9901639a79 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Converter used to convert jpeg spectral data to color pixels. + /// Converter used to convert jpeg spectral data to pixels. /// internal abstract class SpectralConverter { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 6acc6b6db0..7c683af2b3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -128,6 +128,72 @@ private bool IsStreamFlushNeeded get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } + public void Encode(Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + // DEBUG INITIALIZATION SETUP + var frame = new JpegFrame(configuration.MemoryAllocator, image, componentCount: 3); + frame.Init(1, 1); + frame.AllocateComponents(fullScan: false); + + var spectralConverter = new SpectralConverter(configuration); + spectralConverter.InjectFrameData(frame, image, quantTables); + + // DEBUG ENCODING SETUP + int mcu = 0; + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = frame.McusPerLine; + + for (int j = 0; j < mcusPerColumn; j++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Convert from pixels to spectral via given converter + spectralConverter.ConvertStrideBaseline(); + + // decode from binary to spectral + for (int i = 0; i < mcusPerLine; i++) + { + // Scan an interleaved mcu... process components in order + int mcuCol = mcu % mcusPerLine; + for (int k = 0; k < frame.ComponentCount; k++) + { + JpegComponent component = frame.Components[k]; + + ref HuffmanLut dcHuffmanTable = ref this.huffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.huffmanTables[component.AcTableId]; + + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; + + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int x = 0; x < h; x++) + { + int blockCol = (mcuCol * h) + x; + + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable, + ref acHuffmanTable); + } + } + } + + // After all interleaved components, that's an interleaved MCU + mcu++; + } + } + + this.FlushRemainingBytes(); + } + /// /// Encodes the image with no subsampling. /// @@ -441,6 +507,56 @@ private int WriteBlock( return dc; } + private void WriteBlock( + JpegComponent component, + ref Block8x8 block, + ref HuffmanLut dcTable, + ref HuffmanLut acTable) + { + // Emit the DC delta. + int dc = block[0]; + this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor); + component.DcPredictor = dc; + + // Emit the AC components. + int[] acHuffTable = acTable.Values; + + nint lastValuableIndex = block.GetLastNonZeroIndex(); + + int runLength = 0; + ref short blockRef = ref Unsafe.As(ref block); + for (nint zig = 1; zig <= lastValuableIndex; zig++) + { + const int zeroRun1 = 1 << 4; + const int zeroRun16 = 16 << 4; + + int ac = Unsafe.Add(ref blockRef, zig); + if (ac == 0) + { + runLength += zeroRun1; + } + else + { + while (runLength >= zeroRun16) + { + this.EmitHuff(acHuffTable, 0xf0); + runLength -= zeroRun16; + } + + this.EmitHuffRLE(acHuffTable, runLength, ac); + runLength = 0; + } + } + + // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over + // this can be done for any number of trailing zeros, even when all 63 ac values are zero + // (Block8x8F.Size - 1) == 63 - last index of the mcu elements + if (lastValuableIndex != Block8x8F.Size - 1) + { + this.EmitHuff(acHuffTable, 0x00); + } + } + /// /// Emits the most significant count of bits to the buffer. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs new file mode 100644 index 0000000000..3293f55d1e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Represents a single frame component. + /// + internal class JpegComponent : IDisposable + { + private readonly MemoryAllocator memoryAllocator; + + public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, byte quantizationTableIndex) + { + this.memoryAllocator = memoryAllocator; + + this.HorizontalSamplingFactor = horizontalFactor; + this.VerticalSamplingFactor = verticalFactor; + this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + + this.QuantizationTableIndex = quantizationTableIndex; + } + + /// + /// Gets or sets DC coefficient predictor. + /// + public int DcPredictor { get; set; } + + /// + /// Gets the horizontal sampling factor. + /// + public int HorizontalSamplingFactor { get; } + + /// + /// Gets the vertical sampling factor. + /// + public int VerticalSamplingFactor { get; } + + /// + public Buffer2D SpectralBlocks { get; private set; } + + /// + public Size SubSamplingDivisors { get; private set; } + + /// + public int QuantizationTableIndex { get; } + + /// + public Size SizeInBlocks { get; private set; } + + /// + public Size SamplingFactors { get; set; } + + /// + /// Gets the number of blocks per line. + /// + public int WidthInBlocks { get; private set; } + + /// + /// Gets the number of blocks per column. + /// + public int HeightInBlocks { get; private set; } + + /// + /// Gets or sets the index for the DC Huffman table. + /// + public int DcTableId { get; set; } + + /// + /// Gets or sets the index for the AC Huffman table. + /// + public int AcTableId { get; set; } + + /// + public void Dispose() + { + this.SpectralBlocks?.Dispose(); + this.SpectralBlocks = null; + } + + /// + /// Initializes component for future buffers initialization. + /// + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) + { + this.WidthInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); + + this.HeightInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); + + int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; + int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; + this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); + + this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); + + if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) + { + JpegThrowHelper.ThrowBadSampling(); + } + } + + public void AllocateSpectral(bool fullScan) + { + int spectralAllocWidth = this.SizeInBlocks.Width; + int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; + + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs new file mode 100644 index 0000000000..dd9d485a84 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegComponentPostProcessor : IDisposable + { + private readonly JpegComponent component; + + private readonly Block8x8F dequantTable; + + public JpegComponentPostProcessor(JpegComponent component, Block8x8F dequantTable) + { + this.component = component; + this.dequantTable = dequantTable; + } + + public void Dispose() + { + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs new file mode 100644 index 0000000000..585e3bafaa --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -0,0 +1,97 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Represent a single jpeg frame. + /// + internal sealed class JpegFrame : IDisposable + { + public JpegFrame(MemoryAllocator allocator, Image image, byte componentCount) + { + this.PixelWidth = image.Width; + this.PixelHeight = image.Height; + + if (componentCount != 3) + { + throw new ArgumentException("This is YCbCr debug path only."); + } + + this.Components = new JpegComponent[] + { + new JpegComponent(allocator, 1, 1, 0), + new JpegComponent(allocator, 1, 1, 1), + new JpegComponent(allocator, 1, 1, 1), + }; + } + + /// + /// Gets the number of pixel per row. + /// + public int PixelHeight { get; private set; } + + /// + /// Gets the number of pixels per line. + /// + public int PixelWidth { get; private set; } + + /// + /// Gets the number of components within a frame. + /// + public int ComponentCount => this.Components.Length; + + /// + /// Gets the frame component collection. + /// + public JpegComponent[] Components { get; } + + /// + /// Gets or sets the number of MCU's per line. + /// + public int McusPerLine { get; set; } + + /// + /// Gets or sets the number of MCU's per column. + /// + public int McusPerColumn { get; set; } + + /// + public void Dispose() + { + for (int i = 0; i < this.Components.Length; i++) + { + this.Components[i]?.Dispose(); + } + } + + /// + /// Allocates the frame component blocks. + /// + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(int maxSubFactorH, int maxSubFactorV) + { + this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); + + for (int i = 0; i < this.ComponentCount; i++) + { + JpegComponent component = this.Components[i]; + component.Init(this, maxSubFactorH, maxSubFactorV); + } + } + + public void AllocateComponents(bool fullScan) + { + for (int i = 0; i < this.ComponentCount; i++) + { + JpegComponent component = this.Components[i]; + component.AllocateSpectral(fullScan); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs new file mode 100644 index 0000000000..b85cf1d371 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + /// + /// Converter used to convert pixel data to jpeg spectral data. + /// + internal abstract class SpectralConverter + { + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs new file mode 100644 index 0000000000..1065e69c56 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + + /// + internal class SpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + private readonly Configuration configuration; + + private JpegComponentPostProcessor[] componentProcessors; + + private int pixelRowsPerStep; + + private int pixelRowCounter; + + private IMemoryOwner rgbBuffer; + + private IMemoryOwner paddedProxyPixelRow; + + private Buffer2D pixelBuffer; + + public SpectralConverter(Configuration configuration) => + this.configuration = configuration; + + public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] dequantTables) + { + MemoryAllocator allocator = this.configuration.MemoryAllocator; + + // iteration data + int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); + + const int blockPixelHeight = 8; + this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; + + // pixel buffer of the image + // currently codec only supports encoding single frame jpegs + this.pixelBuffer = image.GetRootFramePixelBuffer(); + + // ??? + //this.paddedProxyPixelRow = allocator.Allocate(frame.PixelWidth + 3); + + // component processors from spectral to Rgba32 + const int blockPixelWidth = 8; + var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); + this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; + for (int i = 0; i < this.componentProcessors.Length; i++) + { + JpegComponent component = frame.Components[i]; + this.componentProcessors[i] = new JpegComponentPostProcessor(component, dequantTables[component.QuantizationTableIndex]); + } + + // single 'stride' rgba32 buffer for conversion between spectral and TPixel + //this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); + + // color converter from Rgba32 to TPixel + //this.colorConverter = this.GetColorConverter(frame, jpegData); + } + + public void ConvertStrideBaseline() + { + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates the need of virtual call + // from JpegComponentPostProcessor + this.ConvertStride(spectralStep: 0); + } + + private void ConvertStride(int spectralStep) + { + // 1. Unpack from TPixel to r/g/b planes + // 2. Byte r/g/b planes to normalized float r/g/b planes + // 3. Convert from r/g/b planes to target pixel type with JpegColorConverter + // 4. Convert color buffer to spectral blocks with component post processors + + int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); + + //for (int i = 0; i < this.componentProcessors.Length; i++) + //{ + // this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); + //} + + //int width = this.pixelBuffer.Width; + + //for (int yy = this.pixelRowCounter; yy < maxY; yy++) + //{ + // int y = yy - this.pixelRowCounter; + + // var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + + // this.colorConverter.ConvertToRgbInplace(values); + // values = values.Slice(0, width); // slice away Jpeg padding + + // Span r = this.rgbBuffer.Slice(0, width); + // Span g = this.rgbBuffer.Slice(width, width); + // Span b = this.rgbBuffer.Slice(width * 2, width); + + // SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r); + // SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g); + // SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b); + + // // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. + // // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, + // // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. + // if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) + // { + // PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow); + // } + // else + // { + // Span proxyRow = this.paddedProxyPixelRow.GetSpan(); + // PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); + // proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); + // } + //} + + this.pixelRowCounter += this.pixelRowsPerStep; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index a3cff8f31d..c20babd3d5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -131,25 +131,28 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); + var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; + new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); + // 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; - } + //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(); From 45dfed1411f258c72feaa30049f51e1c176f5112 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 7 May 2022 01:46:21 +0300 Subject: [PATCH 02/39] RGB debug pass encoding done --- .../Jpeg/Components/Block8x8F.ScaledCopyTo.cs | 32 +++++++ .../Formats/Jpeg/Components/Block8x8F.cs | 13 +++ .../JpegColorConverter.FromYCbCrAvx.cs | 5 ++ .../ColorConverters/JpegColorConverterBase.cs | 21 +++++ .../Components/Encoder/HuffmanScanEncoder.cs | 6 ++ .../Jpeg/Components/Encoder/JpegComponent.cs | 4 +- .../Encoder/JpegComponentPostProcessor.cs | 80 +++++++++++++++++- .../Jpeg/Components/Encoder/JpegFrame.cs | 4 +- .../Encoder/SpectralConverter{TPixel}.cs | 83 ++++++++----------- .../Formats/Jpeg/JpegEncoderCore.cs | 40 ++++----- .../PixelFormats/PixelOperations{TPixel}.cs | 37 +++++++++ 11 files changed, 250 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs index 498fe4d03b..ae2d1f722c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; @@ -21,6 +22,37 @@ public void ScaledCopyTo(in Buffer2DRegion region, int horizontalScale, i this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale); } + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyFrom(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride * sizeof(float); + + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 0); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 1); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 2); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 3); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 4); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 5); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 6); + CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 7); + return; + } + + throw new NotImplementedException("This is a test setup!"); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImplFromStrides(ref byte src, ref byte dst, int srcStride, int row) + { + ref byte s = ref Unsafe.Add(ref dst, row * srcStride); + ref byte d = ref Unsafe.Add(ref src, row * 8 * sizeof(float)); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + } + [MethodImpl(InliningOptions.ShortMethod)] public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index d7511fddac..0190fc7454 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -352,6 +352,19 @@ public void NormalizeColorsAndRoundInPlace(float maximum) } } + public void DE_NormalizeColors(float maximum) + { + if (SimdUtils.HasVector8) + { + this.NormalizeColorsAndRoundInPlaceVector8(maximum); + } + else + { + this.NormalizeColorsInPlace(maximum); + this.RoundInPlace(); + } + } + /// /// Rounds all values in the block. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 892bcc79e1..53fa176480 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -68,6 +68,11 @@ public override void ConvertToRgbInplace(in ComponentValues values) c2 = b; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + return; + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 808ca687b4..464358d0e8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -79,6 +79,8 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); + public virtual void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException("This is a test exception"); + /// /// Returns the s for all supported colorspaces and precisions. /// @@ -233,6 +235,25 @@ public ComponentValues(IReadOnlyList processors, int this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } + /// + /// Initializes a new instance of the struct. + /// + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) + { + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); + + this.ComponentCount = processors.Count; + + this.Component0 = processors[0].GetColorBufferRowSpan(row); + + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; + } + internal ComponentValues( int componentCount, Span c0, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 7c683af2b3..ece305a5aa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -132,6 +132,8 @@ public void Encode(Image image, Block8x8F[] quantTables, Configu where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP + this.huffmanTables = HuffmanLut.TheHuffmanLut; + var frame = new JpegFrame(configuration.MemoryAllocator, image, componentCount: 3); frame.Init(1, 1); frame.AllocateComponents(fullScan: false); @@ -188,6 +190,10 @@ ref Unsafe.Add(ref blockRef, blockCol), // After all interleaved components, that's an interleaved MCU mcu++; + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index 3293f55d1e..f9c7eb6349 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -67,12 +67,12 @@ public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int /// /// Gets or sets the index for the DC Huffman table. /// - public int DcTableId { get; set; } + public int DcTableId { get; set; } = 0; // TODO: DEBUG!!! /// /// Gets or sets the index for the AC Huffman table. /// - public int AcTableId { get; set; } + public int AcTableId { get; set; } = 1; // TODO: DEBUG!!! /// public void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index dd9d485a84..8c6008620b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -8,18 +8,90 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { internal class JpegComponentPostProcessor : IDisposable { + private readonly Size blockAreaSize; + private readonly JpegComponent component; - private readonly Block8x8F dequantTable; + private readonly int blockRowsPerStep; + + private Block8x8F quantTable; - public JpegComponentPostProcessor(JpegComponent component, Block8x8F dequantTable) + public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent component, Size postProcessorBufferSize, Block8x8F quantTable) { this.component = component; - this.dequantTable = dequantTable; + this.quantTable = quantTable; + FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable); + + this.component = component; + this.blockAreaSize = this.component.SubSamplingDivisors * 8; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, + this.blockAreaSize.Height); + + this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; } - public void Dispose() + /// + /// Gets the temporary working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } + + public void CopyColorBufferToBlocks(int spectralStep) { + Buffer2D spectralBuffer = this.component.SpectralBlocks; + + // should be this.frame.MaxColorChannelValue + // but currently 12-bit jpegs are not supported + float maximumValue = 255f; + float normalizationValue = -128f; + + int blocksRowsPerStep = this.component.SamplingFactors.Height; + + int destAreaStride = this.ColorBuffer.Width; + + int yBlockStart = spectralStep * this.blockRowsPerStep; + + Size subSamplingDivisors = this.component.SubSamplingDivisors; + + Block8x8F workspaceBlock = default; + + for (int y = 0; y < this.blockRowsPerStep; y++) + { + int yBuffer = y * this.blockAreaSize.Height; + + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + + // load 8x8 block from 8 pixel strides + int xColorBufferStart = xBlock * this.blockAreaSize.Width; + workspaceBlock.ScaledCopyFrom( + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); + + // multiply by maximum (for debug only, should be done in color converter) + workspaceBlock.MultiplyInPlace(maximumValue); + + // level shift via -128f + workspaceBlock.AddInPlace(normalizationValue); + + // FDCT + FastFloatingPointDCT.TransformFDCT(ref workspaceBlock); + + // Quantize and save to spectral blocks + Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); + } + } } + + public Span GetColorBufferRowSpan(int row) + => this.ColorBuffer.DangerousGetRowSpan(row); + + public void Dispose() + => this.ColorBuffer.Dispose(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 585e3bafaa..3343ae02ba 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -24,8 +24,8 @@ public JpegFrame(MemoryAllocator allocator, Image image, byte componentCount) this.Components = new JpegComponent[] { new JpegComponent(allocator, 1, 1, 0), - new JpegComponent(allocator, 1, 1, 1), - new JpegComponent(allocator, 1, 1, 1), + new JpegComponent(allocator, 1, 1, 0), + new JpegComponent(allocator, 1, 1, 0), }; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 1065e69c56..fe9c31b093 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -24,10 +24,10 @@ internal class SpectralConverter : SpectralConverter private IMemoryOwner rgbBuffer; - private IMemoryOwner paddedProxyPixelRow; - private Buffer2D pixelBuffer; + private Decoder.ColorConverters.JpegColorConverterBase colorConverter; + public SpectralConverter(Configuration configuration) => this.configuration = configuration; @@ -46,9 +46,6 @@ public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] de // currently codec only supports encoding single frame jpegs this.pixelBuffer = image.GetRootFramePixelBuffer(); - // ??? - //this.paddedProxyPixelRow = allocator.Allocate(frame.PixelWidth + 3); - // component processors from spectral to Rgba32 const int blockPixelWidth = 8; var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); @@ -56,14 +53,14 @@ public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] de for (int i = 0; i < this.componentProcessors.Length; i++) { JpegComponent component = frame.Components[i]; - this.componentProcessors[i] = new JpegComponentPostProcessor(component, dequantTables[component.QuantizationTableIndex]); + this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, component, postProcessorBufferSize, dequantTables[component.QuantizationTableIndex]); } // single 'stride' rgba32 buffer for conversion between spectral and TPixel - //this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); + this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); - // color converter from Rgba32 to TPixel - //this.colorConverter = this.GetColorConverter(frame, jpegData); + // color converter from Rgb24 to YCbCr + this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: Decoder.JpegColorSpace.YCbCr, precision: 8); } public void ConvertStrideBaseline() @@ -83,44 +80,36 @@ private void ConvertStride(int spectralStep) int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - //for (int i = 0; i < this.componentProcessors.Length; i++) - //{ - // this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); - //} - - //int width = this.pixelBuffer.Width; - - //for (int yy = this.pixelRowCounter; yy < maxY; yy++) - //{ - // int y = yy - this.pixelRowCounter; - - // var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - - // this.colorConverter.ConvertToRgbInplace(values); - // values = values.Slice(0, width); // slice away Jpeg padding - - // Span r = this.rgbBuffer.Slice(0, width); - // Span g = this.rgbBuffer.Slice(width, width); - // Span b = this.rgbBuffer.Slice(width * 2, width); - - // SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r); - // SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g); - // SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b); - - // // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. - // // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, - // // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. - // if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) - // { - // PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow); - // } - // else - // { - // Span proxyRow = this.paddedProxyPixelRow.GetSpan(); - // PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); - // proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); - // } - //} + int width = this.pixelBuffer.Width; + + // unpack TPixel to r/g/b planes + Span r = this.rgbBuffer.Slice(0, width); + Span g = this.rgbBuffer.Slice(width, width); + Span b = this.rgbBuffer.Slice(width * 2, width); + + for (int yy = this.pixelRowCounter; yy < maxY; yy++) + { + int y = yy - this.pixelRowCounter; + + // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. + // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, + // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. + Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); + PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, r, g, b, sourceRow); + + var values = new Decoder.ColorConverters.JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + + SimdUtils.ByteToNormalizedFloat(r, values.Component0); + SimdUtils.ByteToNormalizedFloat(g, values.Component1); + SimdUtils.ByteToNormalizedFloat(b, values.Component2); + + this.colorConverter.ConvertFromRgbInplace(values); + } + + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); + } this.pixelRowCounter += this.pixelRowsPerStep; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index c20babd3d5..7d80278291 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -131,28 +131,28 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); - var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); + //var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; + //new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); // 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; - //} + 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(); diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 710eb9c083..701d63b55f 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -198,6 +198,43 @@ internal virtual void PackFromRgbPlanes( } } + /// + /// Bulk operation that unpacks pixels from + /// into 3 seperate RGB channels. The destination must have a padding of 3. + /// + /// A to configure internal operations. + /// A to the red values. + /// A to the green values. + /// A to the blue values. + /// A to the destination pixels. + internal virtual void UnpackIntoRgbPlanes( + Configuration configuration, + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span source) + { + Guard.NotNull(configuration, nameof(configuration)); + + int count = redChannel.Length; + + Rgba32 rgba32 = default; + ref byte r = ref MemoryMarshal.GetReference(redChannel); + ref byte g = ref MemoryMarshal.GetReference(greenChannel); + ref byte b = ref MemoryMarshal.GetReference(blueChannel); + ref TPixel d = ref MemoryMarshal.GetReference(source); + + for (int i = 0; i < count; i++) + { + // TODO: Create ToRgb24 method in IPixel + // TODO: create a fast intrinsic accelerated Rgb24 -> r/g/b planes overload + Unsafe.Add(ref d, i).ToRgba32(ref rgba32); + Unsafe.Add(ref r, i) = rgba32.R; + Unsafe.Add(ref g, i) = rgba32.G; + Unsafe.Add(ref b, i) = rgba32.B; + } + } + [MethodImpl(InliningOptions.ShortMethod)] internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) { From ebdf4dd91f56344e1a4bce024b3f32c3a890f2ed Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 7 May 2022 02:46:05 +0300 Subject: [PATCH 03/39] Compilation fixes --- .../Components/Encoder/HuffmanScanEncoder.cs | 2 +- .../Jpeg/Components/Encoder/JpegComponent.cs | 6 +---- .../Encoder/SpectralConverter{TPixel}.cs | 2 -- .../Formats/Jpeg/JpegEncoderCore.cs | 24 ++----------------- 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index ece305a5aa..03f503ddee 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -777,7 +777,7 @@ private void FlushToStream(int endIndex) /// /// Flushes spectral data bytes after encoding all channel blocks - /// in a single jpeg macroblock using . + /// in a single jpeg macroblock using . /// /// /// This must be called only if is true diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index f9c7eb6349..f50f9e8891 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -39,19 +39,14 @@ public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int /// public int VerticalSamplingFactor { get; } - /// public Buffer2D SpectralBlocks { get; private set; } - /// public Size SubSamplingDivisors { get; private set; } - /// public int QuantizationTableIndex { get; } - /// public Size SizeInBlocks { get; private set; } - /// public Size SamplingFactors { get; set; } /// @@ -84,6 +79,7 @@ public void Dispose() /// /// Initializes component for future buffers initialization. /// + /// asdfasdf. /// Maximal horizontal subsampling factor among all the components. /// Maximal vertical subsampling factor among all the components. public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index fe9c31b093..e3f659b721 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -9,7 +9,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { - /// internal class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel @@ -77,7 +76,6 @@ private void ConvertStride(int spectralStep) // 2. Byte r/g/b planes to normalized float r/g/b planes // 3. Convert from r/g/b planes to target pixel type with JpegColorConverter // 4. Convert color buffer to spectral blocks with component post processors - int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); int width = this.pixelBuffer.Width; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 7d80278291..910a723fb7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -131,28 +131,8 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); - //var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - //new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); - - // 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; - } + var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; + new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); From 0604a403855c76eb0923dd5135c9eb56f69ca677 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 7 May 2022 23:52:16 +0300 Subject: [PATCH 04/39] Implemented ycbcr/rgb/grayscale color converters --- .../JpegColorConverter.FromCmykAvx.cs | 2 + .../JpegColorConverter.FromCmykScalar.cs | 2 + .../JpegColorConverter.FromCmykVector.cs | 6 ++- .../JpegColorConverter.FromGrayScaleAvx.cs | 16 ++++++ .../JpegColorConverter.FromGrayScaleScalar.cs | 20 +++++-- .../JpegColorConverter.FromGrayScaleVector.cs | 24 +++++++-- .../JpegColorConverter.FromRgbAvx.cs | 23 ++++++++ .../JpegColorConverter.FromRgbScalar.cs | 22 +++++--- .../JpegColorConverter.FromRgbVector.cs | 32 +++++++++-- .../JpegColorConverter.FromYCbCrAvx.cs | 42 ++++++++++++++- .../JpegColorConverter.FromYCbCrScalar.cs | 30 ++++++++++- .../JpegColorConverter.FromYCbCrVector.cs | 54 +++++++++++++++++-- .../JpegColorConverter.FromYccKAvx.cs | 2 + .../JpegColorConverter.FromYccKScalar.cs | 2 + .../JpegColorConverter.FromYccKVector.cs | 6 ++- .../ColorConverters/JpegColorConverterBase.cs | 2 +- .../JpegColorConverterVector.cs | 35 ++++++++++-- .../Components/Encoder/HuffmanScanEncoder.cs | 3 +- .../Jpeg/Components/Encoder/JpegComponent.cs | 4 +- .../Encoder/JpegComponentPostProcessor.cs | 17 ++---- .../Jpeg/Components/Encoder/JpegFrame.cs | 51 +++++++----------- .../Encoder/SpectralConverter{TPixel}.cs | 20 +++---- .../Formats/Jpeg/JpegEncoderCore.cs | 24 ++++++++- 23 files changed, 349 insertions(+), 90 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 7366ee30a9..6429b0ea85 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -18,6 +18,8 @@ public FromCmykAvx(int precision) { } + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new System.NotImplementedException(); + public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index 68dfa9bfba..17459a5851 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -38,6 +38,8 @@ internal static void ConvertCoreInplace(in ComponentValues values, float maxValu c2[i] = y * k; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException(); } } } 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 6b7ed169e3..50228c09a1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -16,7 +16,7 @@ public FromCmykVector(int precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector cBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -44,8 +44,10 @@ protected override void ConvertCoreVectorizedInplace(in ComponentValues values) } } - protected override void ConvertCoreInplace(in ComponentValues values) => + 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.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index 963543ad44..bcb366298d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -33,6 +33,22 @@ public override void ConvertToRgbInplace(in ComponentValues values) c0 = Avx.Multiply(c0, scale); } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + // Used for the color conversion + var scale = Vector256.Create(this.MaximumValue); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + c0 = Avx.Multiply(c0, scale); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs index 3f6a6caa45..a0db2e801c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -16,10 +16,13 @@ public FromGrayscaleScalar(int precision) { } - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values.Component0, this.MaximumValue); + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); - internal static void ConvertCoreInplace(Span values, float maxValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values.Component0, this.MaximumValue); + + internal static void ConvertCoreInplaceToRgb(Span values, float maxValue) { ref float valuesRef = ref MemoryMarshal.GetReference(values); float scale = 1 / maxValue; @@ -29,6 +32,17 @@ internal static void ConvertCoreInplace(Span values, float maxValue) Unsafe.Add(ref valuesRef, i) *= scale; } } + + internal static void ConvertCoreInplaceFromRgb(Span values, float maxValue) + { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = maxValue; + + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index c484aac28d..ca7781f1be 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -16,7 +16,7 @@ public FromGrayScaleVector(int precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector cBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -31,8 +31,26 @@ protected override void ConvertCoreVectorizedInplace(in ComponentValues values) } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); + + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + var scale = new Vector(this.MaximumValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref cBase, i); + c0 *= scale; + } + } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component0, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs index f017716e3f..8453aa9ac5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -40,6 +40,29 @@ public override void ConvertToRgbInplace(in ComponentValues values) b = Avx.Multiply(b, scale); } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + ref Vector256 rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + // Used for the color conversion + var scale = Vector256.Create(this.MaximumValue); + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 r = ref Unsafe.Add(ref rBase, i); + ref Vector256 g = ref Unsafe.Add(ref gBase, i); + ref Vector256 b = ref Unsafe.Add(ref bBase, i); + r = Avx.Multiply(r, scale); + g = Avx.Multiply(g, scale); + b = Avx.Multiply(b, scale); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs index 24c59206d8..c4c8523bfc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -12,14 +12,24 @@ public FromRgbScalar(int precision) { } - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue); + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values, this.MaximumValue); - internal static void ConvertCoreInplace(ComponentValues values, float maxValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values, this.MaximumValue); + + internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue) + { + FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); + } + + internal static void ConvertCoreInplaceFromRgb(ComponentValues values, float maxValue) { - FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); - FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); - FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component0, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component1, maxValue); + FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component2, maxValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs index ff3a2bee19..ac789ec8c4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -16,7 +16,7 @@ public FromRgbVector(int precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -39,8 +39,34 @@ protected override void ConvertCoreVectorizedInplace(in ComponentValues values) } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromRgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); + + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var scale = new Vector(this.MaximumValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector r = ref Unsafe.Add(ref rBase, i); + ref Vector g = ref Unsafe.Add(ref gBase, i); + ref Vector b = ref Unsafe.Add(ref bBase, i); + r *= scale; + g *= scale; + b *= scale; + } + } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromRgbScalar.ConvertCoreInplaceFromRgb(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 53fa176480..653a2f482b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -71,7 +71,47 @@ public override void ConvertToRgbInplace(in ComponentValues values) public override void ConvertFromRgbInplace(in ComponentValues values) { - return; + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(this.HalfValue); + var scale = Vector256.Create(this.MaximumValue); + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + + Vector256 r = Avx.Multiply(c0, scale); + Vector256 g = Avx.Multiply(c1, scale); + Vector256 b = Avx.Multiply(c2, scale); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + c0 = y; + c1 = cb; + c2 = cr; + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index 4b6d88f725..4c426d856b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -21,9 +21,12 @@ public FromYCbCrScalar(int precision) } public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + => ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertCoreInplaceFromRgb(values, this.MaximumValue, this.HalfValue); + + public static void ConvertCoreInplaceToRgb(in ComponentValues values, float maxValue, float halfValue) { Span c0 = values.Component0; Span c1 = values.Component1; @@ -45,6 +48,29 @@ internal static void ConvertCoreInplace(in ComponentValues values, float maxValu c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; } } + + public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + float scale = maxValue; + + for (int i = 0; i < c0.Length; i++) + { + float r = c0[i] * scale; + float g = c1[i] * scale; + float b = c2[i] * scale; + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + c0[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + c1[i] = 128 - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + c2[i] = 128 + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index 48e311d995..a26e09ceda 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -17,7 +17,7 @@ public FromYCbCrVector(int precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -67,8 +67,56 @@ protected override void ConvertCoreVectorizedInplace(in ComponentValues values) } } - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromYCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); + + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var chromaOffset = new Vector(this.HalfValue); + + var scale = new Vector(this.MaximumValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + + Vector r = c0 * scale; + Vector g = c1 * scale; + Vector b = c2 * scale; + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + c0 = (rYMult * r) + (gYMult * g) + (bYMult * b); + c1 = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + c2 = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + } + } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromYCbCrScalar.ConvertCoreInplaceFromRgb(values, this.MaximumValue, this.HalfValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs index 1f18d5324d..bd672d6b84 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -19,6 +19,8 @@ public FromYccKAvx(int precision) { } + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new System.NotImplementedException(); + public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs index d6387ae714..345da654e5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -38,6 +38,8 @@ internal static void ConvertCoreInplace(in ComponentValues values, float maxValu c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) => throw new 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 66c79ae7c8..e90ff9438c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -16,7 +16,7 @@ public FromYccKVector(int precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) { ref Vector c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -68,8 +68,10 @@ protected override void ConvertCoreVectorizedInplace(in ComponentValues values) } } - protected override void ConvertCoreInplace(in ComponentValues values) => + 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/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 464358d0e8..18dc0fd15c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -79,7 +79,7 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); - public virtual void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException("This is a test exception"); + public abstract void ConvertFromRgbInplace(in ComponentValues values); /// /// Returns the s for all supported colorspaces and precisions. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index ca482d78df..e07b87db45 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -38,7 +38,7 @@ public override void ConvertToRgbInplace(in ComponentValues values) // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide // Thus there's no need to check whether simdCount is greater than zero int simdCount = length - remainder; - this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); + this.ConvertCoreVectorizedInplaceToRgb(values.Slice(0, simdCount)); // Jpeg images width is always divisible by 8 without a remainder // so it's safe to say SSE/AVX implementations would never have @@ -47,13 +47,40 @@ public override void ConvertToRgbInplace(in ComponentValues values) // remainder pixels if (remainder > 0) { - this.ConvertCoreInplace(values.Slice(simdCount, remainder)); + this.ConvertCoreInplaceToRgb(values.Slice(simdCount, remainder)); } } - protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); + public override void ConvertFromRgbInplace(in ComponentValues values) + { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); + + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); + + // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide + // Thus there's no need to check whether simdCount is greater than zero + int simdCount = length - remainder; + this.ConvertCoreVectorizedInplaceFromRgb(values.Slice(0, simdCount)); + + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) + { + this.ConvertCoreInplaceFromRgb(values.Slice(simdCount, remainder)); + } + } + + protected abstract void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values); + + protected abstract void ConvertCoreInplaceToRgb(in ComponentValues values); + + protected abstract void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values); - protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); + protected abstract void ConvertCoreInplaceFromRgb(in ComponentValues values); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 03f503ddee..8d404a3fd2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -128,13 +128,12 @@ private bool IsStreamFlushNeeded get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } - public void Encode(Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + public void EncodeScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP this.huffmanTables = HuffmanLut.TheHuffmanLut; - var frame = new JpegFrame(configuration.MemoryAllocator, image, componentCount: 3); frame.Init(1, 1); frame.AllocateComponents(fullScan: false); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index f50f9e8891..1c071c3352 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -62,12 +62,12 @@ public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int /// /// Gets or sets the index for the DC Huffman table. /// - public int DcTableId { get; set; } = 0; // TODO: DEBUG!!! + public int DcTableId { get; set; } /// /// Gets or sets the index for the AC Huffman table. /// - public int AcTableId { get; set; } = 1; // TODO: DEBUG!!! + public int AcTableId { get; set; } /// public void Dispose() diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index 8c6008620b..ad7bb5f0ff 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -12,8 +12,6 @@ internal class JpegComponentPostProcessor : IDisposable private readonly JpegComponent component; - private readonly int blockRowsPerStep; - private Block8x8F quantTable; public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent component, Size postProcessorBufferSize, Block8x8F quantTable) @@ -27,9 +25,8 @@ public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( postProcessorBufferSize.Width, postProcessorBufferSize.Height, - this.blockAreaSize.Height); - - this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; + this.blockAreaSize.Height, + AllocationOptions.Clean); } /// @@ -42,21 +39,20 @@ public void CopyColorBufferToBlocks(int spectralStep) Buffer2D spectralBuffer = this.component.SpectralBlocks; // should be this.frame.MaxColorChannelValue - // but currently 12-bit jpegs are not supported - float maximumValue = 255f; + // but 12-bit jpegs are not supported currently float normalizationValue = -128f; int blocksRowsPerStep = this.component.SamplingFactors.Height; int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = spectralStep * this.blockRowsPerStep; + int yBlockStart = spectralStep * blocksRowsPerStep; Size subSamplingDivisors = this.component.SubSamplingDivisors; Block8x8F workspaceBlock = default; - for (int y = 0; y < this.blockRowsPerStep; y++) + for (int y = 0; y < blocksRowsPerStep; y++) { int yBuffer = y * this.blockAreaSize.Height; @@ -73,9 +69,6 @@ public void CopyColorBufferToBlocks(int spectralStep) subSamplingDivisors.Width, subSamplingDivisors.Height); - // multiply by maximum (for debug only, should be done in color converter) - workspaceBlock.MultiplyInPlace(maximumValue); - // level shift via -128f workspaceBlock.AddInPlace(normalizationValue); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 3343ae02ba..31e59acf8a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -11,55 +11,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal sealed class JpegFrame : IDisposable { - public JpegFrame(MemoryAllocator allocator, Image image, byte componentCount) + public JpegFrame(MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) { + this.ColorSpace = colorSpace; + this.PixelWidth = image.Width; this.PixelHeight = image.Height; - if (componentCount != 3) - { - throw new ArgumentException("This is YCbCr debug path only."); - } - + // int componentCount = 3; this.Components = new JpegComponent[] { - new JpegComponent(allocator, 1, 1, 0), - new JpegComponent(allocator, 1, 1, 0), - new JpegComponent(allocator, 1, 1, 0), + // 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 } }; } - /// - /// Gets the number of pixel per row. - /// + public Decoder.JpegColorSpace ColorSpace { get; } + public int PixelHeight { get; private set; } - /// - /// Gets the number of pixels per line. - /// public int PixelWidth { get; private set; } - /// - /// Gets the number of components within a frame. - /// public int ComponentCount => this.Components.Length; - /// - /// Gets the frame component collection. - /// public JpegComponent[] Components { get; } - /// - /// Gets or sets the number of MCU's per line. - /// public int McusPerLine { get; set; } - /// - /// Gets or sets the number of MCU's per column. - /// public int McusPerColumn { get; set; } - /// public void Dispose() { for (int i = 0; i < this.Components.Length; i++) @@ -68,11 +58,6 @@ public void Dispose() } } - /// - /// Allocates the frame component blocks. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. public void Init(int maxSubFactorH, int maxSubFactorV) { this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index e3f659b721..7164a49f80 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -25,6 +25,8 @@ internal class SpectralConverter : SpectralConverter private Buffer2D pixelBuffer; + private int alignedPixelWidth; + private Decoder.ColorConverters.JpegColorConverterBase colorConverter; public SpectralConverter(Configuration configuration) => @@ -47,7 +49,8 @@ public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] de // component processors from spectral to Rgba32 const int blockPixelWidth = 8; - var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); + this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; + var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { @@ -56,10 +59,10 @@ public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] de } // single 'stride' rgba32 buffer for conversion between spectral and TPixel - this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); + this.rgbBuffer = allocator.Allocate(this.alignedPixelWidth * 3); // color converter from Rgb24 to YCbCr - this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: Decoder.JpegColorSpace.YCbCr, precision: 8); + this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); } public void ConvertStrideBaseline() @@ -80,18 +83,15 @@ private void ConvertStride(int spectralStep) int width = this.pixelBuffer.Width; - // unpack TPixel to r/g/b planes - Span r = this.rgbBuffer.Slice(0, width); - Span g = this.rgbBuffer.Slice(width, width); - Span b = this.rgbBuffer.Slice(width * 2, width); + Span r = this.rgbBuffer.Slice(0, this.alignedPixelWidth); + Span g = this.rgbBuffer.Slice(this.alignedPixelWidth, this.alignedPixelWidth); + Span b = this.rgbBuffer.Slice(this.alignedPixelWidth * 2, this.alignedPixelWidth); for (int yy = this.pixelRowCounter; yy < maxY; yy++) { int y = yy - this.pixelRowCounter; - // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. - // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, - // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. + // unpack TPixel to r/g/b planes Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, r, g, b, sourceRow); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 910a723fb7..79106856d7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -131,13 +131,35 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); + var frame = new Components.Encoder.JpegFrame(Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.colorType.Value)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken); + new HuffmanScanEncoder(3, stream).EncodeScan(frame, image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); stream.Flush(); + + static JpegColorSpace GetTargetColorSpace(JpegColorType colorType) + { + switch (colorType) + { + case JpegColorType.YCbCrRatio444: + case JpegColorType.YCbCrRatio422: + case JpegColorType.YCbCrRatio420: + case JpegColorType.YCbCrRatio411: + case JpegColorType.YCbCrRatio410: + return JpegColorSpace.YCbCr; + case JpegColorType.Rgb: + return JpegColorSpace.RGB; + case JpegColorType.Cmyk: + return JpegColorSpace.Cmyk; + case JpegColorType.Luminance: + return JpegColorSpace.Grayscale; + default: + throw new NotImplementedException($"Unknown output color space: {colorType}"); + } + } } /// From cad0ed017b40420b08c6a0d0dde3dd026345b4a3 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 8 May 2022 17:14:39 +0300 Subject: [PATCH 05/39] 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 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val 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 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val 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 @@ private bool IsStreamFlushNeeded 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 @@ ref Unsafe.Add(ref blockRef, blockCol), 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 @@ internal class JpegComponent : IDisposable { 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 @@ public JpegFrame(MemoryAllocator allocator, Image image, Decoder.JpegColorSpace 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 @@ public enum JpegColorType : byte /// /// 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 @@ public enum JpegColorType : byte /// /// 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 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions /// public JpegColorType? ColorType { get; set; } + public JpegFrameConfig JpegFrameConfig { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -28,7 +31,7 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions 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 @@ public void Encode(Image image, Stream stream) 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 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// private JpegColorType? colorType; + private JpegFrameConfig frameConfig; + /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -53,14 +55,12 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// 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 @@ public void Encode(Image image, Stream stream, CancellationToken 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 @@ public void Encode(Image image, Stream stream, CancellationToken // 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(); From fdb8b97f6f0f87964a9d08a4ba19e5038f01385f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 8 May 2022 19:18:50 +0300 Subject: [PATCH 06/39] Implemented quantization and coding tables selectors --- .../Jpeg/Components/Encoder/HuffmanLut.cs | 9 ++ .../Components/Encoder/HuffmanScanEncoder.cs | 6 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 4 +- .../Formats/Jpeg/JpegEncoderCore.cs | 135 ++++-------------- 4 files changed, 42 insertions(+), 112 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index 44b39dfd71..6e17762c72 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -31,6 +31,9 @@ internal readonly struct HuffmanLut /// public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; + public static readonly HuffmanLut[] DcHuffmanLut = new HuffmanLut[2]; + public static readonly HuffmanLut[] AcHuffmanLut = new HuffmanLut[2]; + /// /// Initializes static members of the struct. /// @@ -41,6 +44,12 @@ static HuffmanLut() { TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); } + + // TODO: REWRITE THIS + DcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[0]); + DcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[2]); + AcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[1]); + AcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[3]); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index bc765742e9..9803bcf2a4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -132,8 +132,6 @@ public void EncodeInterleavedScan(JpegFrame frame, Image image, where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP - this.huffmanTables = HuffmanLut.TheHuffmanLut; - frame.Init(1, 1); frame.AllocateComponents(fullScan: false); @@ -161,8 +159,8 @@ public void EncodeInterleavedScan(JpegFrame frame, Image image, { JpegComponent component = frame.Components[k]; - ref HuffmanLut dcHuffmanTable = ref this.huffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.huffmanTables[component.AcTableId]; + ref HuffmanLut dcHuffmanTable = ref HuffmanLut.DcHuffmanLut[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref HuffmanLut.AcHuffmanLut[component.AcTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 7323a30baf..87fd218eb8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -89,7 +89,7 @@ static int GetComponentCountFromColorType(JpegColorType colorType) public JpegComponentConfig[] Components { get; } - public JpegFrameConfig PopulateComponent(int index, int id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) { this.Components[index] = new JpegComponentConfig { @@ -107,7 +107,7 @@ public JpegFrameConfig PopulateComponent(int index, int id, int hsf, int vsf, in public class JpegComponentConfig { - public int Id { get; set; } + public byte Id { get; set; } public int HorizontalSampleFactor { get; set; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 065d9c6019..082ab83ccf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -89,7 +89,6 @@ public void Encode(Image image, Stream stream, CancellationToken // Compute number of components based on color type in options. int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; - ReadOnlySpan componentIds = this.GetComponentIds(); // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that // Initialize the quantization tables. @@ -117,13 +116,13 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds); + this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig.Components); // Write the Huffman tables. this.WriteDefineHuffmanTables(componentCount); // Write the scan header. - this.WriteStartOfScan(componentCount, componentIds); + this.WriteStartOfScan(componentCount, this.frameConfig.Components); var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; @@ -200,15 +199,6 @@ private static bool IsSupportedColorType(JpegColorType? colorType) || colorType == JpegColorType.Luminance || colorType == JpegColorType.Rgb; - /// - /// Gets the component ids. - /// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3. - /// - /// The component Ids. - private ReadOnlySpan GetComponentIds() => this.colorType == JpegColorType.Rgb - ? new ReadOnlySpan(new byte[] { 82, 71, 66 }) - : new ReadOnlySpan(new byte[] { 1, 2, 3 }); - /// /// Writes data to "Define Quantization Tables" block for QuantIndex. /// @@ -646,91 +636,37 @@ private void WriteProfiles(ImageMetadata metadata) /// The height of the image. /// The number of components in a pixel. /// The component Id's. - private void WriteStartOfFrame(int width, int height, int componentCount, ReadOnlySpan componentIds) + private void WriteStartOfFrame(int width, int height, JpegComponentConfig[] components) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - // "default" to 4:2:0 - ReadOnlySpan subsamples = new byte[] - { - 0x22, - 0x11, - 0x11 - }; - - ReadOnlySpan chroma = new byte[] - { - 0x00, - 0x01, - 0x01 - }; - - if (this.colorType == JpegColorType.Luminance) - { - subsamples = new byte[] - { - 0x11, - 0x00, - 0x00 - }; - } - else - { - switch (this.colorType) - { - case JpegColorType.YCbCrRatio444: - case JpegColorType.Rgb: - subsamples = new byte[] - { - 0x11, - 0x11, - 0x11 - }; - - if (this.colorType == JpegColorType.Rgb) - { - chroma = new byte[] - { - 0x00, - 0x00, - 0x00 - }; - } - - break; - case JpegColorType.YCbCrRatio420: - subsamples = new byte[] - { - 0x22, - 0x11, - 0x11 - }; - break; - } - } - // Length (high byte, low byte), 8 + components * 3. - int markerlen = 8 + (3 * componentCount); + int markerlen = 8 + (3 * components.Length); this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported this.buffer[1] = (byte)(height >> 8); this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported this.buffer[3] = (byte)(width >> 8); this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - this.buffer[5] = (byte)componentCount; + this.buffer[5] = (byte)components.Length; - for (int i = 0; i < componentCount; i++) + // Components data + for (int i = 0; i < components.Length; i++) { int i3 = 3 * i; - - // Component ID. Span bufferSpan = this.buffer.AsSpan(i3 + 6, 3); - bufferSpan[2] = chroma[i]; - bufferSpan[1] = subsamples[i]; - bufferSpan[0] = componentIds[i]; + + // Quantization table selector + bufferSpan[2] = (byte)components[i].QuantizatioTableIndex; + + // Sampling factors + // 4 bits + int samplingFactors = components[i].HorizontalSampleFactor | (components[i].VerticalSampleFactor << 4); + bufferSpan[1] = (byte)samplingFactors; + + // Id + bufferSpan[0] = components[i].Id; } - this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); + this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9); } /// @@ -738,28 +674,8 @@ private void WriteStartOfFrame(int width, int height, int componentCount, ReadOn /// /// The number of components in a pixel. /// The componentId's. - private void WriteStartOfScan(int componentCount, ReadOnlySpan componentIds) + private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - ReadOnlySpan huffmanId = new byte[] - { - 0x00, - 0x11, - 0x11 - }; - - // Use the same DC/AC tables for all channels for RGB. - if (this.colorType == JpegColorType.Rgb) - { - huffmanId = new byte[] - { - 0x00, - 0x00, - 0x00 - }; - } - // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: // - the marker length "\x00\x0c", // - the number of components "\x03", @@ -777,11 +693,18 @@ private void WriteStartOfScan(int componentCount, ReadOnlySpan componentId this.buffer[2] = 0x00; this.buffer[3] = (byte)sosSize; this.buffer[4] = (byte)componentCount; // Number of components in a scan + + // Components data for (int i = 0; i < componentCount; i++) { int i2 = 2 * i; - this.buffer[i2 + 5] = componentIds[i]; // Component Id - this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table + + // Id + this.buffer[i2 + 5] = components[i].Id; + + // Table selectors + int tableSelectors = (components[i].dcTableSelector << 4) | (components[i].acTableSelector); + this.buffer[i2 + 6] = (byte)tableSelectors; } this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection. From ccf664c150d333cf6cfd67637b4ffacd36fb99e1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 8 May 2022 23:34:27 +0300 Subject: [PATCH 07/39] Implemented cmyk encoding --- .../JpegColorConverter.FromCmykAvx.cs | 43 ++++++++++++++++- .../JpegColorConverter.FromCmykScalar.cs | 41 ++++++++++++++-- .../JpegColorConverter.FromCmykVector.cs | 47 +++++++++++++++++-- 3 files changed, 122 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 6429b0ea85..02a0c42bbf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -18,8 +18,6 @@ public FromCmykAvx(int precision) { } - public override void ConvertFromRgbInplace(in ComponentValues values) => throw new System.NotImplementedException(); - public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -48,6 +46,47 @@ public override void ConvertToRgbInplace(in ComponentValues values) y = Avx.Multiply(y, k); } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 c3Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + // Used for the color conversion + var scale = Vector256.Create(this.MaximumValue); + var one = Vector256.Create(1f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + ref Vector256 c3 = ref Unsafe.Add(ref c3Base, i); + + Vector256 ctmp = Avx.Subtract(one, c0); + Vector256 mtmp = Avx.Subtract(one, c1); + Vector256 ytmp = Avx.Subtract(one, c2); + Vector256 ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp)); + + Vector256 kMask = Avx.CompareNotEqual(ktmp, one); + + ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(one, ktmp)), kMask); + mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(one, ktmp)), kMask); + ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(one, ktmp)), kMask); + + c0 = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); + c1 = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); + c2 = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); + c3 = Avx.Subtract(scale, Avx.Multiply(ktmp, scale)); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index 17459a5851..e951b57219 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -15,9 +15,12 @@ public FromCmykScalar(int precision) } public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue); + ConvertToRgbInplace(values, this.MaximumValue); - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => ConvertFromRgbInplace(values, this.MaximumValue); + + public static void ConvertToRgbInplace(in ComponentValues values, float maxValue) { Span c0 = values.Component0; Span c1 = values.Component1; @@ -39,7 +42,39 @@ internal static void ConvertCoreInplace(in ComponentValues values, float maxValu } } - public override void ConvertFromRgbInplace(in ComponentValues values) => throw new NotImplementedException(); + public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + for (int i = 0; i < c0.Length; i++) + { + float ctmp = 1f - c0[i]; + float mtmp = 1f - c1[i]; + float ytmp = 1f - c2[i]; + float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); + + if (1f - ktmp <= float.Epsilon) + { + ctmp = 0f; + mtmp = 0f; + ytmp = 0f; + } + else + { + ctmp = (ctmp - ktmp) / (1f - ktmp); + mtmp = (mtmp - ktmp) / (1f - ktmp); + ytmp = (ytmp - ktmp) / (1f - ktmp); + } + + c0[i] = maxValue - (ctmp * maxValue); + c1[i] = maxValue - (mtmp * maxValue); + c2[i] = maxValue - (ytmp * maxValue); + c3[i] = maxValue - (ktmp * maxValue); + } + } } } } 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 00ec1f8689..d6c23f0e0b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -44,12 +44,51 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } } - protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => - FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); + protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + => FromCmykScalar.ConvertToRgbInplace(values, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector c3Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + // Used for the color conversion + var scale = new Vector(this.MaximumValue); + var one = new Vector(1f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + ref Vector c3 = ref Unsafe.Add(ref c3Base, i); + + Vector ctmp = one - c0; + Vector mtmp = one - c1; + Vector ytmp = one - c2; + Vector ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp)); + + var kMask = Vector.Equals(ktmp, Vector.One); + ctmp = Vector.AndNot((ctmp - ktmp) / (one - ktmp), kMask.As()); + mtmp = Vector.AndNot((mtmp - ktmp) / (one - ktmp), kMask.As()); + ytmp = Vector.AndNot((ytmp - ktmp) / (one - ktmp), kMask.As()); + + c0 = scale - (ctmp * scale); + c1 = scale - (mtmp * scale); + c2 = scale - (ytmp * scale); + c3 = scale - (ktmp * scale); + } + } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => FromCmykScalar.ConvertFromRgbInplace(values, this.MaximumValue); } } } From dfb2053847a08125da4cdb5e8242dc6653852317 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 8 May 2022 23:37:44 +0300 Subject: [PATCH 08/39] Removed precision parameter, removed YccK color space --- .../JpegColorConverter.FromYCbCrScalar.cs | 4 ++-- .../JpegColorConverter.FromYccKScalar.cs | 9 +++++---- .../JpegColorConverter.FromYccKVector.cs | 2 +- .../Formats/Jpeg/Components/Encoder/JpegFrame.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegColorType.cs | 5 ----- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 5 +---- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 15 +++++++-------- 7 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index 4c426d856b..41f059be23 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -67,8 +67,8 @@ public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float ma // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) c0[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - c1[i] = 128 - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - c2[i] = 128 + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + c1[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + c2[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs index 345da654e5..f270ac6894 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -15,9 +15,12 @@ public FromYccKScalar(int precision) } public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) + public override void ConvertFromRgbInplace(in ComponentValues values) + => throw new NotImplementedException(); + + public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) { Span c0 = values.Component0; Span c1 = values.Component1; @@ -38,8 +41,6 @@ internal static void ConvertCoreInplace(in ComponentValues values, float maxValu c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; } } - - public override void ConvertFromRgbInplace(in ComponentValues values) => throw new 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 796685278f..0e63ab942b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -69,7 +69,7 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => - FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + FromYccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 18a543dc33..fe6a6ddb01 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(Jpeg.JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) + public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) { this.ColorSpace = colorSpace; diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs index ff4fa013df..b3a792aff3 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs @@ -58,10 +58,5 @@ public enum JpegColorType : byte /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. /// Cmyk = 7, - - /// - /// YCCK colorspace. - /// - YccK = 8, } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 87fd218eb8..632e16e702 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -53,10 +53,9 @@ public Task EncodeAsync(Image image, Stream stream, Cancellation public class JpegFrameConfig { - public JpegFrameConfig(JpegColorType colorType, int precision) + public JpegFrameConfig(JpegColorType colorType) { this.ColorType = colorType; - this.Precision = precision; int componentCount = GetComponentCountFromColorType(colorType); this.Components = new JpegComponentConfig[componentCount]; @@ -85,8 +84,6 @@ static int GetComponentCountFromColorType(JpegColorType colorType) public JpegColorType ColorType { get; } - public int Precision { get; } - public JpegComponentConfig[] Components { get; } public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 082ab83ccf..256967e6e6 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -87,12 +87,9 @@ public void Encode(Image image, Stream stream, CancellationToken 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; - // 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); + this.InitQuantizationTables(this.frameConfig.Components.Length, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); // Write the Start Of Image marker. this.WriteStartOfImage(); @@ -116,13 +113,13 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig.Components); + this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); // Write the Huffman tables. - this.WriteDefineHuffmanTables(componentCount); + this.WriteDefineHuffmanTables(this.frameConfig.Components.Length); // Write the scan header. - this.WriteStartOfScan(componentCount, this.frameConfig.Components); + this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; @@ -636,8 +633,10 @@ private void WriteProfiles(ImageMetadata metadata) /// The height of the image. /// The number of components in a pixel. /// The component Id's. - private void WriteStartOfFrame(int width, int height, JpegComponentConfig[] components) + private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) { + JpegComponentConfig[] components = frame.Components; + // Length (high byte, low byte), 8 + components * 3. int markerlen = 8 + (3 * components.Length); this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); From 74c9bb6b6b0e71a03549c89186870b8053fb62dc Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 9 May 2022 00:06:22 +0300 Subject: [PATCH 09/39] Moved color convertes to 'Components' folder --- .../JpegColorConverter.FromCmykAvx.cs | 2 +- .../JpegColorConverter.FromCmykScalar.cs | 2 +- .../JpegColorConverter.FromCmykVector.cs | 2 +- .../JpegColorConverter.FromGrayScaleAvx.cs | 2 +- .../JpegColorConverter.FromGrayScaleScalar.cs | 2 +- .../JpegColorConverter.FromGrayScaleVector.cs | 2 +- .../JpegColorConverter.FromRgbAvx.cs | 2 +- .../JpegColorConverter.FromRgbScalar.cs | 2 +- .../JpegColorConverter.FromRgbVector.cs | 2 +- .../JpegColorConverter.FromYCbCrAvx.cs | 2 +- .../JpegColorConverter.FromYCbCrScalar.cs | 2 +- .../JpegColorConverter.FromYCbCrVector.cs | 2 +- .../JpegColorConverter.FromYccKAvx.cs | 2 +- .../JpegColorConverter.FromYccKScalar.cs | 2 +- .../JpegColorConverter.FromYccKVector.cs | 2 +- .../ColorConverters/JpegColorConverterAvx.cs | 2 +- .../ColorConverters/JpegColorConverterBase.cs | 14 +--- .../JpegColorConverterScalar.cs | 2 +- .../JpegColorConverterVector.cs | 2 +- .../Components/Decoder/SpectralConverter.cs | 2 - .../Decoder/SpectralConverter{TPixel}.cs | 1 - .../Jpeg/Components/Encoder/JpegFrame.cs | 4 +- .../Encoder/SpectralConverter{TPixel}.cs | 6 +- .../{Decoder => }/JpegColorSpace.cs | 4 +- .../Formats/Jpeg/IJpegEncoderOptions.cs | 2 +- .../Formats/Jpeg/JpegDecoderCore.cs | 22 ++--- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 25 +++--- .../Formats/Jpeg/JpegEncoderCore.cs | 42 +++++----- .../{JpegColorType.cs => JpegEncodingMode.cs} | 2 +- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 2 +- .../Compressors/TiffJpegCompressor.cs | 2 +- .../GrayJpegSpectralConverter.cs | 2 +- .../Decompressors/RgbJpegSpectralConverter.cs | 2 +- .../Codecs/Jpeg/EncodeJpegComparison.cs | 2 +- .../Codecs/Jpeg/EncodeJpegFeatures.cs | 6 +- .../Formats/Jpg/JpegColorConverterTests.cs | 8 +- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 30 +++---- .../Formats/Jpg/JpegEncoderTests.cs | 82 +++++++++---------- .../Formats/Jpg/JpegMetadataTests.cs | 4 +- .../JpegProfilingBenchmarks.cs | 10 +-- 40 files changed, 146 insertions(+), 164 deletions(-) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromCmykAvx.cs (98%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromCmykScalar.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromCmykVector.cs (98%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs (96%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs (95%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs (96%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromRgbAvx.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromRgbScalar.cs (94%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromRgbVector.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs (98%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromYCbCrVector.cs (98%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromYccKAvx.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromYccKScalar.cs (95%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverter.FromYccKVector.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverterAvx.cs (93%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverterBase.cs (96%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverterScalar.cs (89%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/ColorConverters/JpegColorConverterVector.cs (97%) rename src/ImageSharp/Formats/Jpeg/Components/{Decoder => }/JpegColorSpace.cs (89%) rename src/ImageSharp/Formats/Jpeg/{JpegColorType.cs => JpegEncodingMode.cs} (98%) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 02a0c42bbf..038fc8f9dd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -7,7 +7,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs index e951b57219..33fe742471 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs index d6c23f0e0b..ff2bd2323f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs similarity index 96% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index bcb366298d..778424a15d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -7,7 +7,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs similarity index 95% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs index a0db2e801c..027d762ea9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs similarity index 96% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index ca7781f1be..e1d9178dd0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 8453aa9ac5..987fe01e5f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -7,7 +7,7 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs similarity index 94% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs index c4c8523bfc..e92e5e34d8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs index ac789ec8c4..2150c3459b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 653a2f482b..a89228b348 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -9,7 +9,7 @@ using static SixLabors.ImageSharp.SimdUtils; // ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index 41f059be23..d01f4c5f91 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index a26e09ceda..9fd372a312 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; // ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs index bd672d6b84..d09e3f7473 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -8,7 +8,7 @@ using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs similarity index 95% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs index f270ac6894..755839e244 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs index 0e63ab942b..c4fd7ca326 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs index 81c7c0764d..90e5df88f8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs @@ -3,7 +3,7 @@ #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs similarity index 96% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 18dc0fd15c..df1f8db8fc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -6,7 +6,7 @@ using System.Linq; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// /// Encapsulates the conversion of color channels from jpeg image to RGB channels. @@ -110,9 +110,7 @@ private static JpegColorConverterBase[] CreateConverters() /// private static IEnumerable GetYCbCrConverters(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS yield return new FromYCbCrAvx(precision); -#endif yield return new FromYCbCrVector(precision); yield return new FromYCbCrScalar(precision); } @@ -122,9 +120,7 @@ private static IEnumerable GetYCbCrConverters(int precis /// private static IEnumerable GetYccKConverters(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS yield return new FromYccKAvx(precision); -#endif yield return new FromYccKVector(precision); yield return new FromYccKScalar(precision); } @@ -134,9 +130,7 @@ private static IEnumerable GetYccKConverters(int precisi /// private static IEnumerable GetCmykConverters(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS yield return new FromCmykAvx(precision); -#endif yield return new FromCmykVector(precision); yield return new FromCmykScalar(precision); } @@ -146,9 +140,7 @@ private static IEnumerable GetCmykConverters(int precisi /// private static IEnumerable GetGrayScaleConverters(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS yield return new FromGrayscaleAvx(precision); -#endif yield return new FromGrayScaleVector(precision); yield return new FromGrayscaleScalar(precision); } @@ -158,9 +150,7 @@ private static IEnumerable GetGrayScaleConverters(int pr /// private static IEnumerable GetRgbConverters(int precision) { -#if SUPPORTS_RUNTIME_INTRINSICS yield return new FromRgbAvx(precision); -#endif yield return new FromRgbVector(precision); yield return new FromRgbScalar(precision); } @@ -221,7 +211,7 @@ public ComponentValues(IReadOnlyList> componentBuffers, int row) /// /// List of component color processors. /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) + public ComponentValues(IReadOnlyList processors, int row) { DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs index ff88ab969f..44046f8e85 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs index e07b87db45..6b7621d8fb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 9901639a79..34900b1c37 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; - namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 532892e060..8177e6dde8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.Linq; using System.Threading; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index fe6a6ddb01..4213e7e434 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(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) + public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, JpegColorSpace colorSpace) { this.ColorSpace = colorSpace; @@ -32,7 +32,7 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i } } - public Decoder.JpegColorSpace ColorSpace { get; } + public JpegColorSpace ColorSpace { get; } public int PixelHeight { get; private set; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 7164a49f80..2877d8f65e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -27,7 +27,7 @@ internal class SpectralConverter : SpectralConverter private int alignedPixelWidth; - private Decoder.ColorConverters.JpegColorConverterBase colorConverter; + private JpegColorConverterBase colorConverter; public SpectralConverter(Configuration configuration) => this.configuration = configuration; @@ -62,7 +62,7 @@ public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] de this.rgbBuffer = allocator.Allocate(this.alignedPixelWidth * 3); // color converter from Rgb24 to YCbCr - this.colorConverter = Decoder.ColorConverters.JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); + this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); } public void ConvertStrideBaseline() @@ -95,7 +95,7 @@ private void ConvertStride(int spectralStep) Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, r, g, b, sourceRow); - var values = new Decoder.ColorConverters.JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); SimdUtils.ByteToNormalizedFloat(r, values.Component0); SimdUtils.ByteToNormalizedFloat(g, values.Component1); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs rename to src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs index 7ef2809323..c806855391 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs @@ -1,15 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components { /// /// Identifies the colorspace of a Jpeg image. /// internal enum JpegColorSpace { - Undefined = 0, - /// /// Color space with 1 component. /// diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index 70cfd18e94..e8daeaf738 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -18,6 +18,6 @@ internal interface IJpegEncoderOptions /// /// Gets the color type, that will be used to encode the image. /// - JpegColorType? ColorType { get; } + JpegEncodingMode? ColorType { get; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a07db1c952..b933ff6fed 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -545,57 +545,57 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount) /// Returns the jpeg color type based on the colorspace and subsampling used. /// /// Jpeg color type. - private JpegColorType DeduceJpegColorType() + private JpegEncodingMode DeduceJpegColorType() { switch (this.ColorSpace) { case JpegColorSpace.Grayscale: - return JpegColorType.Luminance; + return JpegEncodingMode.Luminance; case JpegColorSpace.RGB: - return JpegColorType.Rgb; + return JpegEncodingMode.Rgb; case JpegColorSpace.YCbCr: if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio444; + return JpegEncodingMode.YCbCrRatio444; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio420; + return JpegEncodingMode.YCbCrRatio420; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2) { - return JpegColorType.YCbCrRatio422; + return JpegEncodingMode.YCbCrRatio422; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio411; + return JpegEncodingMode.YCbCrRatio411; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegColorType.YCbCrRatio410; + return JpegEncodingMode.YCbCrRatio410; } else { - return JpegColorType.YCbCrRatio420; + return JpegEncodingMode.YCbCrRatio420; } case JpegColorSpace.Cmyk: - return JpegColorType.Cmyk; + return JpegEncodingMode.Cmyk; default: - return JpegColorType.YCbCrRatio420; + return JpegEncodingMode.YCbCrRatio420; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 632e16e702..209be9d5b1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -18,7 +18,7 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions public int? Quality { get; set; } /// - public JpegColorType? ColorType { get; set; } + public JpegEncodingMode? ColorType { get; set; } public JpegFrameConfig JpegFrameConfig { get; set; } @@ -53,28 +53,27 @@ public Task EncodeAsync(Image image, Stream stream, Cancellation public class JpegFrameConfig { - public JpegFrameConfig(JpegColorType colorType) + public JpegFrameConfig(JpegEncodingMode colorType) { this.ColorType = colorType; int componentCount = GetComponentCountFromColorType(colorType); this.Components = new JpegComponentConfig[componentCount]; - static int GetComponentCountFromColorType(JpegColorType colorType) + static int GetComponentCountFromColorType(JpegEncodingMode colorType) { switch (colorType) { - case JpegColorType.Luminance: + case JpegEncodingMode.Luminance: return 1; - case JpegColorType.YCbCrRatio444: - case JpegColorType.YCbCrRatio422: - case JpegColorType.YCbCrRatio420: - case JpegColorType.YCbCrRatio411: - case JpegColorType.YCbCrRatio410: - case JpegColorType.Rgb: + case JpegEncodingMode.YCbCrRatio444: + case JpegEncodingMode.YCbCrRatio422: + case JpegEncodingMode.YCbCrRatio420: + case JpegEncodingMode.YCbCrRatio411: + case JpegEncodingMode.YCbCrRatio410: + case JpegEncodingMode.Rgb: return 3; - case JpegColorType.Cmyk: - case JpegColorType.YccK: + case JpegEncodingMode.Cmyk: return 4; default: throw new ArgumentException($"Unknown jpeg color space: {colorType}"); @@ -82,7 +81,7 @@ static int GetComponentCountFromColorType(JpegColorType colorType) } } - public JpegColorType ColorType { get; } + public JpegEncodingMode ColorType { get; } public JpegComponentConfig[] Components { get; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 256967e6e6..6d720dfd15 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -42,7 +42,7 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// /// Gets or sets the colorspace to use. /// - private JpegColorType? colorType; + private JpegEncodingMode? colorType; private JpegFrameConfig frameConfig; @@ -95,7 +95,7 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteStartOfImage(); // Do not write APP0 marker for RGB colorspace. - if (this.colorType != JpegColorType.Rgb) + if (this.colorType != JpegEncodingMode.Rgb) { this.WriteJfifApplicationHeader(metadata); } @@ -103,7 +103,7 @@ public void Encode(Image image, Stream stream, CancellationToken // Write Exif, XMP, ICC and IPTC profiles this.WriteProfiles(metadata); - if (this.colorType == JpegColorType.Rgb) + if (this.colorType == JpegEncodingMode.Rgb) { // Write App14 marker to indicate RGB color space. this.WriteApp14Marker(); @@ -130,21 +130,21 @@ public void Encode(Image image, Stream stream, CancellationToken stream.Flush(); - static JpegColorSpace GetTargetColorSpace(JpegColorType colorType) + static JpegColorSpace GetTargetColorSpace(JpegEncodingMode colorType) { switch (colorType) { - case JpegColorType.YCbCrRatio444: - case JpegColorType.YCbCrRatio422: - case JpegColorType.YCbCrRatio420: - case JpegColorType.YCbCrRatio411: - case JpegColorType.YCbCrRatio410: + case JpegEncodingMode.YCbCrRatio444: + case JpegEncodingMode.YCbCrRatio422: + case JpegEncodingMode.YCbCrRatio420: + case JpegEncodingMode.YCbCrRatio411: + case JpegEncodingMode.YCbCrRatio410: return JpegColorSpace.YCbCr; - case JpegColorType.Rgb: + case JpegEncodingMode.Rgb: return JpegColorSpace.RGB; - case JpegColorType.Cmyk: + case JpegEncodingMode.Cmyk: return JpegColorSpace.Cmyk; - case JpegColorType.Luminance: + case JpegEncodingMode.Luminance: return JpegColorSpace.Grayscale; default: throw new NotImplementedException($"Unknown output color space: {colorType}"); @@ -158,11 +158,11 @@ static JpegColorSpace GetTargetColorSpace(JpegColorType colorType) /// returns defering the field assignment /// to . /// - private static JpegColorType? GetFallbackColorType(Image image) + private static JpegEncodingMode? GetFallbackColorType(Image image) where TPixel : unmanaged, IPixel { // First inspect the image metadata. - JpegColorType? colorType = null; + JpegEncodingMode? colorType = null; JpegMetadata metadata = image.Metadata.GetJpegMetadata(); if (IsSupportedColorType(metadata.ColorType)) { @@ -179,7 +179,7 @@ static JpegColorSpace GetTargetColorSpace(JpegColorType colorType) // the quality in InitQuantizationTables. if (isGrayscale) { - colorType = JpegColorType.Luminance; + colorType = JpegEncodingMode.Luminance; } return colorType; @@ -190,11 +190,11 @@ static JpegColorSpace GetTargetColorSpace(JpegColorType colorType) /// /// The color type. /// true, if color type is supported. - private static bool IsSupportedColorType(JpegColorType? colorType) - => colorType == JpegColorType.YCbCrRatio444 - || colorType == JpegColorType.YCbCrRatio420 - || colorType == JpegColorType.Luminance - || colorType == JpegColorType.Rgb; + private static bool IsSupportedColorType(JpegEncodingMode? colorType) + => colorType == JpegEncodingMode.YCbCrRatio444 + || colorType == JpegEncodingMode.YCbCrRatio420 + || colorType == JpegEncodingMode.Luminance + || colorType == JpegEncodingMode.Rgb; /// /// Writes data to "Define Quantization Tables" block for QuantIndex. @@ -783,7 +783,7 @@ private void InitQuantizationTables(int componentCount, JpegMetadata metadata, o if (!this.colorType.HasValue) { - this.colorType = chromaQuality >= 91 ? JpegColorType.YCbCrRatio444 : JpegColorType.YCbCrRatio420; + this.colorType = chromaQuality >= 91 ? JpegEncodingMode.YCbCrRatio444 : JpegEncodingMode.YCbCrRatio420; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegEncodingMode.cs similarity index 98% rename from src/ImageSharp/Formats/Jpeg/JpegColorType.cs rename to src/ImageSharp/Formats/Jpeg/JpegEncodingMode.cs index b3a792aff3..d6105da490 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncodingMode.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Provides enumeration of available JPEG color types. /// - public enum JpegColorType : byte + public enum JpegEncodingMode : byte { /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 0a4b970f4f..be42151223 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -108,7 +108,7 @@ public int Quality /// /// Gets or sets the color type. /// - public JpegColorType? ColorType { get; set; } + public JpegEncodingMode? ColorType { get; set; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs index 0ae8fd37bd..f5eb5507df 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs @@ -35,7 +35,7 @@ public override void CompressStrip(Span rows, int height) var image = Image.LoadPixelData(rows, width, height); image.Save(memoryStream, new JpegEncoder() { - ColorType = JpegColorType.Rgb + ColorType = JpegEncodingMode.Rgb }); memoryStream.Position = 0; memoryStream.WriteTo(this.Output); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs index 5b793c35de..708fbc8458 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index a83518064d..f303dc2e2b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs index 2c4686eddf..344a00b030 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs @@ -42,7 +42,7 @@ public void SetupImageSharp() using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.imageImageSharp = Image.Load(imageBinaryStream); - this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 }; + this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingMode.YCbCrRatio420 }; this.destinationStream = new MemoryStream(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs index 83fb556d5b..2c5dd1f8ca 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -22,14 +22,14 @@ public class EncodeJpegFeatures // No metadata private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - public static IEnumerable ColorSpaceValues => - new[] { JpegColorType.Luminance, JpegColorType.Rgb, JpegColorType.YCbCrRatio420, JpegColorType.YCbCrRatio444 }; + public static IEnumerable ColorSpaceValues => + new[] { JpegEncodingMode.Luminance, JpegEncodingMode.Rgb, JpegEncodingMode.YCbCrRatio420, JpegEncodingMode.YCbCrRatio444 }; [Params(75, 90, 100)] public int Quality; [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] - public JpegColorType TargetColorSpace; + public JpegEncodingMode TargetColorSpace; private Image bmpCore; private JpegEncoder encoder; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 91f87610e2..4db718e12a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,11 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -46,7 +44,8 @@ public JpegColorConverterTests(ITestOutputHelper output) [Fact] public void GetConverterThrowsExceptionOnInvalidColorSpace() { - Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.Undefined, 8)); + var invalidColorSpace = (JpegColorSpace)(-1); + Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); } [Fact] @@ -357,7 +356,6 @@ private static void Validate( case JpegColorSpace.YCbCr: ValidateYCbCr(original, result, i); break; - case JpegColorSpace.Undefined: default: Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}."); break; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index d9915f17d6..16bc6d6969 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -139,15 +139,15 @@ public void Decode_VerifyQuality(string imagePath, int quality) } [Theory] - [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)] - [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegColorType.YCbCrRatio422)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)] - public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType) + [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingMode.Luminance)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingMode.YCbCrRatio420)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingMode.YCbCrRatio444)] + [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingMode.Rgb)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingMode.Cmyk)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingMode.YCbCrRatio410)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegEncodingMode.YCbCrRatio422)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingMode.YCbCrRatio411)] + public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingMode expectedColorType) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) @@ -159,12 +159,12 @@ public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType exp } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegColorType.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegColorType.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegColorType.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegColorType.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegColorType.Cmyk)] - public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegColorType expectedColorType) + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingMode.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingMode.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingMode.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingMode.Rgb)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingMode.Cmyk)] + public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegEncodingMode expectedColorType) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(JpegDecoder)) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index d860836e08..db66015683 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -33,18 +33,18 @@ public partial class JpegEncoderTests { TestImages.Jpeg.Progressive.Fb, 75 } }; - public static readonly TheoryData BitsPerPixel_Quality = + public static readonly TheoryData BitsPerPixel_Quality = new() { - { JpegColorType.YCbCrRatio420, 40 }, - { JpegColorType.YCbCrRatio420, 60 }, - { JpegColorType.YCbCrRatio420, 100 }, - { JpegColorType.YCbCrRatio444, 40 }, - { JpegColorType.YCbCrRatio444, 60 }, - { JpegColorType.YCbCrRatio444, 100 }, - { JpegColorType.Rgb, 40 }, - { JpegColorType.Rgb, 60 }, - { JpegColorType.Rgb, 100 } + { JpegEncodingMode.YCbCrRatio420, 40 }, + { JpegEncodingMode.YCbCrRatio420, 60 }, + { JpegEncodingMode.YCbCrRatio420, 100 }, + { JpegEncodingMode.YCbCrRatio444, 40 }, + { JpegEncodingMode.YCbCrRatio444, 60 }, + { JpegEncodingMode.YCbCrRatio444, 100 }, + { JpegEncodingMode.Rgb, 40 }, + { JpegEncodingMode.Rgb, 60 }, + { JpegEncodingMode.Rgb, 100 } }; public static readonly TheoryData Grayscale_Quality = @@ -64,11 +64,11 @@ public partial class JpegEncoderTests }; [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegColorType.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegColorType.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegColorType.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegColorType expectedColorType) + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingMode.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegEncodingMode.Rgb)] + public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingMode expectedColorType) where TPixel : unmanaged, IPixel { // arrange @@ -107,15 +107,15 @@ public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420(memoryStream); JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType); + Assert.Equal(JpegEncodingMode.YCbCrRatio420, meta.ColorType); } [Theory] - [InlineData(JpegColorType.Cmyk)] - [InlineData(JpegColorType.YCbCrRatio410)] - [InlineData(JpegColorType.YCbCrRatio411)] - [InlineData(JpegColorType.YCbCrRatio422)] - public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegColorType colorType) + [InlineData(JpegEncodingMode.Cmyk)] + [InlineData(JpegEncodingMode.YCbCrRatio410)] + [InlineData(JpegEncodingMode.YCbCrRatio411)] + [InlineData(JpegEncodingMode.YCbCrRatio422)] + public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegEncodingMode colorType) { // arrange var jpegEncoder = new JpegEncoder() { ColorType = colorType }; @@ -129,7 +129,7 @@ public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegColorType col memoryStream.Position = 0; using var output = Image.Load(memoryStream); JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType); + Assert.Equal(JpegEncodingMode.YCbCrRatio420, meta.ColorType); } [Theory] @@ -155,7 +155,7 @@ public void Encode_PreservesQuality(string imagePath, int quality) [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegColorType colorType, int quality) + public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegEncodingMode colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] @@ -164,7 +164,7 @@ public void EncodeBaseline_CalliphoraPartial(TestImageProvider p [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegColorType colorType, int quality) + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingMode colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] @@ -177,7 +177,7 @@ public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider(TestImageProvider provider, JpegColorType colorType, int quality) + public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingMode colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); [Theory] @@ -188,27 +188,27 @@ public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestI [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] public void EncodeBaseline_Grayscale(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegColorType.Luminance, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingMode.Luminance, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegColorType colorType, int quality) + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingMode colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegColorType colorType, int quality) + public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingMode colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegColorType.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegColorType.YCbCrRatio420)] - public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegColorType colorType) + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio444)] + [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegEncodingMode.YCbCrRatio420)] + [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegEncodingMode.YCbCrRatio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingMode colorType) where TPixel : unmanaged, IPixel { - ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444 + ImageComparer comparer = colorType == JpegEncodingMode.YCbCrRatio444 ? ImageComparer.TolerantPercentage(0.1f) : ImageComparer.TolerantPercentage(5f); @@ -219,7 +219,7 @@ public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvid /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// - private static ImageComparer GetComparer(int quality, JpegColorType? colorType) + private static ImageComparer GetComparer(int quality, JpegEncodingMode? colorType) { float tolerance = 0.015f; // ~1.5% @@ -227,10 +227,10 @@ private static ImageComparer GetComparer(int quality, JpegColorType? colorType) { tolerance *= 4.5f; } - else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420) + else if (quality < 75 || colorType == JpegEncodingMode.YCbCrRatio420) { tolerance *= 2.0f; - if (colorType == JpegColorType.YCbCrRatio420) + if (colorType == JpegEncodingMode.YCbCrRatio420) { tolerance *= 2.0f; } @@ -241,7 +241,7 @@ private static ImageComparer GetComparer(int quality, JpegColorType? colorType) private static void TestJpegEncoderCore( TestImageProvider provider, - JpegColorType colorType = JpegColorType.YCbCrRatio420, + JpegEncodingMode colorType = JpegEncodingMode.YCbCrRatio420, int quality = 100, ImageComparer comparer = null) where TPixel : unmanaged, IPixel @@ -396,9 +396,9 @@ public void Encode_PreservesIccProfile() } [Theory] - [InlineData(JpegColorType.YCbCrRatio420)] - [InlineData(JpegColorType.YCbCrRatio444)] - public async Task Encode_IsCancellable(JpegColorType colorType) + [InlineData(JpegEncodingMode.YCbCrRatio420)] + [InlineData(JpegEncodingMode.YCbCrRatio444)] + public async Task Encode_IsCancellable(JpegEncodingMode colorType) { var cts = new CancellationTokenSource(); using var pausedStream = new PausedStream(new MemoryStream()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 3f045dd1a0..0c229488aa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -12,11 +12,11 @@ public class JpegMetadataTests [Fact] public void CloneIsDeep() { - var meta = new JpegMetadata { Quality = 50, ColorType = JpegColorType.Luminance }; + var meta = new JpegMetadata { Quality = 50, ColorType = JpegEncodingMode.Luminance }; var clone = (JpegMetadata)meta.DeepClone(); clone.Quality = 99; - clone.ColorType = JpegColorType.YCbCrRatio420; + clone.ColorType = JpegEncodingMode.YCbCrRatio420; Assert.False(meta.Quality.Equals(clone.Quality)); Assert.False(meta.ColorType.Equals(clone.ColorType)); diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index 5eb4aa76d7..ad1402105d 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -91,11 +91,11 @@ public void EncodeJpeg_SingleMidSize() // Benchmark, enable manually! [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(1, 75, JpegColorType.YCbCrRatio420)] - [InlineData(30, 75, JpegColorType.YCbCrRatio420)] - [InlineData(30, 75, JpegColorType.YCbCrRatio444)] - [InlineData(30, 100, JpegColorType.YCbCrRatio444)] - public void EncodeJpeg(int executionCount, int quality, JpegColorType colorType) + [InlineData(1, 75, JpegEncodingMode.YCbCrRatio420)] + [InlineData(30, 75, JpegEncodingMode.YCbCrRatio420)] + [InlineData(30, 75, JpegEncodingMode.YCbCrRatio444)] + [InlineData(30, 100, JpegEncodingMode.YCbCrRatio444)] + public void EncodeJpeg(int executionCount, int quality, JpegEncodingMode colorType) { // do not run this on CI even by accident if (TestEnvironment.RunsOnCI) From 25ab5df08a498f0229ff7c950dc50596ed707b1f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 9 May 2022 02:16:01 +0300 Subject: [PATCH 10/39] Implemented huffman table --- .../Components/Encoder/HuffmanScanEncoder.cs | 25 +++++++-- .../Jpeg/Components/Encoder/HuffmanSpec.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 21 +++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 52 ++++++++----------- 4 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 9803bcf2a4..6b69e43209 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -60,10 +60,14 @@ internal class HuffmanScanEncoder private const int OutputBufferLengthMultiplier = 2; /// - /// Compiled huffman tree to encode given values. + /// The DC Huffman tables. /// - /// Yields codewords by index consisting of [run length | bitsize]. - private HuffmanLut[] huffmanTables; + private readonly HuffmanLut[] dcHuffmanTables; + + /// + /// The AC Huffman tables. + /// + private readonly HuffmanLut[] acHuffmanTables; /// /// Emitted bits 'micro buffer' before being transferred to the . @@ -115,6 +119,9 @@ public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream) this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.target = outputStream; + + this.dcHuffmanTables = new HuffmanLut[4]; + this.acHuffmanTables = new HuffmanLut[4]; } /// @@ -128,6 +135,12 @@ private bool IsStreamFlushNeeded get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } + public void BuildHuffmanTable(JpegHuffmanTableConfig table) + { + HuffmanLut[] tables = table.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[table.DestinationIndex] = new HuffmanLut(table.TableSpec); + } + public void EncodeInterleavedScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -159,8 +172,8 @@ public void EncodeInterleavedScan(JpegFrame frame, Image image, { JpegComponent component = frame.Components[k]; - ref HuffmanLut dcHuffmanTable = ref HuffmanLut.DcHuffmanLut[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref HuffmanLut.AcHuffmanLut[component.AcTableId]; + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -202,6 +215,8 @@ public void EncodeSingleComponentScan(JpegFrame frame, Image ima { } + private HuffmanLut[] huffmanTables; + /// /// Encodes the image with no subsampling. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 51364e3c73..e0b988b436 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The Huffman encoding specifications. /// - internal readonly struct HuffmanSpec + public readonly struct HuffmanSpec { #pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 209be9d5b1..d92fc457a6 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -22,6 +23,8 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions public JpegFrameConfig JpegFrameConfig { get; set; } + public JpegScanConfig JpegScanConfig { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -31,7 +34,7 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig, this.JpegScanConfig); encoder.Encode(image, stream); } @@ -46,7 +49,7 @@ public void Encode(Image image, Stream stream) public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig, this.JpegScanConfig); return encoder.EncodeAsync(image, stream, cancellationToken); } } @@ -115,4 +118,18 @@ public class JpegComponentConfig public int acTableSelector { get; set; } } + + public class JpegHuffmanTableConfig + { + public int Class { get; set; } + + public int DestinationIndex { get; set; } + + public HuffmanSpec TableSpec { get; set; } + } + + public class JpegScanConfig + { + public JpegHuffmanTableConfig[] HuffmanTables { get; set; } + } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 6d720dfd15..414f08248a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -46,6 +46,10 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals private JpegFrameConfig frameConfig; + private JpegScanConfig scanConfig; + + private HuffmanScanEncoder scanEncoder; + /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -55,12 +59,14 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// Initializes a new instance of the class. /// /// The options. - public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) + public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig, JpegScanConfig scanConfig) { this.quality = options.Quality; this.frameConfig = frameConfig; this.colorType = frameConfig.ColorType; + + this.scanConfig = scanConfig; } /// @@ -83,6 +89,8 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); + this.scanEncoder = new HuffmanScanEncoder(3, stream); + this.outputStream = stream; ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); @@ -116,14 +124,14 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); // Write the Huffman tables. - this.WriteDefineHuffmanTables(this.frameConfig.Components.Length); + this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables); // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); 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).EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); + this.scanEncoder.EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); @@ -273,40 +281,26 @@ private void WriteJfifApplicationHeader(ImageMetadata meta) /// Writes the Define Huffman Table marker and tables. /// /// The number of components to write. - private void WriteDefineHuffmanTables(int componentCount) + private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tables) { - // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, - // and doesn't incur any allocation at all. - // Table identifiers. - ReadOnlySpan headers = new byte[] - { - 0x00, - 0x10, - 0x01, - 0x11 - }; - int markerlen = 2; - HuffmanSpec[] specs = HuffmanSpec.TheHuffmanSpecs; - if (componentCount == 1) + for (int i = 0; i < tables.Length; i++) { - // Drop the Chrominance tables. - specs = new[] { HuffmanSpec.TheHuffmanSpecs[0], HuffmanSpec.TheHuffmanSpecs[1] }; - } - - for (int i = 0; i < specs.Length; i++) - { - ref HuffmanSpec s = ref specs[i]; - markerlen += 1 + 16 + s.Values.Length; + markerlen += 1 + 16 + tables[i].TableSpec.Values.Length; } this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < specs.Length; i++) + for (int i = 0; i < tables.Length; i++) { - this.outputStream.WriteByte(headers[i]); - this.outputStream.Write(specs[i].Count); - this.outputStream.Write(specs[i].Values); + JpegHuffmanTableConfig table = tables[i]; + + int header = (table.Class << 4) | table.DestinationIndex; + this.outputStream.WriteByte((byte)header); + this.outputStream.Write(table.TableSpec.Count); + this.outputStream.Write(table.TableSpec.Values); + + this.scanEncoder.BuildHuffmanTable(table); } } From ea81abc4f0f3c625db137bc6dd22c228c1d2cd8c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 9 May 2022 03:23:46 +0300 Subject: [PATCH 11/39] Implemented quantization tables --- .../Jpeg/Components/Block8x8.Intrinsic.cs | 2 +- .../Formats/Jpeg/Components/Block8x8.cs | 17 +++- .../Jpeg/Components/Block8x8F.Generated.cs | 2 +- .../Jpeg/Components/Block8x8F.Intrinsic.cs | 2 +- .../Jpeg/Components/Block8x8F.ScaledCopyTo.cs | 2 +- .../Formats/Jpeg/Components/Block8x8F.cs | 2 +- .../Components/Encoder/HuffmanScanEncoder.cs | 13 ++- .../Formats/Jpeg/Components/Quantization.cs | 14 +++- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 12 ++- .../Formats/Jpeg/JpegEncoderCore.cs | 84 ++++++++++--------- 10 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs index 002d382dc6..0a6c099d02 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - internal unsafe partial struct Block8x8 + public unsafe partial struct Block8x8 { [FieldOffset(0)] public Vector128 V0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 4b03f9f7b9..3bcfbdfa59 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Explicit)] - internal unsafe partial struct Block8x8 + public unsafe partial struct Block8x8 { /// /// A number of scalar coefficients in a @@ -122,6 +122,21 @@ public void CopyTo(Span destination) } } + public static Block8x8 Load(ReadOnlySpan data) + { + Unsafe.SkipInit(out Block8x8 result); + result.LoadFrom(data); + return result; + } + + public void LoadFrom(ReadOnlySpan source) + { + for (int i = 0; i < Size; i++) + { + this[i] = source[i]; + } + } + /// /// Load raw 16bit integers from source. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index dd5d3f1960..8b40ff5c6f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -8,7 +8,7 @@ // namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - internal partial struct Block8x8F + public partial struct Block8x8F { /// /// Level shift by +maximum/2, clip to [0, maximum] diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs index 0971ccdca0..dc86442624 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - internal partial struct Block8x8F + public partial struct Block8x8F { /// /// A number of rows of 8 scalar coefficients each in diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs index ae2d1f722c..fa9aa24498 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs @@ -10,7 +10,7 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - internal partial struct Block8x8F + public partial struct Block8x8F { /// /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 0190fc7454..3e8fddebf7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 8x8 matrix of coefficients. /// [StructLayout(LayoutKind.Explicit)] - internal partial struct Block8x8F : IEquatable + public partial struct Block8x8F : IEquatable { /// /// A number of scalar coefficients in a diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 6b69e43209..3d61967ef3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -62,12 +62,12 @@ internal class HuffmanScanEncoder /// /// The DC Huffman tables. /// - private readonly HuffmanLut[] dcHuffmanTables; + private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4]; /// /// The AC Huffman tables. /// - private readonly HuffmanLut[] acHuffmanTables; + private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4]; /// /// Emitted bits 'micro buffer' before being transferred to the . @@ -119,9 +119,6 @@ public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream) this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.target = outputStream; - - this.dcHuffmanTables = new HuffmanLut[4]; - this.acHuffmanTables = new HuffmanLut[4]; } /// @@ -135,10 +132,10 @@ private bool IsStreamFlushNeeded get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } - public void BuildHuffmanTable(JpegHuffmanTableConfig table) + public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) { - HuffmanLut[] tables = table.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[table.DestinationIndex] = new HuffmanLut(table.TableSpec); + HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); } public void EncodeInterleavedScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs index eab5e6a082..b85503ee15 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs @@ -176,7 +176,19 @@ private static int QualityToScale(int quality) return quality < 50 ? (5000 / quality) : (200 - (quality * 2)); } - private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan unscaledTable) + public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan unscaledTable) + { + Block8x8F table = default; + for (int j = 0; j < Block8x8F.Size; j++) + { + int x = ((unscaledTable[j] * scale) + 50) / 100; + table[j] = Numerics.Clamp(x, 1, 255); + } + + return table; + } + + public static Block8x8F ScaleQuantizationTable(int scale, Block8x8 unscaledTable) { Block8x8F table = default; for (int j = 0; j < Block8x8F.Size; j++) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index d92fc457a6..371f543cf9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.PixelFormats; @@ -125,11 +126,20 @@ public class JpegHuffmanTableConfig public int DestinationIndex { get; set; } - public HuffmanSpec TableSpec { get; set; } + public HuffmanSpec Table { get; set; } + } + + public class JpegQuantizationTableConfig + { + public int DestinationIndex { get; set; } + + public Block8x8 Table { get; set; } } public class JpegScanConfig { public JpegHuffmanTableConfig[] HuffmanTables { get; set; } + + public JpegQuantizationTableConfig[] QuantizationTables { get; set; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 414f08248a..066e79bc0c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -50,6 +50,8 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals private HuffmanScanEncoder scanEncoder; + public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; + /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -95,10 +97,6 @@ public void Encode(Image image, Stream stream, CancellationToken ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); - // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that - // Initialize the quantization tables. - this.InitQuantizationTables(this.frameConfig.Components.Length, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); - // Write the Start Of Image marker. this.WriteStartOfImage(); @@ -117,21 +115,20 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteApp14Marker(); } - // Write the quantization tables. - this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); - // Write the image dimensions. this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); // Write the Huffman tables. this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables); + // Write the quantization tables. + this.InitQuantizationTables(this.scanConfig.QuantizationTables, jpegMetadata); + // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); - var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - this.scanEncoder.EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); + this.scanEncoder.EncodeInterleavedScan(frame, image, this.QuantizationTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); @@ -281,26 +278,26 @@ private void WriteJfifApplicationHeader(ImageMetadata meta) /// Writes the Define Huffman Table marker and tables. /// /// The number of components to write. - private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tables) + private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs) { int markerlen = 2; - for (int i = 0; i < tables.Length; i++) + for (int i = 0; i < tableConfigs.Length; i++) { - markerlen += 1 + 16 + tables[i].TableSpec.Values.Length; + markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length; } this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < tables.Length; i++) + for (int i = 0; i < tableConfigs.Length; i++) { - JpegHuffmanTableConfig table = tables[i]; + JpegHuffmanTableConfig tableConfig = tableConfigs[i]; - int header = (table.Class << 4) | table.DestinationIndex; + int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex; this.outputStream.WriteByte((byte)header); - this.outputStream.Write(table.TableSpec.Count); - this.outputStream.Write(table.TableSpec.Values); + this.outputStream.Write(tableConfig.Table.Count); + this.outputStream.Write(tableConfig.Table.Values); - this.scanEncoder.BuildHuffmanTable(table); + this.scanEncoder.BuildHuffmanTable(tableConfig); } } @@ -749,37 +746,42 @@ private void WriteMarkerHeader(byte marker, int length) /// Jpeg metadata instance. /// Output luminance quantization table. /// Output chrominance quantization table. - private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable) + private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) { - int lumaQuality; - int chromaQuality; - if (this.quality.HasValue) - { - lumaQuality = this.quality.Value; - chromaQuality = this.quality.Value; - } - else - { - lumaQuality = metadata.LuminanceQuality; - chromaQuality = metadata.ChrominanceQuality; - } + int dataLen = configs.Length * (1 + Block8x8F.Size); - // Luminance - lumaQuality = Numerics.Clamp(lumaQuality, 1, 100); - luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality); + // Marker + quantization table lengths. + int markerlen = 2 + dataLen; + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + + byte[] buffer = new byte[dataLen]; + int offset = 0; - // Chrominance - chrominanceQuantTable = default; - if (componentCount > 1) + for (int i = 0; i < configs.Length; i++) { - chromaQuality = Numerics.Clamp(chromaQuality, 1, 100); - chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality); + JpegQuantizationTableConfig config = configs[i]; - if (!this.colorType.HasValue) + // write to the output stream + buffer[offset++] = (byte)config.DestinationIndex; + for (int j = 0; j < Block8x8F.Size; j++) { - this.colorType = chromaQuality >= 91 ? JpegEncodingMode.YCbCrRatio444 : JpegEncodingMode.YCbCrRatio420; + buffer[offset++] = (byte)(uint)config.Table[ZigZag.ZigZagOrder[j]]; } + + // apply scaling and save into buffer + int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata); + this.QuantizationTables[config.DestinationIndex] = Quantization.ScaleQuantizationTable(quality, config.Table); } + + // write filled buffer to the stream + this.outputStream.Write(buffer); + + static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch + { + 0 => encoderQuality ?? metadata.LuminanceQuality, + 1 => encoderQuality ?? metadata.ChrominanceQuality, + _ => encoderQuality ?? metadata.Quality, + }; } } } From dd3c3ec601dd7a9f5ca1399ee5258626783b346e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 9 May 2022 18:06:04 +0300 Subject: [PATCH 12/39] Fixed sampling factors (hopefully) --- .../Jpeg/Components/Block8x8F.ScaledCopy.cs | 162 ++++++++++++++++ .../Jpeg/Components/Block8x8F.ScaledCopyTo.cs | 179 ------------------ .../Components/Encoder/HuffmanScanEncoder.cs | 1 - .../Encoder/JpegComponentPostProcessor.cs | 80 ++++++-- .../Jpeg/Components/Encoder/JpegFrame.cs | 36 ++-- .../Formats/Jpeg/Components/Quantization.cs | 9 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 7 + .../Formats/Jpeg/JpegEncoderCore.cs | 19 +- .../Jpg/Block8x8FTests.CopyToBufferArea.cs | 97 ---------- 9 files changed, 270 insertions(+), 320 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs delete mode 100644 tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs new file mode 100644 index 0000000000..1ba4dc6b0f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -0,0 +1,162 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + public partial struct Block8x8F + { + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => + CopyFrom1x1Scale(ref Unsafe.As(ref areaOrigin), ref Unsafe.As(ref this), areaStride); + + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + CopyTo1x1Scale(ref Unsafe.As(ref this), ref Unsafe.As(ref areaOrigin), areaStride); + return; + } + + if (horizontalScale == 2 && verticalScale == 2) + { + this.CopyTo2x2Scale(ref areaOrigin, areaStride); + return; + } + + // TODO: Optimize: implement all cases with scale-specific, loopless code! + this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); + } + + private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) + { + ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride / 2; + + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + int offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X); + xyLeft.Z = sLeft.Y; + xyLeft.W = sLeft.Y; + + var zwLeft = new Vector4(sLeft.Z); + zwLeft.Z = sLeft.W; + zwLeft.W = sLeft.W; + + var xyRight = new Vector4(sRight.X); + xyRight.Z = sRight.Y; + xyRight.W = sRight.Y; + + var zwRight = new Vector4(sRight.Z); + zwRight.Z = sRight.W; + zwRight.W = sRight.W; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * areaStride) + xx; + + for (int j = 0; j < horizontalScale; j++) + { + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; + } + } + } + } + } + + private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride) + { + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref origin, ref dest, destStride, 0); + CopyRowImpl(ref origin, ref dest, destStride, 1); + CopyRowImpl(ref origin, ref dest, destStride, 2); + CopyRowImpl(ref origin, ref dest, destStride, 3); + CopyRowImpl(ref origin, ref dest, destStride, 4); + CopyRowImpl(ref origin, ref dest, destStride, 5); + CopyRowImpl(ref origin, ref dest, destStride, 6); + CopyRowImpl(ref origin, ref dest, destStride, 7); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row) + { + origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float)); + dest = ref Unsafe.Add(ref dest, row * destStride); + Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); + } + } + + private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride) + { + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref origin, ref dest, destStride, 0); + CopyRowImpl(ref origin, ref dest, destStride, 1); + CopyRowImpl(ref origin, ref dest, destStride, 2); + CopyRowImpl(ref origin, ref dest, destStride, 3); + CopyRowImpl(ref origin, ref dest, destStride, 4); + CopyRowImpl(ref origin, ref dest, destStride, 5); + CopyRowImpl(ref origin, ref dest, destStride, 6); + CopyRowImpl(ref origin, ref dest, destStride, 7); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row) + { + origin = ref Unsafe.Add(ref origin, row * sourceStride); + dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float)); + Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs deleted file mode 100644 index fa9aa24498..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -// ReSharper disable UseObjectOrCollectionInitializer -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - public partial struct Block8x8F - { - /// - /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(in Buffer2DRegion region, int horizontalScale, int verticalScale) - { - ref float areaOrigin = ref region.GetReferenceToOrigin(); - this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyFrom(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref areaOrigin); - int destStride = areaStride * sizeof(float); - - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 0); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 1); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 2); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 3); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 4); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 5); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 6); - CopyRowImplFromStrides(ref selfBase, ref destBase, destStride, 7); - return; - } - - throw new NotImplementedException("This is a test setup!"); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void CopyRowImplFromStrides(ref byte src, ref byte dst, int srcStride, int row) - { - ref byte s = ref Unsafe.Add(ref dst, row * srcStride); - ref byte d = ref Unsafe.Add(ref src, row * 8 * sizeof(float)); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - this.Copy1x1Scale(ref areaOrigin, areaStride); - return; - } - - if (horizontalScale == 2 && verticalScale == 2) - { - this.Copy2x2Scale(ref areaOrigin, areaStride); - return; - } - - // TODO: Optimize: implement all cases with scale-specific, loopless code! - this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); - } - - public void Copy1x1Scale(ref float areaOrigin, int areaStride) - { - ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref areaOrigin); - int destStride = areaStride * sizeof(float); - - CopyRowImpl(ref selfBase, ref destBase, destStride, 0); - CopyRowImpl(ref selfBase, ref destBase, destStride, 1); - CopyRowImpl(ref selfBase, ref destBase, destStride, 2); - CopyRowImpl(ref selfBase, ref destBase, destStride, 3); - CopyRowImpl(ref selfBase, ref destBase, destStride, 4); - CopyRowImpl(ref selfBase, ref destBase, destStride, 5); - CopyRowImpl(ref selfBase, ref destBase, destStride, 6); - CopyRowImpl(ref selfBase, ref destBase, destStride, 7); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) - { - ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); - ref byte d = ref Unsafe.Add(ref destBase, row * destStride); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } - - private void Copy2x2Scale(ref float areaOrigin, int areaStride) - { - ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); - int destStride = areaStride / 2; - - this.WidenCopyRowImpl2x2(ref destBase, 0, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 1, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 2, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 3, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 4, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 5, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 6, destStride); - this.WidenCopyRowImpl2x2(ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WidenCopyRowImpl2x2(ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref this.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - int offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); - - var xyLeft = new Vector4(sLeft.X); - xyLeft.Z = sLeft.Y; - xyLeft.W = sLeft.Y; - - var zwLeft = new Vector4(sLeft.Z); - zwLeft.Z = sLeft.W; - zwLeft.W = sLeft.W; - - var xyRight = new Vector4(sRight.X); - xyRight.Z = sRight.Y; - xyRight.W = sRight.Y; - - var zwRight = new Vector4(sRight.Z); - zwRight.Z = sRight.W; - zwRight.W = sRight.W; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; - } - - [MethodImpl(InliningOptions.ColdPath)] - private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) - { - for (int y = 0; y < 8; y++) - { - int yy = y * verticalScale; - int y8 = y * 8; - - for (int x = 0; x < 8; x++) - { - int xx = x * horizontalScale; - - float value = this[y8 + x]; - - for (int i = 0; i < verticalScale; i++) - { - int baseIdx = ((yy + i) * areaStride) + xx; - - for (int j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 3d61967ef3..4c1071a286 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -142,7 +142,6 @@ public void EncodeInterleavedScan(JpegFrame frame, Image image, where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP - frame.Init(1, 1); frame.AllocateComponents(fullScan: false); var spectralConverter = new SpectralConverter(configuration); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index ad7bb5f0ff..9ef54d83f2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -21,11 +21,11 @@ public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable); this.component = component; - this.blockAreaSize = this.component.SubSamplingDivisors * 8; + this.blockAreaSize = component.SubSamplingDivisors * 8; this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( postProcessorBufferSize.Width, postProcessorBufferSize.Height, - this.blockAreaSize.Height, + 8 * component.SubSamplingDivisors.Height, AllocationOptions.Clean); } @@ -42,32 +42,28 @@ public void CopyColorBufferToBlocks(int spectralStep) // but 12-bit jpegs are not supported currently float normalizationValue = -128f; - int blocksRowsPerStep = this.component.SamplingFactors.Height; + int destAreaStride = this.ColorBuffer.Width * this.component.SubSamplingDivisors.Height; - int destAreaStride = this.ColorBuffer.Width; - - int yBlockStart = spectralStep * blocksRowsPerStep; - - Size subSamplingDivisors = this.component.SubSamplingDivisors; + int yBlockStart = spectralStep * this.component.SamplingFactors.Height; Block8x8F workspaceBlock = default; - for (int y = 0; y < blocksRowsPerStep; y++) + // handle subsampling + this.PackColorBuffer(); + + for (int y = 0; y < spectralBuffer.Height; y++) { int yBuffer = y * this.blockAreaSize.Height; - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); // load 8x8 block from 8 pixel strides - int xColorBufferStart = xBlock * this.blockAreaSize.Width; + int xColorBufferStart = xBlock * 8; workspaceBlock.ScaledCopyFrom( ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); + destAreaStride); // level shift via -128f workspaceBlock.AddInPlace(normalizationValue); @@ -86,5 +82,61 @@ public Span GetColorBufferRowSpan(int row) public void Dispose() => this.ColorBuffer.Dispose(); + + private void PackColorBuffer() + { + Size factors = this.component.SubSamplingDivisors; + + if (factors.Width == 1 && factors.Height == 1) + { + return; + } + + for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) + { + Span targetBufferRow = this.ColorBuffer.DangerousGetRowSpan(i); + + // vertical sum + for (int j = 1; j < factors.Height; j++) + { + SumVertical(targetBufferRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); + } + + // horizontal sum + SumHorizontal(targetBufferRow, factors.Width); + + // calculate average + float multiplier = 1f / (factors.Width * factors.Height); + MultiplyToAverage(targetBufferRow, multiplier); + } + + static void SumVertical(Span target, Span source) + { + for (int i = 0; i < target.Length; i++) + { + target[i] += source[i]; + } + } + + static void SumHorizontal(Span target, int factor) + { + for (int i = 0; i < target.Length / factor; i++) + { + target[i] = target[i * factor]; + for (int j = 1; j < factor; j++) + { + target[i] += target[(i * factor) + j]; + } + } + } + + static void MultiplyToAverage(Span target, float multiplier) + { + for (int i = 0; i < target.Length; i++) + { + target[i] *= multiplier; + } + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 4213e7e434..675cdaca6b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -18,17 +18,29 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i this.PixelWidth = image.Width; this.PixelHeight = image.Height; - // int componentCount = 3; - var componentConfigs = frameConfig.Components; + JpegComponentConfig[] componentConfigs = frameConfig.Components; this.Components = new JpegComponent[componentConfigs.Length]; for (int i = 0; i < this.Components.Length; i++) { - var componentConfig = componentConfigs[i]; + JpegComponentConfig componentConfig = componentConfigs[i]; this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) { DcTableId = componentConfig.dcTableSelector, AcTableId = componentConfig.acTableSelector, }; + + this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor; + } + + int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor; + int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor; + this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8); + + for (int i = 0; i < this.ComponentCount; i++) + { + JpegComponent component = this.Components[i]; + component.Init(this, maxSubFactorH, maxSubFactorV); } } @@ -42,9 +54,11 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i public JpegComponent[] Components { get; } - public int McusPerLine { get; set; } + public int McusPerLine { get; } + + public int McusPerColumn { get; } - public int McusPerColumn { get; set; } + public int BlocksPerMcu { get; } public void Dispose() { @@ -54,18 +68,6 @@ public void Dispose() } } - public void Init(int maxSubFactorH, int maxSubFactorV) - { - this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); - this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); - - for (int i = 0; i < this.ComponentCount; i++) - { - JpegComponent component = this.Components[i]; - component.Init(this, maxSubFactorH, maxSubFactorV); - } - } - public void AllocateComponents(bool fullScan) { for (int i = 0; i < this.ComponentCount; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs index b85503ee15..a5e823ddbd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs @@ -188,13 +188,14 @@ public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan uns return table; } - public static Block8x8F ScaleQuantizationTable(int scale, Block8x8 unscaledTable) + public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable) { - Block8x8F table = default; - for (int j = 0; j < Block8x8F.Size; j++) + int scale = QualityToScale(quality); + Block8x8 table = default; + for (int j = 0; j < Block8x8.Size; j++) { int x = ((unscaledTable[j] * scale) + 50) / 100; - table[j] = Numerics.Clamp(x, 1, 255); + table[j] = (short)(uint)Numerics.Clamp(x, 1, 255); } return table; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 371f543cf9..5e1bde3097 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -89,6 +89,10 @@ static int GetComponentCountFromColorType(JpegEncodingMode colorType) public JpegComponentConfig[] Components { get; } + public int MaxHorizontalSamplingFactor { get; set; } = 1; + + public int MaxVerticalSamplingFactor { get; set; } = 1; + public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) { this.Components[index] = new JpegComponentConfig @@ -101,6 +105,9 @@ public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, i acTableSelector = acIndex, }; + this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, hsf); + this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, vsf); + return this; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 066e79bc0c..2adec881d0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -91,7 +91,8 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); - this.scanEncoder = new HuffmanScanEncoder(3, stream); + var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); + this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); this.outputStream = stream; ImageMetadata metadata = image.Metadata; @@ -127,7 +128,6 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); - var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); this.scanEncoder.EncodeInterleavedScan(frame, image, this.QuantizationTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. @@ -649,7 +649,7 @@ private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) // Sampling factors // 4 bits - int samplingFactors = components[i].HorizontalSampleFactor | (components[i].VerticalSampleFactor << 4); + int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor; bufferSpan[1] = (byte)samplingFactors; // Id @@ -748,7 +748,7 @@ private void WriteMarkerHeader(byte marker, int length) /// Output chrominance quantization table. private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) { - int dataLen = configs.Length * (1 + Block8x8F.Size); + int dataLen = configs.Length * (1 + Block8x8.Size); // Marker + quantization table lengths. int markerlen = 2 + dataLen; @@ -761,16 +761,19 @@ private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegM { JpegQuantizationTableConfig config = configs[i]; + int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata); + Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); + // write to the output stream buffer[offset++] = (byte)config.DestinationIndex; - for (int j = 0; j < Block8x8F.Size; j++) + + for (int j = 0; j < Block8x8.Size; j++) { - buffer[offset++] = (byte)(uint)config.Table[ZigZag.ZigZagOrder[j]]; + buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; } // apply scaling and save into buffer - int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata); - this.QuantizationTables[config.DestinationIndex] = Quantization.ScaleQuantizationTable(quality, config.Table); + this.QuantizationTables[config.DestinationIndex].LoadFromInt16Scalar(ref scaledTable); } // write filled buffer to the stream diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs deleted file mode 100644 index cba042fcbd..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -// Uncomment this to turn unit tests into benchmarks: -// #define BENCHMARKING -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -using Xunit; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - [Trait("Format", "Jpg")] - public partial class Block8x8FTests - { - public class CopyToBufferArea : JpegFixture - { - public CopyToBufferArea(ITestOutputHelper output) - : base(output) - { - } - - private static void VerifyAllZeroOutsideSubArea(Buffer2D buffer, int subX, int subY, int horizontalFactor = 1, int verticalFactor = 1) - { - for (int y = 0; y < 20; y++) - { - for (int x = 0; x < 20; x++) - { - if (x < subX || x >= subX + (8 * horizontalFactor) || y < subY || y >= subY + (8 * verticalFactor)) - { - Assert.Equal(0, buffer[x, y]); - } - } - } - } - - [Fact] - public void Copy1x1Scale() - { - Block8x8F block = CreateRandomFloatBlock(0, 100); - - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) - { - Buffer2DRegion region = buffer.GetRegion(5, 10, 8, 8); - block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride); - - Assert.Equal(block[0, 0], buffer[5, 10]); - Assert.Equal(block[1, 0], buffer[6, 10]); - Assert.Equal(block[0, 1], buffer[5, 11]); - Assert.Equal(block[0, 7], buffer[5, 17]); - Assert.Equal(block[63], buffer[12, 17]); - - VerifyAllZeroOutsideSubArea(buffer, 5, 10); - } - } - - [Theory] - [InlineData(1, 1)] - [InlineData(1, 2)] - [InlineData(2, 1)] - [InlineData(2, 2)] - [InlineData(4, 2)] - [InlineData(4, 4)] - public void CopyTo(int horizontalFactor, int verticalFactor) - { - Block8x8F block = CreateRandomFloatBlock(0, 100); - - var start = new Point(50, 50); - - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(100, 100, AllocationOptions.Clean)) - { - Buffer2DRegion region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); - block.ScaledCopyTo(region, horizontalFactor, verticalFactor); - - for (int y = 0; y < 8 * verticalFactor; y++) - { - for (int x = 0; x < 8 * horizontalFactor; x++) - { - int yy = y / verticalFactor; - int xx = x / horizontalFactor; - - float expected = block[xx, yy]; - float actual = region[x, y]; - - Assert.Equal(expected, actual); - } - } - - VerifyAllZeroOutsideSubArea(buffer, start.X, start.Y, horizontalFactor, verticalFactor); - } - } - } - } -} From 3d31a16a883f0350fffbd61b676c77531c15256c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 10 May 2022 13:41:49 +0300 Subject: [PATCH 13/39] Made frame configs internal --- .../Components/Encoder/HuffmanScanEncoder.cs | 7 +- .../Encoder/SpectralConverter{TPixel}.cs | 5 +- .../Formats/Jpeg/IJpegEncoderOptions.cs | 5 - .../Formats/Jpeg/JpegDecoderCore.cs | 22 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 201 +++++++++++++----- .../Formats/Jpeg/JpegEncoderCore.cs | 112 ++++------ ...egEncodingMode.cs => JpegEncodingColor.cs} | 6 +- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 2 +- .../Compressors/TiffJpegCompressor.cs | 2 +- .../Codecs/Jpeg/EncodeJpegComparison.cs | 2 +- .../Codecs/Jpeg/EncodeJpegFeatures.cs | 6 +- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 30 +-- .../Formats/Jpg/JpegEncoderTests.cs | 82 +++---- .../Formats/Jpg/JpegMetadataTests.cs | 4 +- .../JpegProfilingBenchmarks.cs | 10 +- 15 files changed, 274 insertions(+), 222 deletions(-) rename src/ImageSharp/Formats/Jpeg/{JpegEncodingMode.cs => JpegEncodingColor.cs} (93%) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 4c1071a286..1dde766e78 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -138,15 +138,12 @@ public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); } - public void EncodeInterleavedScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + public void EncodeInterleavedBaselineScan(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP frame.AllocateComponents(fullScan: false); - var spectralConverter = new SpectralConverter(configuration); - spectralConverter.InjectFrameData(frame, image, quantTables); - // DEBUG ENCODING SETUP int mcu = 0; int mcusPerColumn = frame.McusPerColumn; @@ -157,7 +154,7 @@ public void EncodeInterleavedScan(JpegFrame frame, Image image, cancellationToken.ThrowIfCancellationRequested(); // Convert from pixels to spectral via given converter - spectralConverter.ConvertStrideBaseline(); + converter.ConvertStrideBaseline(); // decode from binary to spectral for (int i = 0; i < mcusPerLine; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 2877d8f65e..b9a98aace2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -29,11 +29,10 @@ internal class SpectralConverter : SpectralConverter private JpegColorConverterBase colorConverter; - public SpectralConverter(Configuration configuration) => + public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables, Configuration configuration) + { this.configuration = configuration; - public void InjectFrameData(JpegFrame frame, Image image, Block8x8F[] dequantTables) - { MemoryAllocator allocator = this.configuration.MemoryAllocator; // iteration data diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index e8daeaf738..ff87d88eb5 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -14,10 +14,5 @@ internal interface IJpegEncoderOptions /// Defaults to 75. /// public int? Quality { get; set; } - - /// - /// Gets the color type, that will be used to encode the image. - /// - JpegEncodingMode? ColorType { get; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index b933ff6fed..10e52066d1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -545,57 +545,57 @@ private JpegColorSpace DeduceJpegColorSpace(byte componentCount) /// Returns the jpeg color type based on the colorspace and subsampling used. /// /// Jpeg color type. - private JpegEncodingMode DeduceJpegColorType() + private JpegEncodingColor DeduceJpegColorType() { switch (this.ColorSpace) { case JpegColorSpace.Grayscale: - return JpegEncodingMode.Luminance; + return JpegEncodingColor.Luminance; case JpegColorSpace.RGB: - return JpegEncodingMode.Rgb; + return JpegEncodingColor.Rgb; case JpegColorSpace.YCbCr: if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegEncodingMode.YCbCrRatio444; + return JpegEncodingColor.YCbCrRatio444; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegEncodingMode.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio420; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2) { - return JpegEncodingMode.YCbCrRatio422; + return JpegEncodingColor.YCbCrRatio422; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegEncodingMode.YCbCrRatio411; + return JpegEncodingColor.YCbCrRatio411; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegEncodingMode.YCbCrRatio410; + return JpegEncodingColor.YCbCrRatio410; } else { - return JpegEncodingMode.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio420; } case JpegColorSpace.Cmyk: - return JpegEncodingMode.Cmyk; + return JpegEncodingColor.Cmyk; default: - return JpegEncodingMode.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio420; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 5e1bde3097..926c49b571 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -16,15 +16,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { + /// + /// The available encodable frame configs. + /// + private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); + /// public int? Quality { get; set; } - /// - public JpegEncodingMode? ColorType { get; set; } + public JpegEncodingColor ColorType + { + set + { + JpegFrameConfig frameConfig = Array.Find( + FrameConfigs, + cfg => cfg.EncodingColor == value); + + if (frameConfig is null) + { + throw new ArgumentException(nameof(value)); + } + + this.FrameConfig = frameConfig; + } + } - public JpegFrameConfig JpegFrameConfig { get; set; } + internal JpegFrameConfig FrameConfig { get; set; } - public JpegScanConfig JpegScanConfig { get; set; } + public JpegScanConfig ScanConfig { get; set; } /// /// Encodes the image to the specified stream from the . @@ -35,7 +54,7 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.JpegFrameConfig, this.JpegScanConfig); + var encoder = new JpegEncoderCore(this, this.FrameConfig, this.ScanConfig); encoder.Encode(image, stream); } @@ -50,81 +69,153 @@ public void Encode(Image image, Stream stream) public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.JpegFrameConfig, this.JpegScanConfig); + var encoder = new JpegEncoderCore(this, this.FrameConfig, this.ScanConfig); return encoder.EncodeAsync(image, stream, cancellationToken); } + + private static JpegFrameConfig[] CreateFrameConfigs() => new JpegFrameConfig[] + { + // YCbCr 4:4:4 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio444, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }), + + // YCbCr 4:2:2 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio422, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }), + + // YCbCr 4:2:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio420, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }), + + // YCbCr 4:1:1 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio411, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }), + + // YCbCr 4:1:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio410, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }), + + // Luminance + new JpegFrameConfig( + JpegColorSpace.Grayscale, + JpegEncodingColor.Luminance, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }), + + // Rgb + new JpegFrameConfig( + JpegColorSpace.RGB, + JpegEncodingColor.Rgb, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }), + + // Cmyk + new JpegFrameConfig( + JpegColorSpace.Cmyk, + JpegEncodingColor.Cmyk, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }), + }; } - public class JpegFrameConfig + internal class JpegFrameConfig { - public JpegFrameConfig(JpegEncodingMode colorType) + public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components) { this.ColorType = colorType; + this.EncodingColor = encodingColor; + this.Components = components; - int componentCount = GetComponentCountFromColorType(colorType); - this.Components = new JpegComponentConfig[componentCount]; - - static int GetComponentCountFromColorType(JpegEncodingMode colorType) + this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; + this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; + for (int i = 1; i < components.Length; i++) { - switch (colorType) - { - case JpegEncodingMode.Luminance: - return 1; - case JpegEncodingMode.YCbCrRatio444: - case JpegEncodingMode.YCbCrRatio422: - case JpegEncodingMode.YCbCrRatio420: - case JpegEncodingMode.YCbCrRatio411: - case JpegEncodingMode.YCbCrRatio410: - case JpegEncodingMode.Rgb: - return 3; - case JpegEncodingMode.Cmyk: - return 4; - default: - throw new ArgumentException($"Unknown jpeg color space: {colorType}"); - } + JpegComponentConfig component = components[i]; + this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); + this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); } } - public JpegEncodingMode ColorType { get; } + public JpegColorSpace ColorType { get; } + + public JpegEncodingColor EncodingColor { get; } public JpegComponentConfig[] Components { get; } - public int MaxHorizontalSamplingFactor { get; set; } = 1; + public int MaxHorizontalSamplingFactor { get; } - public int MaxVerticalSamplingFactor { get; set; } = 1; + public int MaxVerticalSamplingFactor { get; } + } - public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + internal class JpegComponentConfig + { + public JpegComponentConfig(byte 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, - }; - - this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, hsf); - this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, vsf); - - return this; + this.Id = id; + this.HorizontalSampleFactor = hsf; + this.VerticalSampleFactor = vsf; + this.QuantizatioTableIndex = quantIndex; + this.dcTableSelector = dcIndex; + this.acTableSelector = acIndex; } - } - public class JpegComponentConfig - { - public byte Id { get; set; } + public byte Id { get; } - public int HorizontalSampleFactor { get; set; } + public int HorizontalSampleFactor { get; } - public int VerticalSampleFactor { get; set; } + public int VerticalSampleFactor { get; } - public int QuantizatioTableIndex { get; set; } + public int QuantizatioTableIndex { get; } - public int dcTableSelector { get; set; } + public int dcTableSelector { get; } - public int acTableSelector { get; set; } + public int acTableSelector { get; } } public class JpegHuffmanTableConfig diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 2adec881d0..452dae4e5f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -8,7 +8,6 @@ using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -42,7 +41,7 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// /// Gets or sets the colorspace to use. /// - private JpegEncodingMode? colorType; + private JpegEncodingColor? colorType; private JpegFrameConfig frameConfig; @@ -66,7 +65,7 @@ public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig, this.quality = options.Quality; this.frameConfig = frameConfig; - this.colorType = frameConfig.ColorType; + this.colorType = frameConfig.EncodingColor; this.scanConfig = scanConfig; } @@ -91,7 +90,7 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); - var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); + var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.EncodingColor)); this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); this.outputStream = stream; @@ -102,7 +101,7 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteStartOfImage(); // Do not write APP0 marker for RGB colorspace. - if (this.colorType != JpegEncodingMode.Rgb) + if (this.colorType != JpegEncodingColor.Rgb) { this.WriteJfifApplicationHeader(metadata); } @@ -110,7 +109,7 @@ public void Encode(Image image, Stream stream, CancellationToken // Write Exif, XMP, ICC and IPTC profiles this.WriteProfiles(metadata); - if (this.colorType == JpegEncodingMode.Rgb) + if (this.colorType == JpegEncodingColor.Rgb) { // Write App14 marker to indicate RGB color space. this.WriteApp14Marker(); @@ -128,28 +127,29 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); - this.scanEncoder.EncodeInterleavedScan(frame, image, this.QuantizationTables, Configuration.Default, cancellationToken); + var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables, Configuration.Default); + this.scanEncoder.EncodeInterleavedBaselineScan(frame, spectralConverter, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); stream.Flush(); - static JpegColorSpace GetTargetColorSpace(JpegEncodingMode colorType) + static JpegColorSpace GetTargetColorSpace(JpegEncodingColor colorType) { switch (colorType) { - case JpegEncodingMode.YCbCrRatio444: - case JpegEncodingMode.YCbCrRatio422: - case JpegEncodingMode.YCbCrRatio420: - case JpegEncodingMode.YCbCrRatio411: - case JpegEncodingMode.YCbCrRatio410: + case JpegEncodingColor.YCbCrRatio444: + case JpegEncodingColor.YCbCrRatio422: + case JpegEncodingColor.YCbCrRatio420: + case JpegEncodingColor.YCbCrRatio411: + case JpegEncodingColor.YCbCrRatio410: return JpegColorSpace.YCbCr; - case JpegEncodingMode.Rgb: + case JpegEncodingColor.Rgb: return JpegColorSpace.RGB; - case JpegEncodingMode.Cmyk: + case JpegEncodingColor.Cmyk: return JpegColorSpace.Cmyk; - case JpegEncodingMode.Luminance: + case JpegEncodingColor.Luminance: return JpegColorSpace.Grayscale; default: throw new NotImplementedException($"Unknown output color space: {colorType}"); @@ -163,11 +163,11 @@ static JpegColorSpace GetTargetColorSpace(JpegEncodingMode colorType) /// returns defering the field assignment /// to . /// - private static JpegEncodingMode? GetFallbackColorType(Image image) + private static JpegEncodingColor? GetFallbackColorType(Image image) where TPixel : unmanaged, IPixel { // First inspect the image metadata. - JpegEncodingMode? colorType = null; + JpegEncodingColor? colorType = null; JpegMetadata metadata = image.Metadata.GetJpegMetadata(); if (IsSupportedColorType(metadata.ColorType)) { @@ -184,7 +184,7 @@ static JpegColorSpace GetTargetColorSpace(JpegEncodingMode colorType) // the quality in InitQuantizationTables. if (isGrayscale) { - colorType = JpegEncodingMode.Luminance; + colorType = JpegEncodingColor.Luminance; } return colorType; @@ -195,11 +195,11 @@ static JpegColorSpace GetTargetColorSpace(JpegEncodingMode colorType) /// /// The color type. /// true, if color type is supported. - private static bool IsSupportedColorType(JpegEncodingMode? colorType) - => colorType == JpegEncodingMode.YCbCrRatio444 - || colorType == JpegEncodingMode.YCbCrRatio420 - || colorType == JpegEncodingMode.Luminance - || colorType == JpegEncodingMode.Rgb; + private static bool IsSupportedColorType(JpegEncodingColor? colorType) + => colorType == JpegEncodingColor.YCbCrRatio444 + || colorType == JpegEncodingColor.YCbCrRatio420 + || colorType == JpegEncodingColor.Luminance + || colorType == JpegEncodingColor.Rgb; /// /// Writes data to "Define Quantization Tables" block for QuantIndex. @@ -301,33 +301,12 @@ private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs) } } - /// - /// Writes the Define Quantization Marker and tables. - /// - private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable) - { - // Marker + quantization table lengths. - int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); - this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - - // Loop through and collect the tables as one array. - // This allows us to reduce the number of writes to the stream. - int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount; - byte[] dqt = new byte[dqtCount]; - int offset = 0; - - WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref luminanceQuantTable); - WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref chrominanceQuantTable); - - this.outputStream.Write(dqt, 0, dqtCount); - } - /// /// Writes the APP14 marker to indicate the image is in RGB color space. /// private void WriteApp14Marker() { - this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + AdobeMarker.Length); + this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length); // Identifier: ASCII "Adobe". this.buffer[0] = 0x41; @@ -373,14 +352,14 @@ private void WriteExifProfile(ExifProfile exifProfile) } // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. - int exifMarkerLength = ProfileResolver.ExifMarker.Length; + int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length; int remaining = exifMarkerLength + data.Length; int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; int app1Length = bytesToWrite + 2; // Write the app marker, EXIF marker, and data this.WriteApp1Header(app1Length); - this.outputStream.Write(ProfileResolver.ExifMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength); remaining -= bytesToWrite; @@ -393,7 +372,7 @@ private void WriteExifProfile(ExifProfile exifProfile) this.WriteApp1Header(app1Length); // Write Exif00 marker - this.outputStream.Write(ProfileResolver.ExifMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); // Write the exif data this.outputStream.Write(data, idx, bytesToWrite); @@ -429,14 +408,14 @@ private void WriteIptcProfile(IptcProfile iptcProfile) throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes"); } - int app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length + - ProfileResolver.AdobeImageResourceBlockMarker.Length + - ProfileResolver.AdobeIptcMarker.Length + + int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length + + Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length + + Components.Decoder.ProfileResolver.AdobeIptcMarker.Length + 2 + 4 + data.Length; this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); - this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker); - this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker); - this.outputStream.Write(ProfileResolver.AdobeIptcMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker); this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) this.outputStream.WriteByte(0); BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); @@ -483,9 +462,9 @@ private void WriteXmpProfile(XmpProfile xmpProfile) dataLength -= length; - int app1Length = 2 + ProfileResolver.XmpMarker.Length + length; + int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length; this.WriteApp1Header(app1Length); - this.outputStream.Write(ProfileResolver.XmpMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker); this.outputStream.Write(data, offset, length); offset += length; @@ -729,23 +708,18 @@ private void WriteMarkerHeader(byte marker, int length) } /// - /// Initializes quantization tables. + /// Writes the Define Quantization Marker and prepares tables for encoding. /// /// - /// - /// Zig-zag ordering is NOT applied to the resulting tables. - /// - /// /// We take quality values in a hierarchical order: - /// 1. Check if encoder has set quality - /// 2. Check if metadata has set quality - /// 3. Take default quality value - 75 - /// + /// + /// Check if encoder has set quality. + /// Check if metadata has set quality. + /// Take default quality value from + /// /// - /// Color components count. + /// Quantization tables configs. /// Jpeg metadata instance. - /// Output luminance quantization table. - /// Output chrominance quantization table. private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) { int dataLen = configs.Length * (1 + Block8x8.Size); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncodingMode.cs b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/JpegEncodingMode.cs rename to src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs index d6105da490..995b6036c8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncodingMode.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Provides enumeration of available JPEG color types. /// - public enum JpegEncodingMode : byte + public enum JpegEncodingColor : byte { /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. @@ -25,16 +25,12 @@ public enum JpegEncodingMode : byte /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio422 = 2, /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio411 = 3, diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index be42151223..bb4cbeeee9 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -108,7 +108,7 @@ public int Quality /// /// Gets or sets the color type. /// - public JpegEncodingMode? ColorType { get; set; } + public JpegEncodingColor? ColorType { get; set; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs index f5eb5507df..231afa3d04 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs @@ -35,7 +35,7 @@ public override void CompressStrip(Span rows, int height) var image = Image.LoadPixelData(rows, width, height); image.Save(memoryStream, new JpegEncoder() { - ColorType = JpegEncodingMode.Rgb + ColorType = JpegEncodingColor.Rgb }); memoryStream.Position = 0; memoryStream.WriteTo(this.Output); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs index 344a00b030..1c05ca8ce6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs @@ -42,7 +42,7 @@ public void SetupImageSharp() using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.imageImageSharp = Image.Load(imageBinaryStream); - this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingMode.YCbCrRatio420 }; + this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingColor.YCbCrRatio420 }; this.destinationStream = new MemoryStream(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs index 2c5dd1f8ca..b115550f93 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -22,14 +22,14 @@ public class EncodeJpegFeatures // No metadata private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - public static IEnumerable ColorSpaceValues => - new[] { JpegEncodingMode.Luminance, JpegEncodingMode.Rgb, JpegEncodingMode.YCbCrRatio420, JpegEncodingMode.YCbCrRatio444 }; + public static IEnumerable ColorSpaceValues => + new[] { JpegEncodingColor.Luminance, JpegEncodingColor.Rgb, JpegEncodingColor.YCbCrRatio420, JpegEncodingColor.YCbCrRatio444 }; [Params(75, 90, 100)] public int Quality; [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] - public JpegEncodingMode TargetColorSpace; + public JpegEncodingColor TargetColorSpace; private Image bmpCore; private JpegEncoder encoder; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 16bc6d6969..7a5dcedef9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -139,15 +139,15 @@ public void Decode_VerifyQuality(string imagePath, int quality) } [Theory] - [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingMode.Luminance)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingMode.YCbCrRatio420)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingMode.YCbCrRatio444)] - [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingMode.Rgb)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingMode.Cmyk)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingMode.YCbCrRatio410)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegEncodingMode.YCbCrRatio422)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingMode.YCbCrRatio411)] - public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingMode expectedColorType) + [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingColor.Luminance)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingColor.YCbCrRatio420)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingColor.YCbCrRatio444)] + [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegEncodingColor.YCbCrRatio422)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] + public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) { var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) @@ -159,12 +159,12 @@ public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingMode } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingMode.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingMode.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingMode.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingMode.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingMode.Cmyk)] - public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegEncodingMode expectedColorType) + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] + public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(JpegDecoder)) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index db66015683..9f36fcc0f1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -33,18 +33,18 @@ public partial class JpegEncoderTests { TestImages.Jpeg.Progressive.Fb, 75 } }; - public static readonly TheoryData BitsPerPixel_Quality = + public static readonly TheoryData BitsPerPixel_Quality = new() { - { JpegEncodingMode.YCbCrRatio420, 40 }, - { JpegEncodingMode.YCbCrRatio420, 60 }, - { JpegEncodingMode.YCbCrRatio420, 100 }, - { JpegEncodingMode.YCbCrRatio444, 40 }, - { JpegEncodingMode.YCbCrRatio444, 60 }, - { JpegEncodingMode.YCbCrRatio444, 100 }, - { JpegEncodingMode.Rgb, 40 }, - { JpegEncodingMode.Rgb, 60 }, - { JpegEncodingMode.Rgb, 100 } + { JpegEncodingColor.YCbCrRatio420, 40 }, + { JpegEncodingColor.YCbCrRatio420, 60 }, + { JpegEncodingColor.YCbCrRatio420, 100 }, + { JpegEncodingColor.YCbCrRatio444, 40 }, + { JpegEncodingColor.YCbCrRatio444, 60 }, + { JpegEncodingColor.YCbCrRatio444, 100 }, + { JpegEncodingColor.Rgb, 40 }, + { JpegEncodingColor.Rgb, 60 }, + { JpegEncodingColor.Rgb, 100 } }; public static readonly TheoryData Grayscale_Quality = @@ -64,11 +64,11 @@ public partial class JpegEncoderTests }; [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingMode.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegEncodingMode.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingMode expectedColorType) + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegEncodingColor.Rgb)] + public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) where TPixel : unmanaged, IPixel { // arrange @@ -107,15 +107,15 @@ public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420(memoryStream); JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegEncodingMode.YCbCrRatio420, meta.ColorType); + Assert.Equal(JpegEncodingColor.YCbCrRatio420, meta.ColorType); } [Theory] - [InlineData(JpegEncodingMode.Cmyk)] - [InlineData(JpegEncodingMode.YCbCrRatio410)] - [InlineData(JpegEncodingMode.YCbCrRatio411)] - [InlineData(JpegEncodingMode.YCbCrRatio422)] - public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegEncodingMode colorType) + [InlineData(JpegEncodingColor.Cmyk)] + [InlineData(JpegEncodingColor.YCbCrRatio410)] + [InlineData(JpegEncodingColor.YCbCrRatio411)] + [InlineData(JpegEncodingColor.YCbCrRatio422)] + public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegEncodingColor colorType) { // arrange var jpegEncoder = new JpegEncoder() { ColorType = colorType }; @@ -129,7 +129,7 @@ public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegEncodingMode memoryStream.Position = 0; using var output = Image.Load(memoryStream); JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegEncodingMode.YCbCrRatio420, meta.ColorType); + Assert.Equal(JpegEncodingColor.YCbCrRatio420, meta.ColorType); } [Theory] @@ -155,7 +155,7 @@ public void Encode_PreservesQuality(string imagePath, int quality) [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegEncodingMode colorType, int quality) + public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegEncodingColor colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] @@ -164,7 +164,7 @@ public void EncodeBaseline_CalliphoraPartial(TestImageProvider p [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingMode colorType, int quality) + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] @@ -177,7 +177,7 @@ public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider(TestImageProvider provider, JpegEncodingMode colorType, int quality) + public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); [Theory] @@ -188,27 +188,27 @@ public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestI [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] public void EncodeBaseline_Grayscale(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingMode.Luminance, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingMode colorType, int quality) + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingMode colorType, int quality) + public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegEncodingMode.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegEncodingMode.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegEncodingMode.YCbCrRatio420)] - public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingMode colorType) + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegEncodingColor.YCbCrRatio420)] + [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegEncodingColor.YCbCrRatio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingColor colorType) where TPixel : unmanaged, IPixel { - ImageComparer comparer = colorType == JpegEncodingMode.YCbCrRatio444 + ImageComparer comparer = colorType == JpegEncodingColor.YCbCrRatio444 ? ImageComparer.TolerantPercentage(0.1f) : ImageComparer.TolerantPercentage(5f); @@ -219,7 +219,7 @@ public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvid /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// - private static ImageComparer GetComparer(int quality, JpegEncodingMode? colorType) + private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) { float tolerance = 0.015f; // ~1.5% @@ -227,10 +227,10 @@ private static ImageComparer GetComparer(int quality, JpegEncodingMode? colorTyp { tolerance *= 4.5f; } - else if (quality < 75 || colorType == JpegEncodingMode.YCbCrRatio420) + else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) { tolerance *= 2.0f; - if (colorType == JpegEncodingMode.YCbCrRatio420) + if (colorType == JpegEncodingColor.YCbCrRatio420) { tolerance *= 2.0f; } @@ -241,7 +241,7 @@ private static ImageComparer GetComparer(int quality, JpegEncodingMode? colorTyp private static void TestJpegEncoderCore( TestImageProvider provider, - JpegEncodingMode colorType = JpegEncodingMode.YCbCrRatio420, + JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, int quality = 100, ImageComparer comparer = null) where TPixel : unmanaged, IPixel @@ -396,9 +396,9 @@ public void Encode_PreservesIccProfile() } [Theory] - [InlineData(JpegEncodingMode.YCbCrRatio420)] - [InlineData(JpegEncodingMode.YCbCrRatio444)] - public async Task Encode_IsCancellable(JpegEncodingMode colorType) + [InlineData(JpegEncodingColor.YCbCrRatio420)] + [InlineData(JpegEncodingColor.YCbCrRatio444)] + public async Task Encode_IsCancellable(JpegEncodingColor colorType) { var cts = new CancellationTokenSource(); using var pausedStream = new PausedStream(new MemoryStream()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 0c229488aa..b72059b669 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -12,11 +12,11 @@ public class JpegMetadataTests [Fact] public void CloneIsDeep() { - var meta = new JpegMetadata { Quality = 50, ColorType = JpegEncodingMode.Luminance }; + var meta = new JpegMetadata { Quality = 50, ColorType = JpegEncodingColor.Luminance }; var clone = (JpegMetadata)meta.DeepClone(); clone.Quality = 99; - clone.ColorType = JpegEncodingMode.YCbCrRatio420; + clone.ColorType = JpegEncodingColor.YCbCrRatio420; Assert.False(meta.Quality.Equals(clone.Quality)); Assert.False(meta.ColorType.Equals(clone.ColorType)); diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index ad1402105d..3ca4944eba 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -91,11 +91,11 @@ public void EncodeJpeg_SingleMidSize() // Benchmark, enable manually! [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(1, 75, JpegEncodingMode.YCbCrRatio420)] - [InlineData(30, 75, JpegEncodingMode.YCbCrRatio420)] - [InlineData(30, 75, JpegEncodingMode.YCbCrRatio444)] - [InlineData(30, 100, JpegEncodingMode.YCbCrRatio444)] - public void EncodeJpeg(int executionCount, int quality, JpegEncodingMode colorType) + [InlineData(1, 75, JpegEncodingColor.YCbCrRatio420)] + [InlineData(30, 75, JpegEncodingColor.YCbCrRatio420)] + [InlineData(30, 75, JpegEncodingColor.YCbCrRatio444)] + [InlineData(30, 100, JpegEncodingColor.YCbCrRatio444)] + public void EncodeJpeg(int executionCount, int quality, JpegEncodingColor colorType) { // do not run this on CI even by accident if (TestEnvironment.RunsOnCI) From 5d3dcc0cf0fa40e6e8ac192150de8178155fe62e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 10 May 2022 16:09:00 +0300 Subject: [PATCH 14/39] Implemented single component scan encoding --- .../Components/Encoder/HuffmanScanEncoder.cs | 44 +++++++++++++++++-- .../Formats/Jpeg/JpegEncoderCore.cs | 10 ++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 1dde766e78..3c606d6e06 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -138,7 +138,7 @@ public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); } - public void EncodeInterleavedBaselineScan(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + public void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP @@ -156,7 +156,7 @@ public void EncodeInterleavedBaselineScan(JpegFrame frame, SpectralConve // Convert from pixels to spectral via given converter converter.ConvertStrideBaseline(); - // decode from binary to spectral + // Encode spectral to binary for (int i = 0; i < mcusPerLine; i++) { // Scan an interleaved mcu... process components in order @@ -203,9 +203,47 @@ ref Unsafe.Add(ref blockRef, blockCol), this.FlushRemainingBytes(); } - public void EncodeSingleComponentScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + // DEBUG INITIALIZATION SETUP + frame.AllocateComponents(fullScan: false); + + JpegComponent component = frame.Components[0]; + int mcuLines = frame.McusPerColumn; + int w = component.WidthInBlocks; + int h = component.SamplingFactors.Height; + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + for (int i = 0; i < mcuLines; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); + + // Encode spectral to binary + for (int j = 0; j < h; j++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int k = 0; k < w; k++) + { + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); + + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } + } + } + } } private HuffmanLut[] huffmanTables; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 452dae4e5f..a8e4dc2a17 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -128,7 +128,15 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables, Configuration.Default); - this.scanEncoder.EncodeInterleavedBaselineScan(frame, spectralConverter, cancellationToken); + + if (frame.ComponentCount > 1) + { + this.scanEncoder.EncodeScanBaselineInterleaved(frame, spectralConverter, cancellationToken); + } + else + { + this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken); + } // Write the End Of Image marker. this.WriteEndOfImageMarker(); From a5305ba5ca7c1392fda971413fd827003794ff35 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 10 May 2022 16:22:36 +0300 Subject: [PATCH 15/39] Removed obsolete code --- .../Jpeg/Components/Encoder/HuffIndex.cs | 35 -- .../Components/Encoder/HuffmanScanEncoder.cs | 318 ------------------ .../LuminanceForwardConverter{TPixel}.cs | 127 ------- .../Encoder/RgbForwardConverter{TPixel}.cs | 165 --------- .../Encoder/RgbToYCbCrConverterLut.cs | 237 ------------- .../Encoder/RgbToYCbCrConverterVectorized.cs | 259 -------------- .../YCbCrForwardConverter420{TPixel}.cs | 121 ------- .../YCbCrForwardConverter444{TPixel}.cs | 122 ------- .../Encoder/YCbCrForwardConverter{TPixel}.cs | 61 ---- .../Formats/Jpeg/JpegEncoderCore.cs | 23 +- .../Formats/Jpg/RgbToYCbCrConverterTests.cs | 272 --------------- 11 files changed, 1 insertion(+), 1739 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs delete mode 100644 tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs deleted file mode 100644 index e2416d9273..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffIndex.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// Enumerates the Huffman tables - /// - internal enum HuffIndex - { - /// - /// The DC luminance huffman table index - /// - LuminanceDC = 0, - - // ReSharper disable UnusedMember.Local - - /// - /// The AC luminance huffman table index - /// - LuminanceAC = 1, - - /// - /// The DC chrominance huffman table index - /// - ChrominanceDC = 2, - - /// - /// The AC chrominance huffman table index - /// - ChrominanceAC = 3, - - // ReSharper restore UnusedMember.Local - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 3c606d6e06..6e81e3a9a6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -98,8 +97,6 @@ internal class HuffmanScanEncoder private int emitWriteIndex; - private Block8x8 tempBlock; - /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -246,321 +243,6 @@ ref Unsafe.Add(ref blockRef, k), } } - private HuffmanLut[] huffmanTables; - - /// - /// Encodes the image with no subsampling. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Luminance quantization table provided by the callee. - /// Chrominance quantization table provided by the callee. - /// The token to monitor for cancellation. - public void Encode444(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable); - FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; - - // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - var pixelConverter = new YCbCrForwardConverter444(frame); - - for (int y = 0; y < pixels.Height; y += 8) - { - cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - - for (int x = 0; x < pixels.Width; x += 8) - { - pixelConverter.Convert(x, y, ref currentRows); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref luminanceQuantTable); - - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - ref pixelConverter.Cb, - ref chrominanceQuantTable); - - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - ref pixelConverter.Cr, - ref chrominanceQuantTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes the image with subsampling. The Cb and Cr components are each subsampled - /// at a factor of 2 both horizontally and vertically. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Luminance quantization table provided by the callee. - /// Chrominance quantization table provided by the callee. - /// The token to monitor for cancellation. - public void Encode420(Image pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable); - FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; - - // ReSharper disable once InconsistentNaming - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - var pixelConverter = new YCbCrForwardConverter420(frame); - - for (int y = 0; y < pixels.Height; y += 16) - { - cancellationToken.ThrowIfCancellationRequested(); - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 2; i++) - { - int yOff = i * 8; - currentRows.Update(pixelBuffer, y + yOff); - pixelConverter.Convert(x, y, ref currentRows, i); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.YLeft, - ref luminanceQuantTable); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.YRight, - ref luminanceQuantTable); - } - - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - ref pixelConverter.Cb, - ref chrominanceQuantTable); - - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - ref pixelConverter.Cr, - ref chrominanceQuantTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes the image with no chroma, just luminance. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Luminance quantization table provided by the callee. - /// The token to monitor for cancellation. - public void EncodeGrayscale(Image pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; - - // ReSharper disable once InconsistentNaming - int prevDCY = 0; - - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - var pixelConverter = new LuminanceForwardConverter(frame); - - for (int y = 0; y < pixels.Height; y += 8) - { - cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - - for (int x = 0; x < pixels.Width; x += 8) - { - pixelConverter.Convert(x, y, ref currentRows); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - ref pixelConverter.Y, - ref luminanceQuantTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Encodes the image with no subsampling and keeps the pixel data as Rgb24. - /// - /// The pixel format. - /// The pixel accessor providing access to the image pixels. - /// Quantization table provided by the callee. - /// The token to monitor for cancellation. - public void EncodeRgb(Image pixels, ref Block8x8F quantTable, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - FastFloatingPointDCT.AdjustToFDCT(ref quantTable); - - this.huffmanTables = HuffmanLut.TheHuffmanLut; - - // ReSharper disable once InconsistentNaming - int prevDCR = 0, prevDCG = 0, prevDCB = 0; - - ImageFrame frame = pixels.Frames.RootFrame; - Buffer2D pixelBuffer = frame.PixelBuffer; - RowOctet currentRows = default; - - var pixelConverter = new RgbForwardConverter(frame); - - for (int y = 0; y < pixels.Height; y += 8) - { - cancellationToken.ThrowIfCancellationRequested(); - currentRows.Update(pixelBuffer, y); - - for (int x = 0; x < pixels.Width; x += 8) - { - pixelConverter.Convert(x, y, ref currentRows); - - prevDCR = this.WriteBlock( - QuantIndex.Luminance, - prevDCR, - ref pixelConverter.R, - ref quantTable); - - prevDCG = this.WriteBlock( - QuantIndex.Luminance, - prevDCG, - ref pixelConverter.G, - ref quantTable); - - prevDCB = this.WriteBlock( - QuantIndex.Luminance, - prevDCB, - ref pixelConverter.B, - ref quantTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } - } - } - - this.FlushRemainingBytes(); - } - - /// - /// Writes a block of pixel data using the given quantization table, - /// returning the post-quantized DC value of the DCT-transformed block. - /// The block is in natural (not zig-zag) order. - /// - /// The quantization table index. - /// The previous DC value. - /// Source block. - /// Quantization table. - /// The . - private int WriteBlock( - QuantIndex index, - int prevDC, - ref Block8x8F block, - ref Block8x8F quant) - { - ref Block8x8 spectralBlock = ref this.tempBlock; - - // Shifting level from 0..255 to -128..127 - block.AddInPlace(-128f); - - // Discrete cosine transform - FastFloatingPointDCT.TransformFDCT(ref block); - - // Quantization - Block8x8F.Quantize(ref block, ref spectralBlock, ref quant); - - // Emit the DC delta. - int dc = spectralBlock[0]; - this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC); - - // Emit the AC components. - int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; - - nint lastValuableIndex = spectralBlock.GetLastNonZeroIndex(); - - int runLength = 0; - ref short blockRef = ref Unsafe.As(ref spectralBlock); - for (nint zig = 1; zig <= lastValuableIndex; zig++) - { - const int zeroRun1 = 1 << 4; - const int zeroRun16 = 16 << 4; - - int ac = Unsafe.Add(ref blockRef, zig); - if (ac == 0) - { - runLength += zeroRun1; - } - else - { - while (runLength >= zeroRun16) - { - this.EmitHuff(acHuffTable, 0xf0); - runLength -= zeroRun16; - } - - this.EmitHuffRLE(acHuffTable, runLength, ac); - runLength = 0; - } - } - - // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over - // this can be done for any number of trailing zeros, even when all 63 ac values are zero - // (Block8x8F.Size - 1) == 63 - last index of the mcu elements - if (lastValuableIndex != Block8x8F.Size - 1) - { - this.EmitHuff(acHuffTable, 0x00); - } - - return dc; - } - private void WriteBlock( JpegComponent component, ref Block8x8 block, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs deleted file mode 100644 index e87f2fc573..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> L8 -> Y conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct LuminanceForwardConverter - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 8 * 8; - - /// - /// The Y component - /// - public Block8x8F Y; - - /// - /// Temporal 64-pixel span to hold unconverted TPixel data. - /// - private readonly Span pixelSpan; - - /// - /// Temporal 64-byte span to hold converted data. - /// - private readonly Span l8Span; - - /// - /// Sampled pixel buffer size. - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations. - /// - private readonly Configuration config; - - public LuminanceForwardConverter(ImageFrame frame) - { - this.Y = default; - - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.l8Span = new L8[PixelsPerSample].AsSpan(); - - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(8, 8); - - /// - /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure () - /// - public void Convert(int x, int y, ref RowOctet currentRows) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToL8(this.config, this.pixelSpan, this.l8Span); - - ref Block8x8F yBlock = ref this.Y; - ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span); - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - ConvertAvx(ref l8Start, ref yBlock); - } - else - { - ConvertScalar(ref l8Start, ref yBlock); - } - } - - /// - /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics. - /// - /// Start of span of L8 pixels with size of 64 - /// 8x8 destination matrix of Luminance(Y) converted data - private static void ConvertAvx(ref L8 l8Start, ref Block8x8F yBlock) - { - Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - ref Vector128 l8ByteSpan = ref Unsafe.As>(ref l8Start); - ref Vector256 destRef = ref yBlock.V0; - - const int bytesPerL8Stride = 8; - for (nint i = 0; i < 8; i++) - { - Unsafe.Add(ref destRef, i) = Avx2.ConvertToVector256Single(Avx2.ConvertToVector256Int32(Unsafe.AddByteOffset(ref l8ByteSpan, bytesPerL8Stride * i))); - } -#endif - } - - /// - /// Converts 8x8 L8 pixel matrix to 8x8 Block of floats. - /// - /// Start of span of L8 pixels with size of 64 - /// 8x8 destination matrix of Luminance(Y) converted data - private static void ConvertScalar(ref L8 l8Start, ref Block8x8F yBlock) - { - for (int i = 0; i < Block8x8F.Size; i++) - { - ref L8 c = ref Unsafe.Add(ref l8Start, i); - yBlock[i] = c.PackedValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs deleted file mode 100644 index e2d12916c0..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks. - /// - /// The pixel type to work on. - internal ref struct RgbForwardConverter - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 8 * 8; - - /// - /// Total byte size of processed pixels converted from TPixel to - /// - private const int RgbSpanByteSize = PixelsPerSample * 3; - - /// - /// The Red component. - /// - public Block8x8F R; - - /// - /// The Green component. - /// - public Block8x8F G; - - /// - /// The Blue component. - /// - public Block8x8F B; - - /// - /// Temporal 64-byte span to hold unconverted TPixel data. - /// - private readonly Span pixelSpan; - - /// - /// Temporal 64-byte span to hold converted Rgb24 data. - /// - private readonly Span rgbSpan; - - /// - /// Sampled pixel buffer size. - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations. - /// - private readonly Configuration config; - - public RgbForwardConverter(ImageFrame frame) - { - this.R = default; - this.G = default; - this.B = default; - - // temporal pixel buffers - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); - - // frame data - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(8, 8); - - /// - /// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24. - /// - public void Convert(int x, int y, ref RowOctet currentRows) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); - - ref Block8x8F redBlock = ref this.R; - ref Block8x8F greenBlock = ref this.G; - ref Block8x8F blueBlock = ref this.B; - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - ConvertAvx(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); - } - else - { - ConvertScalar(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock); - } - } - - /// - /// Converts 8x8 RGB24 pixel matrix to 8x8 Block of floats using Avx2 Intrinsics. - /// - /// Span of Rgb24 pixels with size of 64 - /// 8x8 destination matrix of Red converted data - /// 8x8 destination matrix of Blue converted data - /// 8x8 destination matrix of Green converted data - private static void ConvertAvx(Span rgbSpan, ref Block8x8F rBlock, ref Block8x8F gBlock, ref Block8x8F bBlock) - { - Debug.Assert(RgbToYCbCrConverterVectorized.IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - ref Vector256 redRef = ref rBlock.V0; - ref Vector256 greenRef = ref gBlock.V0; - ref Vector256 blueRef = ref bBlock.V0; - var zero = Vector256.Create(0).AsByte(); - - var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.MoveFirst24BytesToSeparateLanes)); - var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(RgbToYCbCrConverterVectorized.ExtractRgb)); - Vector256 rgb, rg, bx; - - const int bytesPerRgbStride = 24; - for (nint i = 0; i < 8; i++) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, bytesPerRgbStride * i).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - Unsafe.Add(ref redRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - Unsafe.Add(ref greenRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - Unsafe.Add(ref blueRef, i) = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - } -#endif - } - - private static void ConvertScalar(Span rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock) - { - ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan); - - for (int i = 0; i < Block8x8F.Size; i++) - { - Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i); - - redBlock[i] = c.R; - greenBlock[i] = c.G; - blueBlock[i] = c.B; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs deleted file mode 100644 index 15574a32a2..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. - /// Methods to build the tables are based on libjpeg implementation. - /// - internal unsafe struct RgbToYCbCrConverterLut - { - /// - /// The red luminance table - /// - public fixed int YRTable[256]; - - /// - /// The green luminance table - /// - public fixed int YGTable[256]; - - /// - /// The blue luminance table - /// - public fixed int YBTable[256]; - - /// - /// The red blue-chrominance table - /// - public fixed int CbRTable[256]; - - /// - /// The green blue-chrominance table - /// - public fixed int CbGTable[256]; - - /// - /// The blue blue-chrominance table - /// B=>Cb and R=>Cr are the same - /// - public fixed int CbBTable[256]; - - /// - /// The green red-chrominance table - /// - public fixed int CrGTable[256]; - - /// - /// The blue red-chrominance table - /// - public fixed int CrBTable[256]; - - // Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places. - private const int ScaleBits = 16; - - private const int CBCrOffset = 128 << ScaleBits; - - private const int Half = 1 << (ScaleBits - 1); - - /// - /// Initializes the YCbCr tables - /// - /// The initialized - public static RgbToYCbCrConverterLut Create() - { - RgbToYCbCrConverterLut tables = default; - - for (int i = 0; i <= 255; i++) - { - // The values for the calculations are left scaled up since we must add them together before rounding. - tables.YRTable[i] = Fix(0.299F) * i; - tables.YGTable[i] = Fix(0.587F) * i; - tables.YBTable[i] = (Fix(0.114F) * i) + Half; - tables.CbRTable[i] = (-Fix(0.168735892F)) * i; - tables.CbGTable[i] = (-Fix(0.331264108F)) * i; - - // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr. - // This ensures that the maximum output will round to 255 - // not 256, and thus that we don't have to range-limit. - // - // B=>Cb and R=>Cr tables are the same - tables.CbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1; - - tables.CrGTable[i] = (-Fix(0.418687589F)) * i; - tables.CrBTable[i] = (-Fix(0.081312411F)) * i; - } - - return tables; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateY(byte r, byte g, byte b) - { - // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateCb(byte r, byte g, byte b) - { - // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateCr(byte r, byte g, byte b) - { - // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; - } - - /// - /// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma. - /// - /// Span of Rgb24 pixel data - /// Resulting Y values block - /// Resulting Cb values block - /// Resulting Cr values block - public void Convert444(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) - { - ref Rgb24 rgbStart = ref rgbSpan[0]; - - for (int i = 0; i < Block8x8F.Size; i++) - { - Rgb24 c = Unsafe.Add(ref rgbStart, i); - - yBlock[i] = this.CalculateY(c.R, c.G, c.B); - cbBlock[i] = this.CalculateCb(c.R, c.G, c.B); - crBlock[i] = this.CalculateCr(c.R, c.G, c.B); - } - } - - /// - /// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma. - /// - /// Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param. - /// Span of Rgb24 pixel data - /// First or "left" resulting Y block - /// Second or "right" resulting Y block - /// Resulting Cb values block - /// Resulting Cr values block - /// Row index of the 16x16 block, 0 or 1 - public void Convert420(Span rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row) - { - DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row)); - - ref float yBlockLeftRef = ref Unsafe.As(ref yBlockLeft); - ref float yBlockRightRef = ref Unsafe.As(ref yBlockRight); - - // 0-31 or 32-63 - // upper or lower part - int chromaWriteOffset = row * (Block8x8F.Size / 2); - ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As(ref cbBlock), chromaWriteOffset); - ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As(ref crBlock), chromaWriteOffset); - - ref Rgb24 rgbStart = ref rgbSpan[0]; - - for (int i = 0; i < 8; i += 2) - { - int yBlockWriteOffset = i * 8; - ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16); - - int chromaOffset = 8 * (i / 2); - - // left - this.ConvertChunk420( - ref stride, - ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset), - ref Unsafe.Add(ref cbBlockRef, chromaOffset), - ref Unsafe.Add(ref crBlockRef, chromaOffset)); - - // right - this.ConvertChunk420( - ref Unsafe.Add(ref stride, 8), - ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset), - ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4), - ref Unsafe.Add(ref crBlockRef, chromaOffset + 4)); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock) - { - // jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons) - // each row is 16 pixels wide thus +16 stride reference offset - // resulting luminance (Y`) are sampled at original resolution thus +8 reference offset - for (int k = 0; k < 8; k += 2) - { - ref float yBlockRef = ref Unsafe.Add(ref yBlock, k); - - // top row - Rgb24 px0 = Unsafe.Add(ref stride, k); - Rgb24 px1 = Unsafe.Add(ref stride, k + 1); - yBlockRef = this.CalculateY(px0.R, px0.G, px0.B); - Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B); - - // bottom row - Rgb24 px2 = Unsafe.Add(ref stride, k + 16); - Rgb24 px3 = Unsafe.Add(ref stride, k + 17); - Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B); - Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B); - - // chroma average for 2x2 pixel block - Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3); - Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) - { - return 0.25f - * (this.CalculateCb(px0.R, px0.G, px0.B) - + this.CalculateCb(px1.R, px1.G, px1.B) - + this.CalculateCb(px2.R, px2.G, px2.B) - + this.CalculateCb(px3.R, px3.G, px3.B)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3) - { - return 0.25f - * (this.CalculateCr(px0.R, px0.G, px0.B) - + this.CalculateCr(px1.R, px1.G, px1.B) - + this.CalculateCr(px2.R, px2.G, px2.B) - + this.CalculateCr(px3.R, px3.G, px3.B)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Fix(float x) - => (int)((x * (1L << ScaleBits)) + 0.5F); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs deleted file mode 100644 index d7542d7a59..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - internal static class RgbToYCbCrConverterVectorized - { - public static bool IsSupported - { - get - { -#if SUPPORTS_RUNTIME_INTRINSICS - return Avx2.IsSupported; -#else - return false; -#endif - } - } - - public static int AvxCompatibilityPadding - { - // rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total - // Strides are stored sequentially - one big span of 64 * 3 = 192 bytes - // Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits - // Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride: - // stride 0 0 - 192 -(+64bits)-> 256 - // stride 1 192 - 384 -(+64bits)-> 448 - // stride 2 384 - 576 -(+64bits)-> 640 - // stride 3 576 - 768 -(+64bits)-> 832 - // stride 4 768 - 960 -(+64bits)-> 1024 - // stride 5 960 - 1152 -(+64bits)-> 1216 - // stride 6 1152 - 1344 -(+64bits)-> 1408 - // stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION - // - // Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits - // This is not permitted - we are reading foreign memory - // - // 8 byte padding to rgb byte span will solve this problem without extra code in converters - get - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (IsSupported) - { - return 8; - } -#endif - return 0; - } - } - -#if SUPPORTS_RUNTIME_INTRINSICS - - internal static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] - { - 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, - 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 - }; - - internal static ReadOnlySpan ExtractRgb => new byte[] - { - 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, - 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF - }; -#endif - - /// - /// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling - /// - /// Total size of rgb span must be 200 bytes - /// Span of rgb pixels with size of 64 - /// 8x8 destination matrix of Luminance(Y) converted data - /// 8x8 destination matrix of Chrominance(Cb) converted data - /// 8x8 destination matrix of Chrominance(Cr) converted data - public static void Convert444(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) - { - Debug.Assert(IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); - var fn0168736 = Vector256.Create(-0.168736f); - var fn0331264 = Vector256.Create(-0.331264f); - var f128 = Vector256.Create(128f); - var fn0418688 = Vector256.Create(-0.418688f); - var fn0081312F = Vector256.Create(-0.081312F); - var f05 = Vector256.Create(0.5f); - var zero = Vector256.Create(0).AsByte(); - - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - ref Vector256 destYRef = ref yBlock.V0; - ref Vector256 destCbRef = ref cbBlock.V0; - ref Vector256 destCrRef = ref crBlock.V0; - - var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); - var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); - Vector256 rgb, rg, bx; - Vector256 r, g, b; - - const int bytesPerRgbStride = 24; - for (int i = 0; i < 8; i++) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - } -#endif - } - - /// - /// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling - /// - public static void Convert420(ReadOnlySpan rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row) - { - Debug.Assert(IsSupported, "AVX2 is required to run this converter"); - -#if SUPPORTS_RUNTIME_INTRINSICS - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); - var fn0168736 = Vector256.Create(-0.168736f); - var fn0331264 = Vector256.Create(-0.331264f); - var f128 = Vector256.Create(128f); - var fn0418688 = Vector256.Create(-0.418688f); - var fn0081312F = Vector256.Create(-0.081312F); - var f05 = Vector256.Create(0.5f); - var zero = Vector256.Create(0).AsByte(); - - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); - - int destOffset = row * 4; - - ref Vector256 destCbRef = ref Unsafe.Add(ref Unsafe.As>(ref cbBlock), destOffset); - ref Vector256 destCrRef = ref Unsafe.Add(ref Unsafe.As>(ref crBlock), destOffset); - - var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); - var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); - Vector256 rgb, rg, bx; - Vector256 r, g, b; - - Span> rDataLanes = stackalloc Vector256[4]; - Span> gDataLanes = stackalloc Vector256[4]; - Span> bDataLanes = stackalloc Vector256[4]; - - const int bytesPerRgbStride = 24; - for (int i = 0; i < 4; i++) - { - // 16x2 => 8x1 - // left 8x8 column conversions - for (int j = 0; j < 4; j += 2) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - - int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1); - - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - - rDataLanes[j] = r; - gDataLanes[j] = g; - bDataLanes[j] = b; - } - - // 16x2 => 8x1 - // right 8x8 column conversions - for (int j = 1; j < 4; j += 2) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte(); - - rgb = Avx2.Shuffle(rgb, extractRgbMask); - - rg = Avx2.UnpackLow(rgb, zero); - bx = Avx2.UnpackHigh(rgb, zero); - - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); - - int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1); - - // (0.299F * r) + (0.587F * g) + (0.114F * b); - Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - - rDataLanes[j] = r; - gDataLanes[j] = g; - bDataLanes[j] = b; - } - - r = Scale16x2_8x1(rDataLanes); - g = Scale16x2_8x1(gDataLanes); - b = Scale16x2_8x1(bDataLanes); - - // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) - Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - - // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) - Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - } -#endif - } - -#if SUPPORTS_RUNTIME_INTRINSICS - /// - /// Scales 16x2 matrix to 8x1 using 2x2 average - /// - /// Input matrix consisting of 4 256bit vectors - /// 256bit vector containing upper and lower scaled parts of the input matrix - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector256 Scale16x2_8x1(ReadOnlySpan> v) - { - Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter"); - DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements"); - - var f025 = Vector256.Create(0.25f); - - Vector256 left = Avx.Add(v[0], v[2]); - Vector256 right = Avx.Add(v[1], v[3]); - Vector256 avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025); - - return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle(); - } -#endif - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs deleted file mode 100644 index 3a878f3c63..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct YCbCrForwardConverter420 - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 16 * 8; - - /// - /// Total byte size of processed pixels converted from TPixel to - /// - private const int RgbSpanByteSize = PixelsPerSample * 3; - - /// - /// The left Y component - /// - public Block8x8F YLeft; - - /// - /// The left Y component - /// - public Block8x8F YRight; - - /// - /// The Cb component - /// - public Block8x8F Cb; - - /// - /// The Cr component - /// - public Block8x8F Cr; - - /// - /// The color conversion tables - /// - private RgbToYCbCrConverterLut colorTables; - - /// - /// Temporal 16x8 block to hold TPixel data - /// - private readonly Span pixelSpan; - - /// - /// Temporal RGB block - /// - private readonly Span rgbSpan; - - /// - /// Sampled pixel buffer size - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations - /// - private readonly Configuration config; - - public YCbCrForwardConverter420(ImageFrame frame) - { - // matrices would be filled during convert calls - this.YLeft = default; - this.YRight = default; - this.Cb = default; - this.Cr = default; - - // temporal pixel buffers - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); - - // frame data - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - - // conversion vector fallback data - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.colorTables = RgbToYCbCrConverterLut.Create(); - } - else - { - this.colorTables = default; - } - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(16, 8); - - public void Convert(int x, int y, ref RowOctet currentRows, int idx) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); - } - else - { - this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs deleted file mode 100644 index 5f7725bddb..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. - /// - /// The pixel type to work on - internal ref struct YCbCrForwardConverter444 - where TPixel : unmanaged, IPixel - { - /// - /// Number of pixels processed per single call - /// - private const int PixelsPerSample = 8 * 8; - - /// - /// Total byte size of processed pixels converted from TPixel to - /// - private const int RgbSpanByteSize = PixelsPerSample * 3; - - /// - /// The Y component - /// - public Block8x8F Y; - - /// - /// The Cb component - /// - public Block8x8F Cb; - - /// - /// The Cr component - /// - public Block8x8F Cr; - - /// - /// The color conversion tables - /// - private RgbToYCbCrConverterLut colorTables; - - /// - /// Temporal 64-byte span to hold unconverted TPixel data - /// - private readonly Span pixelSpan; - - /// - /// Temporal 64-byte span to hold converted Rgb24 data - /// - private readonly Span rgbSpan; - - /// - /// Sampled pixel buffer size - /// - private readonly Size samplingAreaSize; - - /// - /// for internal operations - /// - private readonly Configuration config; - - public YCbCrForwardConverter444(ImageFrame frame) - { - // matrices would be filled during convert calls - this.Y = default; - this.Cb = default; - this.Cr = default; - - // temporal pixel buffers - this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); - this.rgbSpan = MemoryMarshal.Cast(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan()); - - // frame data - this.samplingAreaSize = new Size(frame.Width, frame.Height); - this.config = frame.GetConfiguration(); - - // conversion vector fallback data - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.colorTables = RgbToYCbCrConverterLut.Create(); - } - else - { - this.colorTables = default; - } - } - - /// - /// Gets size of sampling area from given frame pixel buffer. - /// - private static Size SampleSize => new(8, 8); - - /// - /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) - /// - public void Convert(int x, int y, ref RowOctet currentRows) - { - YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - - PixelOperations.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan); - - ref Block8x8F yBlock = ref this.Y; - ref Block8x8F cbBlock = ref this.Cb; - ref Block8x8F crBlock = ref this.Cr; - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); - } - else - { - this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs deleted file mode 100644 index 6d3620c622..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - internal static class YCbCrForwardConverter - where TPixel : unmanaged, IPixel - { - public static void LoadAndStretchEdges(RowOctet source, Span dest, Point start, Size sampleSize, Size totalSize) - { - DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X)); - DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y)); - - int width = Math.Min(sampleSize.Width, totalSize.Width - start.X); - int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y); - - uint byteWidth = (uint)(width * Unsafe.SizeOf()); - int remainderXCount = sampleSize.Width - width; - - ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(dest)); - int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf(); - - for (int y = 0; y < height; y++) - { - Span row = source[y]; - - ref byte s = ref Unsafe.As(ref row[start.X]); - ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes); - - Unsafe.CopyBlock(ref d, ref s, byteWidth); - - ref TPixel last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); - - for (int x = 1; x <= remainderXCount; x++) - { - Unsafe.Add(ref last, x) = last; - } - } - - int remainderYCount = sampleSize.Height - height; - - if (remainderYCount == 0) - { - return; - } - - ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes); - - for (int y = 1; y <= remainderYCount; y++) - { - ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y); - Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index a8e4dc2a17..cbcaae2fc3 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -90,7 +90,7 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); - var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.EncodingColor)); + var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, this.frameConfig.ColorType); this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); this.outputStream = stream; @@ -142,27 +142,6 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteEndOfImageMarker(); stream.Flush(); - - static JpegColorSpace GetTargetColorSpace(JpegEncodingColor colorType) - { - switch (colorType) - { - case JpegEncodingColor.YCbCrRatio444: - case JpegEncodingColor.YCbCrRatio422: - case JpegEncodingColor.YCbCrRatio420: - case JpegEncodingColor.YCbCrRatio411: - case JpegEncodingColor.YCbCrRatio410: - return JpegColorSpace.YCbCr; - case JpegEncodingColor.Rgb: - return JpegColorSpace.RGB; - case JpegEncodingColor.Cmyk: - return JpegColorSpace.Cmyk; - case JpegEncodingColor.Luminance: - return JpegColorSpace.Grayscale; - default: - throw new NotImplementedException($"Unknown output color space: {colorType}"); - } - } } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs deleted file mode 100644 index 24a8195217..0000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -#endif -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; -using Xunit; -using Xunit.Abstractions; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - public class RgbToYCbCrConverterTests - { - public RgbToYCbCrConverterTests(ITestOutputHelper output) - { - this.Output = output; - } - - private ITestOutputHelper Output { get; } - - [Fact] - public void TestConverterLut444() - { - int dataSize = 8 * 8; - Rgb24[] data = CreateTestData(dataSize); - var target = RgbToYCbCrConverterLut.Create(); - - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - target.Convert444(data.AsSpan(), ref y, ref cb, ref cr); - - Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); - } - - [Fact] - public void TestConverterVectorized444() - { - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); - return; - } - - int dataSize = 8 * 8; - Rgb24[] data = CreateTestData(dataSize); - - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - RgbToYCbCrConverterVectorized.Convert444(data.AsSpan(), ref y, ref cb, ref cr); - - Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); - } - - [Fact] - public void TestConverterLut420() - { - int dataSize = 16 * 16; - Span data = CreateTestData(dataSize).AsSpan(); - var target = RgbToYCbCrConverterLut.Create(); - - var yBlocks = new Block8x8F[4]; - var cb = default(Block8x8F); - var cr = default(Block8x8F); - - target.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); - target.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); - - Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); - } - - [Fact] - public void TestConverterVectorized420() - { - if (!RgbToYCbCrConverterVectorized.IsSupported) - { - this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); - return; - } - - int dataSize = 16 * 16; - Span data = CreateTestData(dataSize).AsSpan(); - - var yBlocks = new Block8x8F[4]; - var cb = default(Block8x8F); - var cr = default(Block8x8F); - - RgbToYCbCrConverterVectorized.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0); - RgbToYCbCrConverterVectorized.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1); - - Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F)); - } - -#if SUPPORTS_RUNTIME_INTRINSICS - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void Scale16x2_8x1(int seed) - { - if (!Avx2.IsSupported) - { - return; - } - - Span data = new Random(seed).GenerateRandomFloatArray(Vector256.Count * 4, -1000, 1000); - - // Act: - Vector256 resultVector = RgbToYCbCrConverterVectorized.Scale16x2_8x1(MemoryMarshal.Cast>(data)); - ref float result = ref Unsafe.As, float>(ref resultVector); - - // Assert: - // Comparison epsilon is tricky but 10^(-4) is good enough (?) - var comparer = new ApproximateFloatComparer(0.0001f); - for (int i = 0; i < Vector256.Count; i++) - { - float actual = Unsafe.Add(ref result, i); - float expected = CalculateAverage16x2_8x1(data, i); - - Assert.True(comparer.Equals(actual, expected), $"Pos {i}, Expected: {expected}, Actual: {actual}"); - } - - static float CalculateAverage16x2_8x1(Span data, int index) - { - int upIdx = index * 2; - int lowIdx = (index + 8) * 2; - return 0.25f * (data[upIdx] + data[upIdx + 1] + data[lowIdx] + data[lowIdx + 1]); - } - } -#endif - - private static void Verify444( - ReadOnlySpan data, - ref Block8x8F yResult, - ref Block8x8F cbResult, - ref Block8x8F crResult, - ApproximateColorSpaceComparer comparer) - { - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - RgbToYCbCr(data, ref y, ref cb, ref cr); - - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.True(comparer.Equals(new YCbCr(y[i], cb[i], cr[i]), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y[i]} == {yResult[i]}, {cb[i]} == {cbResult[i]}, {cr[i]} == {crResult[i]}"); - } - } - - private static void Verify420( - ReadOnlySpan data, - Block8x8F[] yResult, - ref Block8x8F cbResult, - ref Block8x8F crResult, - ApproximateFloatComparer comparer) - { - var trueBlock = default(Block8x8F); - var cbTrue = new Block8x8F[4]; - var crTrue = new Block8x8F[4]; - - Span tempData = new Rgb24[8 * 8].AsSpan(); - - // top left - Copy8x8(data, tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[0], ref crTrue[0]); - VerifyBlock(ref yResult[0], ref trueBlock, comparer); - - // top right - Copy8x8(data.Slice(8), tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[1], ref crTrue[1]); - VerifyBlock(ref yResult[1], ref trueBlock, comparer); - - // bottom left - Copy8x8(data.Slice(8 * 16), tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[2], ref crTrue[2]); - VerifyBlock(ref yResult[2], ref trueBlock, comparer); - - // bottom right - Copy8x8(data.Slice((8 * 16) + 8), tempData); - RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[3], ref crTrue[3]); - VerifyBlock(ref yResult[3], ref trueBlock, comparer); - - // verify Cb - Scale16X16To8X8(ref trueBlock, cbTrue); - VerifyBlock(ref cbResult, ref trueBlock, comparer); - - // verify Cr - Scale16X16To8X8(ref trueBlock, crTrue); - VerifyBlock(ref crResult, ref trueBlock, comparer); - - // extracts 8x8 blocks from 16x8 memory region - static void Copy8x8(ReadOnlySpan source, Span dest) - { - for (int i = 0; i < 8; i++) - { - source.Slice(i * 16, 8).CopyTo(dest.Slice(i * 8)); - } - } - - // scales 16x16 to 8x8, used in chroma subsampling tests - static void Scale16X16To8X8(ref Block8x8F dest, ReadOnlySpan source) - { - for (int i = 0; i < 4; i++) - { - int dstOff = ((i & 2) << 4) | ((i & 1) << 2); - Block8x8F iSource = source[i]; - - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - int j = (16 * y) + (2 * x); - float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9]; - dest[(8 * y) + x + dstOff] = (sum + 2) * .25F; - } - } - } - } - } - - private static void RgbToYCbCr(ReadOnlySpan data, ref Block8x8F y, ref Block8x8F cb, ref Block8x8F cr) - { - for (int i = 0; i < data.Length; i++) - { - int r = data[i].R; - int g = data[i].G; - int b = data[i].B; - - y[i] = (0.299F * r) + (0.587F * g) + (0.114F * b); - cb[i] = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - cr[i] = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - } - } - - private static void VerifyBlock(ref Block8x8F res, ref Block8x8F target, ApproximateFloatComparer comparer) - { - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.True(comparer.Equals(res[i], target[i]), $"Pos {i}, Expected: {target[i]}, Got: {res[i]}"); - } - } - - private static Rgb24[] CreateTestData(int size) - { - var data = new Rgb24[size]; - var r = new Random(); - - var random = new byte[3]; - for (int i = 0; i < data.Length; i++) - { - r.NextBytes(random); - data[i] = new Rgb24(random[0], random[1], random[2]); - } - - return data; - } - } -} From 7ece3dd84a68b75281d748ed60bce5efa4a144c5 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 10 May 2022 17:11:53 +0300 Subject: [PATCH 16/39] Quality property fix --- .../Formats/Jpeg/JpegEncoderCore.cs | 20 +--------- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 39 ++++++++----------- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index cbcaae2fc3..4c44e04961 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -122,7 +122,7 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables); // Write the quantization tables. - this.InitQuantizationTables(this.scanConfig.QuantizationTables, jpegMetadata); + this.WriteDefineQuantizationTables(this.scanConfig.QuantizationTables, jpegMetadata); // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); @@ -188,22 +188,6 @@ private static bool IsSupportedColorType(JpegEncodingColor? colorType) || colorType == JpegEncodingColor.Luminance || colorType == JpegEncodingColor.Rgb; - /// - /// Writes data to "Define Quantization Tables" block for QuantIndex. - /// - /// The "Define Quantization Tables" block. - /// Offset in "Define Quantization Tables" block. - /// The quantization index. - /// The quantization table to copy data from. - private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant) - { - dqt[offset++] = (byte)i; - for (int j = 0; j < Block8x8F.Size; j++) - { - dqt[offset++] = (byte)quant[ZigZag.ZigZagOrder[j]]; - } - } - /// /// Write the start of image marker. /// @@ -707,7 +691,7 @@ private void WriteMarkerHeader(byte marker, int length) /// /// Quantization tables configs. /// Jpeg metadata instance. - private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) + private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) { int dataLen = configs.Length * (1 + Block8x8.Size); diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index bb4cbeeee9..6c34036793 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -67,41 +67,34 @@ internal int ChrominanceQuality } /// - /// Gets or sets the encoded quality. + /// Gets the encoded quality. /// /// /// Note that jpeg image can have different quality for luminance and chrominance components. - /// This property returns maximum value of luma/chroma qualities. + /// This property returns maximum value of luma/chroma qualities if both are present. /// public int Quality { get { - // Jpeg always has a luminance table thus it must have a luminance quality derived from it - if (!this.luminanceQuality.HasValue) + if (this.luminanceQuality.HasValue) { - return Quantization.DefaultQualityFactor; - } - - int lumaQuality = this.luminanceQuality.Value; + if (this.chrominanceQuality.HasValue) + { + return Math.Max(this.luminanceQuality.Value, this.chrominanceQuality.Value); + } - // Jpeg might not have a chrominance table - return luminance quality (grayscale images) - if (!this.chrominanceQuality.HasValue) - { - return lumaQuality; + return this.luminanceQuality.Value; } + else + { + if (this.chrominanceQuality.HasValue) + { + return this.chrominanceQuality.Value; + } - int chromaQuality = this.chrominanceQuality.Value; - - // Theoretically, luma quality would always be greater or equal to chroma quality - // But we've already encountered images which can have higher quality of chroma components - return Math.Max(lumaQuality, chromaQuality); - } - - set - { - this.LuminanceQuality = value; - this.ChrominanceQuality = value; + return Quantization.DefaultQualityFactor; + } } } From fe5e3be5860c0cfe2a0a2ede5b2eddd2de0806b9 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 10 May 2022 20:46:39 +0300 Subject: [PATCH 17/39] Fixed warnings, code cleanup --- .../Jpeg/Components/Block8x8.Intrinsic.cs | 2 +- .../Formats/Jpeg/Components/Block8x8.cs | 2 +- .../Jpeg/Components/Block8x8F.Generated.cs | 2 +- .../Jpeg/Components/Block8x8F.Intrinsic.cs | 2 +- .../Jpeg/Components/Block8x8F.ScaledCopy.cs | 2 +- .../Formats/Jpeg/Components/Block8x8F.cs | 2 +- .../Jpeg/Components/Encoder/JpegFrame.cs | 4 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 295 ++++++++------ .../Formats/Jpeg/JpegEncoderCore.cs | 31 +- .../ColorConversion/CmykColorConversion.cs | 2 +- .../GrayscaleColorConversion.cs | 2 +- .../ColorConversion/RgbColorConversion.cs | 2 +- .../ColorConversion/YCbCrColorConversion.cs | 2 +- .../YCbCrForwardConverterBenchmark.cs | 56 --- .../ColorConversion/YccKColorConverter.cs | 2 +- .../Color/RgbToYCbCr.LookupTables.cs | 240 ------------ .../ImageSharp.Benchmarks/Color/RgbToYCbCr.cs | 359 ------------------ .../Program.cs | 187 ++++++++- .../Formats/Jpg/JpegMetadataTests.cs | 4 +- 19 files changed, 380 insertions(+), 818 deletions(-) delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs delete mode 100644 tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs delete mode 100644 tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs index 0a6c099d02..002d382dc6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - public unsafe partial struct Block8x8 + internal unsafe partial struct Block8x8 { [FieldOffset(0)] public Vector128 V0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 3bcfbdfa59..e3b15b7535 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Explicit)] - public unsafe partial struct Block8x8 + internal unsafe partial struct Block8x8 { /// /// A number of scalar coefficients in a diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 8b40ff5c6f..181aa6d376 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -8,7 +8,7 @@ // namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - public partial struct Block8x8F + internal partial struct Block8x8F { /// /// Level shift by +maximum/2, clip to [0, maximum] diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs index dc86442624..0971ccdca0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - public partial struct Block8x8F + internal partial struct Block8x8F { /// /// A number of rows of 8 scalar coefficients each in diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index 1ba4dc6b0f..7edcd95da4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -10,7 +10,7 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - public partial struct Block8x8F + internal partial struct Block8x8F { [MethodImpl(InliningOptions.ShortMethod)] public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 3e8fddebf7..0190fc7454 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 8x8 matrix of coefficients. /// [StructLayout(LayoutKind.Explicit)] - public partial struct Block8x8F : IEquatable + internal partial struct Block8x8F : IEquatable { /// /// A number of scalar coefficients in a diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 675cdaca6b..c20e3d8635 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -25,8 +25,8 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i JpegComponentConfig componentConfig = componentConfigs[i]; this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) { - DcTableId = componentConfig.dcTableSelector, - AcTableId = componentConfig.acTableSelector, + DcTableId = componentConfig.DcTableSelector, + AcTableId = componentConfig.AcTableSelector, }; this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 926c49b571..5d0e964a1e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -24,6 +24,9 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions /// public int? Quality { get; set; } + /// + /// Sets jpeg color for encoding. + /// public JpegEncodingColor ColorType { set @@ -43,8 +46,6 @@ public JpegEncodingColor ColorType internal JpegFrameConfig FrameConfig { get; set; } - public JpegScanConfig ScanConfig { get; set; } - /// /// Encodes the image to the specified stream from the . /// @@ -54,7 +55,7 @@ public JpegEncodingColor ColorType public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.FrameConfig, this.ScanConfig); + var encoder = new JpegEncoderCore(this, this.FrameConfig); encoder.Encode(image, stream); } @@ -69,108 +70,172 @@ public void Encode(Image image, Stream stream) public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.FrameConfig, this.ScanConfig); + var encoder = new JpegEncoderCore(this, this.FrameConfig); return encoder.EncodeAsync(image, stream, cancellationToken); } - private static JpegFrameConfig[] CreateFrameConfigs() => new JpegFrameConfig[] + private static JpegFrameConfig[] CreateFrameConfigs() { - // YCbCr 4:4:4 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio444, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }), - - // YCbCr 4:2:2 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio422, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }), - - // YCbCr 4:2:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio420, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }), - - // YCbCr 4:1:1 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio411, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }), - - // YCbCr 4:1:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio410, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }), - - // Luminance - new JpegFrameConfig( - JpegColorSpace.Grayscale, - JpegEncodingColor.Luminance, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }), - - // Rgb - new JpegFrameConfig( - JpegColorSpace.RGB, - JpegEncodingColor.Rgb, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }), - - // Cmyk - new JpegFrameConfig( - JpegColorSpace.Cmyk, - JpegEncodingColor.Cmyk, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }), - }; + var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[0]); + var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[1]); + var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[2]); + var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[3]); + + var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Block8x8.Load(Quantization.LuminanceTable)); + var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Block8x8.Load(Quantization.ChrominanceTable)); + + var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC, + defaultChrominanceHuffmanDC, + defaultChrominanceHuffmanAC, + }; + + var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable, + defaultChrominanceQuantTable, + }; + + return new JpegFrameConfig[] + { + // YCbCr 4:4:4 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio444, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:2 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio422, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio420, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:1 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio411, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio410, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // Luminance + new JpegFrameConfig( + JpegColorSpace.Grayscale, + JpegEncodingColor.Luminance, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + + // Rgb + new JpegFrameConfig( + JpegColorSpace.RGB, + JpegEncodingColor.Rgb, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + + // Cmyk + new JpegFrameConfig( + JpegColorSpace.Cmyk, + JpegEncodingColor.Cmyk, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + }; + } } internal class JpegFrameConfig { - public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components) + public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) { this.ColorType = colorType; this.EncodingColor = encodingColor; this.Components = components; + this.HuffmanTables = huffmanTables; + this.QuantizationTables = quantTables; this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; @@ -188,6 +253,10 @@ public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor public JpegComponentConfig[] Components { get; } + public JpegHuffmanTableConfig[] HuffmanTables { get; } + + public JpegQuantizationTableConfig[] QuantizationTables { get; } + public int MaxHorizontalSamplingFactor { get; } public int MaxVerticalSamplingFactor { get; } @@ -201,8 +270,8 @@ public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcInde this.HorizontalSampleFactor = hsf; this.VerticalSampleFactor = vsf; this.QuantizatioTableIndex = quantIndex; - this.dcTableSelector = dcIndex; - this.acTableSelector = acIndex; + this.DcTableSelector = dcIndex; + this.AcTableSelector = acIndex; } public byte Id { get; } @@ -213,31 +282,37 @@ public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcInde public int QuantizatioTableIndex { get; } - public int dcTableSelector { get; } + public int DcTableSelector { get; } - public int acTableSelector { get; } + public int AcTableSelector { get; } } - public class JpegHuffmanTableConfig + internal class JpegHuffmanTableConfig { - public int Class { get; set; } - - public int DestinationIndex { get; set; } + public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) + { + this.Class = @class; + this.DestinationIndex = destIndex; + this.Table = table; + } - public HuffmanSpec Table { get; set; } - } + public int Class { get; } - public class JpegQuantizationTableConfig - { - public int DestinationIndex { get; set; } + public int DestinationIndex { get; } - public Block8x8 Table { get; set; } + public HuffmanSpec Table { get; } } - public class JpegScanConfig + internal class JpegQuantizationTableConfig { - public JpegHuffmanTableConfig[] HuffmanTables { get; set; } + public JpegQuantizationTableConfig(int destIndex, Block8x8 table) + { + this.DestinationIndex = destIndex; + this.Table = table; + } + + public int DestinationIndex { get; } - public JpegQuantizationTableConfig[] QuantizationTables { get; set; } + public Block8x8 Table { get; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4c44e04961..ccdac68f18 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -45,12 +45,8 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals private JpegFrameConfig frameConfig; - private JpegScanConfig scanConfig; - private HuffmanScanEncoder scanEncoder; - public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; - /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -60,16 +56,17 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// Initializes a new instance of the class. /// /// The options. - public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig, JpegScanConfig scanConfig) + /// Frame config. + public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) { this.quality = options.Quality; this.frameConfig = frameConfig; this.colorType = frameConfig.EncodingColor; - - this.scanConfig = scanConfig; } + public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; + /// /// Encode writes the image to the jpeg baseline format with the given options. /// @@ -119,10 +116,10 @@ public void Encode(Image image, Stream stream, CancellationToken this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); // Write the Huffman tables. - this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables); + this.WriteDefineHuffmanTables(this.frameConfig.HuffmanTables); // Write the quantization tables. - this.WriteDefineQuantizationTables(this.scanConfig.QuantizationTables, jpegMetadata); + this.WriteDefineQuantizationTables(this.frameConfig.QuantizationTables, jpegMetadata); // Write the scan header. this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); @@ -148,7 +145,7 @@ public void Encode(Image image, Stream stream, CancellationToken /// 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 defering the field assignment - /// to . + /// to . /// private static JpegEncodingColor? GetFallbackColorType(Image image) where TPixel : unmanaged, IPixel @@ -248,9 +245,13 @@ private void WriteJfifApplicationHeader(ImageMetadata meta) /// /// Writes the Define Huffman Table marker and tables. /// - /// The number of components to write. private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs) { + if (tableConfigs is null) + { + throw new ArgumentNullException(nameof(tableConfigs)); + } + int markerlen = 2; for (int i = 0; i < tableConfigs.Length; i++) @@ -570,10 +571,6 @@ private void WriteProfiles(ImageMetadata metadata) /// /// Writes the Start Of Frame (Baseline) marker. /// - /// The width of the image. - /// The height of the image. - /// The number of components in a pixel. - /// The component Id's. private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) { JpegComponentConfig[] components = frame.Components; @@ -612,8 +609,6 @@ private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) /// /// Writes the StartOfScan marker. /// - /// The number of components in a pixel. - /// The componentId's. private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components) { // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: @@ -643,7 +638,7 @@ private void WriteStartOfScan(int componentCount, JpegComponentConfig[] componen this.buffer[i2 + 5] = components[i].Id; // Table selectors - int tableSelectors = (components[i].dcTableSelector << 4) | (components[i].acTableSelector); + int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector; this.buffer[i2 + 6] = (byte)tableSelectors; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 0f791ed8ea..11125357ca 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 2fdb47077d..48e6332eed 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 987a931948..438626b4b6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 8d68460334..6f65f1b853 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs deleted file mode 100644 index 9aafb6936b..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder -{ - public class YCbCrForwardConverterBenchmark - { - private RgbToYCbCrConverterLut converter; - private Rgb24[] data; - - [GlobalSetup] - public void Setup() - { - this.converter = RgbToYCbCrConverterLut.Create(); - - var r = new Random(42); - this.data = new Rgb24[64]; - - var d = new byte[3]; - for (int i = 0; i < this.data.Length; i++) - { - r.NextBytes(d); - this.data[i] = new Rgb24(d[0], d[1], d[2]); - } - } - - [Benchmark(Baseline = true)] - public void ConvertLut() - { - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - this.converter.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr); - } - - [Benchmark] - public void ConvertVectorized() - { - Block8x8F y = default; - Block8x8F cb = default; - Block8x8F cr = default; - - if (RgbToYCbCrConverterVectorized.IsSupported) - { - RgbToYCbCrConverterVectorized.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr); - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index 7e9edc918e..d03fa5e83e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Formats.Jpeg.Components; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs deleted file mode 100644 index 4b046b3c4f..0000000000 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Benchmarks -{ - public partial class RgbToYCbCr - { - /// - /// Scaled integer RGBA to YCbCr lookup tables - /// - private static class LookupTables - { - public static readonly int[] Y0 = - { - 0, 306, 612, 918, 1224, 1530, 1836, 2142, 2448, 2754, 3060, 3366, 3672, 3978, 4284, - 4590, 4896, 5202, 5508, 5814, 6120, 6426, 6732, 7038, 7344, 7650, 7956, 8262, 8568, - 8874, 9180, 9486, 9792, 10098, 10404, 10710, 11016, 11322, 11628, 11934, 12240, - 12546, 12852, 13158, 13464, 13770, 14076, 14382, 14688, 14994, 15300, 15606, 15912, - 16218, 16524, 16830, 17136, 17442, 17748, 18054, 18360, 18666, 18972, 19278, 19584, - 19890, 20196, 20502, 20808, 21114, 21420, 21726, 22032, 22338, 22644, 22950, 23256, - 23562, 23868, 24174, 24480, 24786, 25092, 25398, 25704, 26010, 26316, 26622, 26928, - 27234, 27540, 27846, 28152, 28458, 28764, 29070, 29376, 29682, 29988, 30294, 30600, - 30906, 31212, 31518, 31824, 32130, 32436, 32742, 33048, 33354, 33660, 33966, 34272, - 34578, 34884, 35190, 35496, 35802, 36108, 36414, 36720, 37026, 37332, 37638, 37944, - 38250, 38556, 38862, 39168, 39474, 39780, 40086, 40392, 40698, 41004, 41310, 41616, - 41922, 42228, 42534, 42840, 43146, 43452, 43758, 44064, 44370, 44676, 44982, 45288, - 45594, 45900, 46206, 46512, 46818, 47124, 47430, 47736, 48042, 48348, 48654, 48960, - 49266, 49572, 49878, 50184, 50490, 50796, 51102, 51408, 51714, 52020, 52326, 52632, - 52938, 53244, 53550, 53856, 54162, 54468, 54774, 55080, 55386, 55692, 55998, 56304, - 56610, 56916, 57222, 57528, 57834, 58140, 58446, 58752, 59058, 59364, 59670, 59976, - 60282, 60588, 60894, 61200, 61506, 61812, 62118, 62424, 62730, 63036, 63342, 63648, - 63954, 64260, 64566, 64872, 65178, 65484, 65790, 66096, 66402, 66708, 67014, 67320, - 67626, 67932, 68238, 68544, 68850, 69156, 69462, 69768, 70074, 70380, 70686, 70992, - 71298, 71604, 71910, 72216, 72522, 72828, 73134, 73440, 73746, 74052, 74358, 74664, - 74970, 75276, 75582, 75888, 76194, 76500, 76806, 77112, 77418, 77724, 78030 - }; - - public static readonly int[] Y1 = - { - 0, 601, 1202, 1803, 2404, 3005, 3606, 4207, 4808, 5409, 6010, 6611, 7212, 7813, 8414, - 9015, 9616, 10217, 10818, 11419, 12020, 12621, 13222, 13823, 14424, 15025, 15626, - 16227, 16828, 17429, 18030, 18631, 19232, 19833, 20434, 21035, 21636, 22237, 22838, - 23439, 24040, 24641, 25242, 25843, 26444, 27045, 27646, 28247, 28848, 29449, 30050, - 30651, 31252, 31853, 32454, 33055, 33656, 34257, 34858, 35459, 36060, 36661, 37262, - 37863, 38464, 39065, 39666, 40267, 40868, 41469, 42070, 42671, 43272, 43873, 44474, - 45075, 45676, 46277, 46878, 47479, 48080, 48681, 49282, 49883, 50484, 51085, 51686, - 52287, 52888, 53489, 54090, 54691, 55292, 55893, 56494, 57095, 57696, 58297, 58898, - 59499, 60100, 60701, 61302, 61903, 62504, 63105, 63706, 64307, 64908, 65509, 66110, - 66711, 67312, 67913, 68514, 69115, 69716, 70317, 70918, 71519, 72120, 72721, 73322, - 73923, 74524, 75125, 75726, 76327, 76928, 77529, 78130, 78731, 79332, 79933, 80534, - 81135, 81736, 82337, 82938, 83539, 84140, 84741, 85342, 85943, 86544, 87145, 87746, - 88347, 88948, 89549, 90150, 90751, 91352, 91953, 92554, 93155, 93756, 94357, 94958, - 95559, 96160, 96761, 97362, 97963, 98564, 99165, 99766, 100367, 100968, 101569, - 102170, 102771, 103372, 103973, 104574, 105175, 105776, 106377, 106978, 107579, - 108180, 108781, 109382, 109983, 110584, 111185, 111786, 112387, 112988, 113589, - 114190, 114791, 115392, 115993, 116594, 117195, 117796, 118397, 118998, 119599, - 120200, 120801, 121402, 122003, 122604, 123205, 123806, 124407, 125008, 125609, - 126210, 126811, 127412, 128013, 128614, 129215, 129816, 130417, 131018, 131619, - 132220, 132821, 133422, 134023, 134624, 135225, 135826, 136427, 137028, 137629, - 138230, 138831, 139432, 140033, 140634, 141235, 141836, 142437, 143038, 143639, - 144240, 144841, 145442, 146043, 146644, 147245, 147846, 148447, 149048, 149649, - 150250, 150851, 151452, 152053, 152654, 153255 - }; - - public static readonly int[] Y2 = - { - 0, 117, 234, 351, 468, 585, 702, 819, 936, 1053, 1170, 1287, 1404, 1521, 1638, 1755, - 1872, 1989, 2106, 2223, 2340, 2457, 2574, 2691, 2808, 2925, 3042, 3159, 3276, 3393, - 3510, 3627, 3744, 3861, 3978, 4095, 4212, 4329, 4446, 4563, 4680, 4797, 4914, 5031, - 5148, 5265, 5382, 5499, 5616, 5733, 5850, 5967, 6084, 6201, 6318, 6435, 6552, 6669, - 6786, 6903, 7020, 7137, 7254, 7371, 7488, 7605, 7722, 7839, 7956, 8073, 8190, 8307, - 8424, 8541, 8658, 8775, 8892, 9009, 9126, 9243, 9360, 9477, 9594, 9711, 9828, 9945, - 10062, 10179, 10296, 10413, 10530, 10647, 10764, 10881, 10998, 11115, 11232, 11349, - 11466, 11583, 11700, 11817, 11934, 12051, 12168, 12285, 12402, 12519, 12636, 12753, - 12870, 12987, 13104, 13221, 13338, 13455, 13572, 13689, 13806, 13923, 14040, 14157, - 14274, 14391, 14508, 14625, 14742, 14859, 14976, 15093, 15210, 15327, 15444, 15561, - 15678, 15795, 15912, 16029, 16146, 16263, 16380, 16497, 16614, 16731, 16848, 16965, - 17082, 17199, 17316, 17433, 17550, 17667, 17784, 17901, 18018, 18135, 18252, 18369, - 18486, 18603, 18720, 18837, 18954, 19071, 19188, 19305, 19422, 19539, 19656, 19773, - 19890, 20007, 20124, 20241, 20358, 20475, 20592, 20709, 20826, 20943, 21060, 21177, - 21294, 21411, 21528, 21645, 21762, 21879, 21996, 22113, 22230, 22347, 22464, 22581, - 22698, 22815, 22932, 23049, 23166, 23283, 23400, 23517, 23634, 23751, 23868, 23985, - 24102, 24219, 24336, 24453, 24570, 24687, 24804, 24921, 25038, 25155, 25272, 25389, - 25506, 25623, 25740, 25857, 25974, 26091, 26208, 26325, 26442, 26559, 26676, 26793, - 26910, 27027, 27144, 27261, 27378, 27495, 27612, 27729, 27846, 27963, 28080, 28197, - 28314, 28431, 28548, 28665, 28782, 28899, 29016, 29133, 29250, 29367, 29484, 29601, - 29718, 29835 - }; - - public static readonly int[] Cb0 = - { - 0, -172, -344, -516, -688, -860, -1032, -1204, -1376, -1548, -1720, -1892, - -2064, -2236, -2408, -2580, -2752, -2924, -3096, -3268, -3440, -3612, - -3784, -3956, -4128, -4300, -4472, -4644, -4816, -4988, -5160, -5332, - -5504, -5676, -5848, -6020, -6192, -6364, -6536, -6708, -6880, -7052, - -7224, -7396, -7568, -7740, -7912, -8084, -8256, -8428, -8600, -8772, - -8944, -9116, -9288, -9460, -9632, -9804, -9976, -10148, -10320, -10492, - -10664, -10836, -11008, -11180, -11352, -11524, -11696, -11868, -12040, - -12212, -12384, -12556, -12728, -12900, -13072, -13244, -13416, -13588, - -13760, -13932, -14104, -14276, -14448, -14620, -14792, -14964, -15136, - -15308, -15480, -15652, -15824, -15996, -16168, -16340, -16512, -16684, - -16856, -17028, -17200, -17372, -17544, -17716, -17888, -18060, -18232, - -18404, -18576, -18748, -18920, -19092, -19264, -19436, -19608, -19780, - -19952, -20124, -20296, -20468, -20640, -20812, -20984, -21156, -21328, - -21500, -21672, -21844, -22016, -22188, -22360, -22532, -22704, -22876, - -23048, -23220, -23392, -23564, -23736, -23908, -24080, -24252, -24424, - -24596, -24768, -24940, -25112, -25284, -25456, -25628, -25800, -25972, - -26144, -26316, -26488, -26660, -26832, -27004, -27176, -27348, -27520, - -27692, -27864, -28036, -28208, -28380, -28552, -28724, -28896, -29068, - -29240, -29412, -29584, -29756, -29928, -30100, -30272, -30444, -30616, - -30788, -30960, -31132, -31304, -31476, -31648, -31820, -31992, -32164, - -32336, -32508, -32680, -32852, -33024, -33196, -33368, -33540, -33712, - -33884, -34056, -34228, -34400, -34572, -34744, -34916, -35088, -35260, - -35432, -35604, -35776, -35948, -36120, -36292, -36464, -36636, -36808, - -36980, -37152, -37324, -37496, -37668, -37840, -38012, -38184, -38356, - -38528, -38700, -38872, -39044, -39216, -39388, -39560, -39732, -39904, - -40076, -40248, -40420, -40592, -40764, -40936, -41108, -41280, -41452, - -41624, -41796, -41968, -42140, -42312, -42484, -42656, -42828, -43000, - -43172, -43344, -43516, -43688, -43860 - }; - - public static readonly int[] Cb1 = - { - 0, 339, 678, 1017, 1356, 1695, 2034, 2373, 2712, 3051, 3390, 3729, 4068, - 4407, 4746, 5085, 5424, 5763, 6102, 6441, 6780, 7119, 7458, 7797, 8136, - 8475, 8814, 9153, 9492, 9831, 10170, 10509, 10848, 11187, 11526, 11865, - 12204, 12543, 12882, 13221, 13560, 13899, 14238, 14577, 14916, 15255, - 15594, 15933, 16272, 16611, 16950, 17289, 17628, 17967, 18306, 18645, - 18984, 19323, 19662, 20001, 20340, 20679, 21018, 21357, 21696, 22035, - 22374, 22713, 23052, 23391, 23730, 24069, 24408, 24747, 25086, 25425, - 25764, 26103, 26442, 26781, 27120, 27459, 27798, 28137, 28476, 28815, - 29154, 29493, 29832, 30171, 30510, 30849, 31188, 31527, 31866, 32205, - 32544, 32883, 33222, 33561, 33900, 34239, 34578, 34917, 35256, 35595, - 35934, 36273, 36612, 36951, 37290, 37629, 37968, 38307, 38646, 38985, - 39324, 39663, 40002, 40341, 40680, 41019, 41358, 41697, 42036, 42375, - 42714, 43053, 43392, 43731, 44070, 44409, 44748, 45087, 45426, 45765, - 46104, 46443, 46782, 47121, 47460, 47799, 48138, 48477, 48816, 49155, - 49494, 49833, 50172, 50511, 50850, 51189, 51528, 51867, 52206, 52545, - 52884, 53223, 53562, 53901, 54240, 54579, 54918, 55257, 55596, 55935, - 56274, 56613, 56952, 57291, 57630, 57969, 58308, 58647, 58986, 59325, - 59664, 60003, 60342, 60681, 61020, 61359, 61698, 62037, 62376, 62715, - 63054, 63393, 63732, 64071, 64410, 64749, 65088, 65427, 65766, 66105, - 66444, 66783, 67122, 67461, 67800, 68139, 68478, 68817, 69156, 69495, - 69834, 70173, 70512, 70851, 71190, 71529, 71868, 72207, 72546, 72885, - 73224, 73563, 73902, 74241, 74580, 74919, 75258, 75597, 75936, 76275, - 76614, 76953, 77292, 77631, 77970, 78309, 78648, 78987, 79326, 79665, - 80004, 80343, 80682, 81021, 81360, 81699, 82038, 82377, 82716, 83055, - 83394, 83733, 84072, 84411, 84750, 85089, 85428, 85767, 86106, 86445 - }; - - public static readonly int[] Cb2Cr0 = - { - 0, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4096, 4608, 5120, 5632, 6144, - 6656, 7168, 7680, 8192, 8704, 9216, 9728, 10240, 10752, 11264, 11776, - 12288, 12800, 13312, 13824, 14336, 14848, 15360, 15872, 16384, 16896, - 17408, 17920, 18432, 18944, 19456, 19968, 20480, 20992, 21504, 22016, - 22528, 23040, 23552, 24064, 24576, 25088, 25600, 26112, 26624, 27136, - 27648, 28160, 28672, 29184, 29696, 30208, 30720, 31232, 31744, 32256, - 32768, 33280, 33792, 34304, 34816, 35328, 35840, 36352, 36864, 37376, - 37888, 38400, 38912, 39424, 39936, 40448, 40960, 41472, 41984, 42496, - 43008, 43520, 44032, 44544, 45056, 45568, 46080, 46592, 47104, 47616, - 48128, 48640, 49152, 49664, 50176, 50688, 51200, 51712, 52224, 52736, - 53248, 53760, 54272, 54784, 55296, 55808, 56320, 56832, 57344, 57856, - 58368, 58880, 59392, 59904, 60416, 60928, 61440, 61952, 62464, 62976, - 63488, 64000, 64512, 65024, 65536, 66048, 66560, 67072, 67584, 68096, - 68608, 69120, 69632, 70144, 70656, 71168, 71680, 72192, 72704, 73216, - 73728, 74240, 74752, 75264, 75776, 76288, 76800, 77312, 77824, 78336, - 78848, 79360, 79872, 80384, 80896, 81408, 81920, 82432, 82944, 83456, - 83968, 84480, 84992, 85504, 86016, 86528, 87040, 87552, 88064, 88576, - 89088, 89600, 90112, 90624, 91136, 91648, 92160, 92672, 93184, 93696, - 94208, 94720, 95232, 95744, 96256, 96768, 97280, 97792, 98304, 98816, - 99328, 99840, 100352, 100864, 101376, 101888, 102400, 102912, 103424, - 103936, 104448, 104960, 105472, 105984, 106496, 107008, 107520, 108032, - 108544, 109056, 109568, 110080, 110592, 111104, 111616, 112128, 112640, - 113152, 113664, 114176, 114688, 115200, 115712, 116224, 116736, 117248, - 117760, 118272, 118784, 119296, 119808, 120320, 120832, 121344, 121856, - 122368, 122880, 123392, 123904, 124416, 124928, 125440, 125952, 126464, - 126976, 127488, 128000, 128512, 129024, 129536, 130048, 130560 - }; - - public static readonly int[] Cr1 = - { - 0, 429, 858, 1287, 1716, 2145, 2574, 3003, 3432, 3861, 4290, 4719, 5148, - 5577, 6006, 6435, 6864, 7293, 7722, 8151, 8580, 9009, 9438, 9867, 10296, - 10725, 11154, 11583, 12012, 12441, 12870, 13299, 13728, 14157, 14586, - 15015, 15444, 15873, 16302, 16731, 17160, 17589, 18018, 18447, 18876, - 19305, 19734, 20163, 20592, 21021, 21450, 21879, 22308, 22737, 23166, - 23595, 24024, 24453, 24882, 25311, 25740, 26169, 26598, 27027, 27456, - 27885, 28314, 28743, 29172, 29601, 30030, 30459, 30888, 31317, 31746, - 32175, 32604, 33033, 33462, 33891, 34320, 34749, 35178, 35607, 36036, - 36465, 36894, 37323, 37752, 38181, 38610, 39039, 39468, 39897, 40326, - 40755, 41184, 41613, 42042, 42471, 42900, 43329, 43758, 44187, 44616, - 45045, 45474, 45903, 46332, 46761, 47190, 47619, 48048, 48477, 48906, - 49335, 49764, 50193, 50622, 51051, 51480, 51909, 52338, 52767, 53196, - 53625, 54054, 54483, 54912, 55341, 55770, 56199, 56628, 57057, 57486, - 57915, 58344, 58773, 59202, 59631, 60060, 60489, 60918, 61347, 61776, - 62205, 62634, 63063, 63492, 63921, 64350, 64779, 65208, 65637, 66066, - 66495, 66924, 67353, 67782, 68211, 68640, 69069, 69498, 69927, 70356, - 70785, 71214, 71643, 72072, 72501, 72930, 73359, 73788, 74217, 74646, - 75075, 75504, 75933, 76362, 76791, 77220, 77649, 78078, 78507, 78936, - 79365, 79794, 80223, 80652, 81081, 81510, 81939, 82368, 82797, 83226, - 83655, 84084, 84513, 84942, 85371, 85800, 86229, 86658, 87087, 87516, - 87945, 88374, 88803, 89232, 89661, 90090, 90519, 90948, 91377, 91806, - 92235, 92664, 93093, 93522, 93951, 94380, 94809, 95238, 95667, 96096, - 96525, 96954, 97383, 97812, 98241, 98670, 99099, 99528, 99957, 100386, - 100815, 101244, 101673, 102102, 102531, 102960, 103389, 103818, 104247, - 104676, 105105, 105534, 105963, 106392, 106821, 107250, 107679, 108108, - 108537, 108966, 109395 - }; - - public static readonly int[] Cr2 = - { - 0, 83, 166, 249, 332, 415, 498, 581, 664, 747, 830, 913, 996, 1079, 1162, - 1245, 1328, 1411, 1494, 1577, 1660, 1743, 1826, 1909, 1992, 2075, 2158, - 2241, 2324, 2407, 2490, 2573, 2656, 2739, 2822, 2905, 2988, 3071, 3154, - 3237, 3320, 3403, 3486, 3569, 3652, 3735, 3818, 3901, 3984, 4067, 4150, - 4233, 4316, 4399, 4482, 4565, 4648, 4731, 4814, 4897, 4980, 5063, 5146, - 5229, 5312, 5395, 5478, 5561, 5644, 5727, 5810, 5893, 5976, 6059, 6142, - 6225, 6308, 6391, 6474, 6557, 6640, 6723, 6806, 6889, 6972, 7055, 7138, - 7221, 7304, 7387, 7470, 7553, 7636, 7719, 7802, 7885, 7968, 8051, 8134, - 8217, 8300, 8383, 8466, 8549, 8632, 8715, 8798, 8881, 8964, 9047, 9130, - 9213, 9296, 9379, 9462, 9545, 9628, 9711, 9794, 9877, 9960, 10043, 10126, - 10209, 10292, 10375, 10458, 10541, 10624, 10707, 10790, 10873, 10956, - 11039, 11122, 11205, 11288, 11371, 11454, 11537, 11620, 11703, 11786, - 11869, 11952, 12035, 12118, 12201, 12284, 12367, 12450, 12533, 12616, - 12699, 12782, 12865, 12948, 13031, 13114, 13197, 13280, 13363, 13446, - 13529, 13612, 13695, 13778, 13861, 13944, 14027, 14110, 14193, 14276, - 14359, 14442, 14525, 14608, 14691, 14774, 14857, 14940, 15023, 15106, - 15189, 15272, 15355, 15438, 15521, 15604, 15687, 15770, 15853, 15936, - 16019, 16102, 16185, 16268, 16351, 16434, 16517, 16600, 16683, 16766, - 16849, 16932, 17015, 17098, 17181, 17264, 17347, 17430, 17513, 17596, - 17679, 17762, 17845, 17928, 18011, 18094, 18177, 18260, 18343, 18426, - 18509, 18592, 18675, 18758, 18841, 18924, 19007, 19090, 19173, 19256, - 19339, 19422, 19505, 19588, 19671, 19754, 19837, 19920, 20003, 20086, - 20169, 20252, 20335, 20418, 20501, 20584, 20667, 20750, 20833, 20916, - 20999, 21082, 21165 - }; - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs deleted file mode 100644 index d231b706ba..0000000000 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks -{ - public partial class RgbToYCbCr - { - private const int InputColorCount = 64; - - private const int InputByteCount = InputColorCount * 3; - - private static readonly Vector3 VectorY = new Vector3(0.299F, 0.587F, 0.114F); - - private static readonly Vector3 VectorCb = new Vector3(-0.168736F, 0.331264F, 0.5F); - - private static readonly Vector3 VectorCr = new Vector3(0.5F, 0.418688F, 0.081312F); - - private static class ScaledCoeffs - { - public static readonly int[] Y = - { - 306, 601, 117, 0, - 306, 601, 117, 0, - }; - - public static readonly int[] Cb = - { - -172, 339, 512, 0, - -172, 339, 512, 0, - }; - - public static readonly int[] Cr = - { - 512, 429, 83, 0, - 512, 429, 83, 0, - }; - - public static class SelectLeft - { - public static readonly int[] Y = - { - 1, 1, 1, 0, - 0, 0, 0, 0, - }; - - public static readonly int[] Cb = - { - 1, -1, 1, 0, - 0, 0, 0, 0, - }; - - public static readonly int[] Cr = - { - 1, -1, -1, 0, - 0, 0, 0, 0, - }; - } - - public static class SelectRight - { - public static readonly int[] Y = - { - 0, 0, 0, 0, - 1, 1, 1, 0, - }; - - public static readonly int[] Cb = - { - 0, 0, 0, 0, - 1, -1, 1, 0, - }; - - public static readonly int[] Cr = - { - 0, 0, 0, 0, - 1, -1, -1, 0, - }; - } - } - - private static class OnStackInputCache - { - public unsafe struct Byte - { - public fixed byte Data[InputByteCount * 3]; - - public static Byte Create(byte[] data) - { - Byte result = default; - for (int i = 0; i < data.Length; i++) - { - result.Data[i] = data[i]; - } - - return result; - } - } - } - - public struct Result - { - internal Block8x8F Y; - internal Block8x8F Cb; - internal Block8x8F Cr; - } - - // The operation is defined as "RGBA -> YCbCr Transform a stream of bytes into a stream of floats" - // We need to benchmark the whole operation, to get true results, not missing any side effects! - private byte[] inputSourceRGB; - - private int[] inputSourceRGBAsInteger; - - [GlobalSetup] - public void Setup() - { - // Console.WriteLine("Vector.Count: " + Vector.Count); - this.inputSourceRGB = new byte[InputByteCount]; - for (int i = 0; i < this.inputSourceRGB.Length; i++) - { - this.inputSourceRGB[i] = (byte)(42 + i); - } - - this.inputSourceRGBAsInteger = new int[InputByteCount + Vector.Count]; // Filling this should be part of the measured operation - } - - [Benchmark(Baseline = true, Description = "Floating Point Conversion")] - public unsafe void RgbaToYcbCrScalarFloat() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - var yPtr = (float*)&result.Y; - var cbPtr = (float*)&result.Cb; - var crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - float r = input.Data[i3 + 0]; - float g = input.Data[i3 + 1]; - float b = input.Data[i3 + 2]; - - *yPtr++ = (0.299F * r) + (0.587F * g) + (0.114F * b); - *cbPtr++ = 128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - *crPtr++ = 128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - } - } - - [Benchmark(Description = "Simd Floating Point Conversion")] - public unsafe void RgbaToYcbCrSimdFloat() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - var yPtr = (float*)&result.Y; - var cbPtr = (float*)&result.Cb; - var crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - - var vectorRgb = new Vector3( - input.Data[i3 + 0], - input.Data[i3 + 1], - input.Data[i3 + 2]); - - Vector3 vectorY = VectorY * vectorRgb; - Vector3 vectorCb = VectorCb * vectorRgb; - Vector3 vectorCr = VectorCr * vectorRgb; - - *yPtr++ = vectorY.X + vectorY.Y + vectorY.Z; - *cbPtr++ = 128 + (vectorCb.X - vectorCb.Y + vectorCb.Z); - *crPtr++ = 128 + (vectorCr.X - vectorCr.Y - vectorCr.Z); - } - } - - [Benchmark(Description = "Scaled Integer Conversion + Vector")] - public unsafe void RgbaToYcbCrScaledIntegerSimd() - { - // Copy the input to the stack: - - // On-stack output: - Result result = default; - var yPtr = (float*)&result.Y; - var cbPtr = (float*)&result.Cb; - var crPtr = (float*)&result.Cr; - - var yCoeffs = new Vector(ScaledCoeffs.Y); - var cbCoeffs = new Vector(ScaledCoeffs.Cb); - var crCoeffs = new Vector(ScaledCoeffs.Cr); - - for (int i = 0; i < this.inputSourceRGB.Length; i++) - { - this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i]; - } - - for (int i = 0; i < InputColorCount; i += 2) - { - var rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); - - Vector y = yCoeffs * rgb; - Vector cb = cbCoeffs * rgb; - Vector cr = crCoeffs * rgb; - - *yPtr++ = (y[0] + y[1] + y[2]) >> 10; - *cbPtr++ = 128 + ((cb[0] - cb[1] + cb[2]) >> 10); - *crPtr++ = 128 + ((cr[0] - cr[1] - cr[2]) >> 10); - - *yPtr++ = (y[4] + y[5] + y[6]) >> 10; - *cbPtr++ = 128 + ((cb[4] - cb[5] + cb[6]) >> 10); - *crPtr++ = 128 + ((cr[4] - cr[5] - cr[6]) >> 10); - } - } - - /// - /// This should perform better. Coreclr emitted Vector.Dot() code lacks the vectorization even with IsHardwareAccelerated == true. - /// Kept this benchmark because maybe it will be improved in a future CLR release. - /// - /// https://www.gamedev.net/topic/673396-c-systemnumericsvectors-slow/ - /// - /// - [Benchmark(Description = "Scaled Integer Conversion + Vector + Dot Product")] - public unsafe void RgbaToYcbCrScaledIntegerSimdWithDotProduct() - { - // Copy the input to the stack: - - // On-stack output: - Result result = default; - float* yPtr = (float*)&result.Y; - float* cbPtr = (float*)&result.Cb; - float* crPtr = (float*)&result.Cr; - - var yCoeffs = new Vector(ScaledCoeffs.Y); - var cbCoeffs = new Vector(ScaledCoeffs.Cb); - var crCoeffs = new Vector(ScaledCoeffs.Cr); - - var leftY = new Vector(ScaledCoeffs.SelectLeft.Y); - var leftCb = new Vector(ScaledCoeffs.SelectLeft.Cb); - var leftCr = new Vector(ScaledCoeffs.SelectLeft.Cr); - - var rightY = new Vector(ScaledCoeffs.SelectRight.Y); - var rightCb = new Vector(ScaledCoeffs.SelectRight.Cb); - var rightCr = new Vector(ScaledCoeffs.SelectRight.Cr); - - for (int i = 0; i < this.inputSourceRGB.Length; i++) - { - this.inputSourceRGBAsInteger[i] = this.inputSourceRGB[i]; - } - - for (int i = 0; i < InputColorCount; i += 2) - { - var rgb = new Vector(this.inputSourceRGBAsInteger, i * 3); - - Vector y = yCoeffs * rgb; - Vector cb = cbCoeffs * rgb; - Vector cr = crCoeffs * rgb; - - VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, leftY, leftCb, leftCr); - VectorizedConvertImpl(ref yPtr, ref cbPtr, ref crPtr, y, cb, cr, rightY, rightCb, rightCr); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void VectorizedConvertImpl( - ref float* yPtr, - ref float* cbPtr, - ref float* crPtr, - Vector y, - Vector cb, - Vector cr, - Vector yAgg, - Vector cbAgg, - Vector crAgg) - { - int ySum = Vector.Dot(y, yAgg); - int cbSum = Vector.Dot(cb, cbAgg); - int crSum = Vector.Dot(cr, crAgg); - *yPtr++ = ySum >> 10; - *cbPtr++ = 128 + (cbSum >> 10); - *crPtr++ = 128 + (crSum >> 10); - } - - [Benchmark(Description = "Scaled Integer Conversion")] - public unsafe void RgbaToYcbCrScaledInteger() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - float* yPtr = (float*)&result.Y; - float* cbPtr = (float*)&result.Cb; - float* crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - int r = input.Data[i3 + 0]; - int g = input.Data[i3 + 1]; - int b = input.Data[i3 + 2]; - - // Scale by 1024, add .5F and truncate value - int y0 = 306 * r; // (0.299F * 1024) + .5F - int y1 = 601 * g; // (0.587F * 1024) + .5F - int y2 = 117 * b; // (0.114F * 1024) + .5F - - int cb0 = -172 * r; // (-0.168736F * 1024) + .5F - int cb1 = 339 * g; // (0.331264F * 1024) + .5F - int cb2 = 512 * b; // (0.5F * 1024) + .5F - - int cr0 = 512 * r; // (0.5F * 1024) + .5F - int cr1 = 429 * g; // (0.418688F * 1024) + .5F - int cr2 = 83 * b; // (0.081312F * 1024) + .5F - - *yPtr++ = (y0 + y1 + y2) >> 10; - *cbPtr++ = 128 + ((cb0 - cb1 + cb2) >> 10); - *crPtr++ = 128 + ((cr0 - cr1 - cr2) >> 10); - } - } - - [Benchmark(Description = "Scaled Integer LUT Conversion")] - public unsafe void RgbaToYcbCrScaledIntegerLut() - { - // Copy the input to the stack: - var input = OnStackInputCache.Byte.Create(this.inputSourceRGB); - - // On-stack output: - Result result = default; - float* yPtr = (float*)&result.Y; - float* cbPtr = (float*)&result.Cb; - float* crPtr = (float*)&result.Cr; - - for (int i = 0; i < InputColorCount; i++) - { - int i3 = i * 3; - - int r = input.Data[i3 + 0]; - int g = input.Data[i3 + 1]; - int b = input.Data[i3 + 2]; - - // TODO: Maybe concatenating all the arrays in LookupTables to a flat one can improve this! - *yPtr++ = (LookupTables.Y0[r] + LookupTables.Y1[g] + LookupTables.Y2[b]) >> 10; - *cbPtr++ = 128 + ((LookupTables.Cb0[r] - LookupTables.Cb1[g] + LookupTables.Cb2Cr0[b]) >> 10); - *crPtr++ = 128 + ((LookupTables.Cb2Cr0[r] - LookupTables.Cr1[g] - LookupTables.Cr2[b]) >> 10); - } - } - } -} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index bc0b40badd..8373027150 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,10 +2,18 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; +using System.IO; using System.Reflection; using System.Threading; -using SixLabors.ImageSharp.Memory.Internals; -using SixLabors.ImageSharp.Tests.Formats.Jpg; +using PhotoSauce.MagicScaler; +using PhotoSauce.MagicScaler.Interpolators; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; using Xunit.Abstractions; @@ -25,31 +33,172 @@ private class ConsoleOutput : ITestOutputHelper public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); } - /// - /// The main entry point. Useful for executing benchmarks and performance unit tests manually, - /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. - /// - /// - /// The arguments to pass to the program. - /// + const string pathTemplate = "C:\\Users\\pl4nu\\Downloads\\{0}.jpg"; + public static void Main(string[] args) { - try + //string imageName = "Calliphora_aligned_size"; + string imageName = "Calliphora"; + //string imageName = "bw_check"; + //string imageName = "bw_check_color"; + //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio444, 100); + //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio422, 100); + //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio420, 100); + //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio411, 100); + //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio410, 100); + //ReEncodeImage(imageName, JpegEncodingColor.Luminance, 100); + //ReEncodeImage(imageName, JpegEncodingColor.Rgb, 100); + //ReEncodeImage(imageName, JpegEncodingColor.Cmyk, 100); + + // Encoding q=75 | color=YCbCrRatio444 + // Elapsed: 4901ms across 500 iterations + // Average: 9,802ms + BenchmarkEncoder(imageName, 500, 75, JpegEncodingColor.YCbCrRatio444); + } + + private static void BenchmarkEncoder(string fileName, int iterations, int quality, JpegEncodingColor color) + { + string loadPath = String.Format(pathTemplate, fileName); + + using var inputStream = new FileStream(loadPath, FileMode.Open); + using var saveStream = new MemoryStream(); + + var decoder = new JpegDecoder { IgnoreMetadata = true }; + using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); + + var encoder = new JpegEncoder() + { + Quality = quality, + ColorType = color, + }; + + Stopwatch sw = new Stopwatch(); + sw.Start(); + for (int i = 0; i < iterations; i++) { - LoadResizeSaveParallelMemoryStress.Run(args); + img.SaveAsJpeg(saveStream, encoder); + saveStream.Position = 0; } - catch (Exception ex) + sw.Stop(); + + Console.WriteLine($"// Encoding q={quality} | color={color}\n" + + $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + + $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); + } + + private static void BenchmarkDecoder(string fileName, int iterations) + { + string loadPath = String.Format(pathTemplate, fileName); + + using var fileStream = new FileStream(loadPath, FileMode.Open); + using var inputStream = new MemoryStream(); + fileStream.CopyTo(inputStream); + + var decoder = new JpegDecoder { IgnoreMetadata = true }; + + var sw = new Stopwatch(); + sw.Start(); + for (int i = 0; i < iterations; i++) { - Console.WriteLine(ex); + inputStream.Position = 0; + using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); } + sw.Stop(); + + Console.WriteLine($"// Decoding\n" + + $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + + $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); + } + + private static void BenchmarkResizingLoop__explicit(string fileName, Size targetSize, int iterations) + { + string loadPath = String.Format(pathTemplate, fileName); - // RunJpegEncoderProfilingTests(); - // RunJpegColorProfilingTests(); - // RunDecodeJpegProfilingTests(); - // RunToVector4ProfilingTest(); - // RunResizeProfilingTest(); + using var fileStream = new FileStream(loadPath, FileMode.Open); + using var saveStream = new MemoryStream(); + using var inputStream = new MemoryStream(); + fileStream.CopyTo(inputStream); + + var decoder = new JpegDecoder { IgnoreMetadata = true }; + var encoder = new JpegEncoder { ColorType = JpegEncodingColor.YCbCrRatio444 }; + + var sw = new Stopwatch(); + sw.Start(); + for (int i = 0; i < iterations; i++) + { + inputStream.Position = 0; + using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); + img.Mutate(ctx => ctx.Resize(targetSize, KnownResamplers.Box, false)); + img.SaveAsJpeg(saveStream, encoder); + } + sw.Stop(); + + Console.WriteLine($"// Decode-Resize-Encode w/ Mutate()\n" + + $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + + $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); + } + + private static void ReEncodeImage(string fileName, JpegEncodingColor mode, int? quality = null) + { + string loadPath = String.Format(pathTemplate, fileName); + using Image img = Image.Load(loadPath); + + string savePath = String.Format(pathTemplate, $"q{quality}_{mode}_test_{fileName}"); + var encoder = new JpegEncoder() + { + Quality = quality, + ColorType = mode, + }; + img.SaveAsJpeg(savePath, encoder); + } + + private static void ReencodeImageResize__explicit(string fileName, Size targetSize, IResampler sampler, int? quality = null) + { + string loadPath = String.Format(pathTemplate, fileName); + string savePath = String.Format(pathTemplate, $"is_res_{sampler.GetType().Name}[{targetSize.Width}x{targetSize.Height}]_{fileName}"); + + var decoder = new JpegDecoder(); + var encoder = new JpegEncoder() + { + Quality = quality, + ColorType = JpegEncodingColor.YCbCrRatio444 + }; + + using Image img = decoder.Decode(Configuration.Default, File.OpenRead(loadPath), CancellationToken.None); + img.Mutate(ctx => ctx.Resize(targetSize, sampler, compand: false)); + img.SaveAsJpeg(savePath, encoder); + } + + private static void ReencodeImageResize__Netvips(string fileName, Size targetSize, int? quality) + { + string loadPath = String.Format(pathTemplate, fileName); + string savePath = String.Format(pathTemplate, $"netvips_resize_{fileName}"); + + using var thumb = NetVips.Image.Thumbnail(loadPath, targetSize.Width, targetSize.Height); + + // Save the results + thumb.Jpegsave(savePath, q: quality, strip: true, subsampleMode: NetVips.Enums.ForeignSubsample.Off); + } + + private static void ReencodeImageResize__MagicScaler(string fileName, Size targetSize, int quality) + { + string loadPath = String.Format(pathTemplate, fileName); + string savePath = String.Format(pathTemplate, $"magicscaler_resize_{fileName}"); + + var settings = new ProcessImageSettings() + { + Width = targetSize.Width, + Height = targetSize.Height, + SaveFormat = FileFormat.Jpeg, + JpegQuality = quality, + JpegSubsampleMode = ChromaSubsampleMode.Subsample444, + Sharpen = false, + ColorProfileMode = ColorProfileMode.Ignore, + HybridMode = HybridScaleMode.Turbo, + }; - // Console.ReadLine(); + using var output = new FileStream(savePath, FileMode.Create); + MagicImageProcessor.ProcessImage(loadPath, output, settings); } private static Version GetNetCoreVersion() diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index b72059b669..06c933a06c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -12,13 +12,11 @@ public class JpegMetadataTests [Fact] public void CloneIsDeep() { - var meta = new JpegMetadata { Quality = 50, ColorType = JpegEncodingColor.Luminance }; + var meta = new JpegMetadata { ColorType = JpegEncodingColor.Luminance }; var clone = (JpegMetadata)meta.DeepClone(); - clone.Quality = 99; clone.ColorType = JpegEncodingColor.YCbCrRatio420; - Assert.False(meta.Quality.Equals(clone.Quality)); Assert.False(meta.ColorType.Equals(clone.ColorType)); } From 0c766a64c492798c1067233deb40f4be7c931d2b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 10 May 2022 21:47:53 +0300 Subject: [PATCH 18/39] Small fixes --- .../Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 4 ---- .../Formats/Jpeg/Components/Encoder/JpegComponent.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 2 ++ 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 6e81e3a9a6..d271287bca 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -138,10 +138,6 @@ public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) public void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - // DEBUG INITIALIZATION SETUP - frame.AllocateComponents(fullScan: false); - - // DEBUG ENCODING SETUP int mcu = 0; int mcusPerColumn = frame.McusPerColumn; int mcusPerLine = frame.McusPerLine; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index 65425c05c1..49c7d4257e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -107,7 +107,7 @@ public void AllocateSpectral(bool fullScan) int spectralAllocWidth = this.SizeInBlocks.Width; int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index ccdac68f18..4318f9e09c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -126,6 +126,8 @@ public void Encode(Image image, Stream stream, CancellationToken var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables, Configuration.Default); + // TODO: change this for non-interleaved scans + frame.AllocateComponents(fullScan: false); if (frame.ComponentCount > 1) { this.scanEncoder.EncodeScanBaselineInterleaved(frame, spectralConverter, cancellationToken); From a83b3b699a0caac6b6935b7e5e51c10475ae8915 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 11 May 2022 14:31:16 +0300 Subject: [PATCH 19/39] Added avx accelerated rgb unpack method --- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 47 +++++++++++++++++++ .../Common/Helpers/SimdUtils.Pack.cs | 39 +++++++++++++++ .../JpegColorConverter.FromYCbCrAvx.cs | 10 ++-- .../Encoder/SpectralConverter{TPixel}.cs | 17 +------ .../PixelOperations/Rgb24.PixelOperations.cs | 9 ++++ .../PixelFormats/PixelOperations{TPixel}.cs | 13 +++-- 6 files changed, 109 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index cd96b51e92..e5494c7dc5 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -21,6 +21,10 @@ public static class HwIntrinsics public static ReadOnlySpan PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 }; + private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; + + internal static ReadOnlySpan ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF }; + private static ReadOnlySpan ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 }; private static ReadOnlySpan ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 }; @@ -962,6 +966,49 @@ internal static void PackFromRgbPlanesAvx2Reduce( blueChannel = blueChannel.Slice(slice); destination = destination.Slice(slice); } + + internal static void UnpackToRgbPlanesAvx2Reduce( + ref ReadOnlySpan redChannel, + ref ReadOnlySpan greenChannel, + ref ReadOnlySpan blueChannel, + ref Span source) + { + ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 destRRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); + ref Vector256 destGRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); + ref Vector256 destBRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); + + Vector256 extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); + Vector256 extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); + Vector256 rgb, rg, bx; + Vector256 r, g, b; + + const int bytesPerRgbStride = 24; + int count = source.Length / 8; + for (int i = 0; i < count; i++) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); + + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, Vector256.Zero); + bx = Avx2.UnpackHigh(rgb, Vector256.Zero); + + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256.Zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256.Zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256.Zero).AsInt32()); + + Unsafe.Add(ref destRRef, i) = r; + Unsafe.Add(ref destGRef, i) = g; + Unsafe.Add(ref destBRef, i) = b; + } + + int sliceCount = count * 8; + redChannel = redChannel.Slice(sliceCount); + greenChannel = greenChannel.Slice(sliceCount); + blueChannel = blueChannel.Slice(sliceCount); + source = source.Slice(sliceCount); + } } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs index 1ccf5ab1a4..b45c8c7d6f 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs @@ -65,6 +65,25 @@ internal static void PackFromRgbPlanes( PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); } + [MethodImpl(InliningOptions.ShortMethod)] + internal static void UnpackToRgbPlanes( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span source) + { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + + if (Avx2.IsSupported) + { + HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source); + } + + UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source); + } + private static void PackFromRgbPlanesScalarBatchedReduce( ref ReadOnlySpan redChannel, ref ReadOnlySpan greenChannel, @@ -200,5 +219,25 @@ private static void PackFromRgbPlanesRemainder( d.A = 255; } } + + private static void UnpackToRgbPlanesScalar( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span source) + { + ref float r = ref MemoryMarshal.GetReference(redChannel); + ref float g = ref MemoryMarshal.GetReference(greenChannel); + ref float b = ref MemoryMarshal.GetReference(blueChannel); + ref Rgb24 rgb = ref MemoryMarshal.GetReference(source); + + for (int i = 0; i < source.Length; i++) + { + ref Rgb24 src = ref Unsafe.Add(ref rgb, i); + Unsafe.Add(ref r, i) = src.R; + Unsafe.Add(ref g, i) = src.G; + Unsafe.Add(ref b, i) = src.B; + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index a89228b348..983fad8d09 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -80,6 +80,7 @@ public override void ConvertFromRgbInplace(in ComponentValues values) // Used for the color conversion var chromaOffset = Vector256.Create(this.HalfValue); + var scale = Vector256.Create(this.MaximumValue); var f0299 = Vector256.Create(0.299f); var f0587 = Vector256.Create(0.587f); @@ -97,9 +98,12 @@ public override void ConvertFromRgbInplace(in ComponentValues values) ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - Vector256 r = Avx.Multiply(c0, scale); - Vector256 g = Avx.Multiply(c1, scale); - Vector256 b = Avx.Multiply(c2, scale); + // Vector256 r = Avx.Multiply(c0, scale); + // Vector256 g = Avx.Multiply(c1, scale); + // Vector256 b = Avx.Multiply(c2, scale); + Vector256 r = c0; + Vector256 g = c1; + Vector256 b = c2; // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index b9a98aace2..f645cb460e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -21,8 +21,6 @@ internal class SpectralConverter : SpectralConverter private int pixelRowCounter; - private IMemoryOwner rgbBuffer; - private Buffer2D pixelBuffer; private int alignedPixelWidth; @@ -57,9 +55,6 @@ public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequa this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, component, postProcessorBufferSize, dequantTables[component.QuantizationTableIndex]); } - // single 'stride' rgba32 buffer for conversion between spectral and TPixel - this.rgbBuffer = allocator.Allocate(this.alignedPixelWidth * 3); - // color converter from Rgb24 to YCbCr this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); } @@ -80,25 +75,15 @@ private void ConvertStride(int spectralStep) // 4. Convert color buffer to spectral blocks with component post processors int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - int width = this.pixelBuffer.Width; - - Span r = this.rgbBuffer.Slice(0, this.alignedPixelWidth); - Span g = this.rgbBuffer.Slice(this.alignedPixelWidth, this.alignedPixelWidth); - Span b = this.rgbBuffer.Slice(this.alignedPixelWidth * 2, this.alignedPixelWidth); - for (int yy = this.pixelRowCounter; yy < maxY; yy++) { int y = yy - this.pixelRowCounter; // unpack TPixel to r/g/b planes Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); - PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, r, g, b, sourceRow); var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - - SimdUtils.ByteToNormalizedFloat(r, values.Component0); - SimdUtils.ByteToNormalizedFloat(g, values.Component1); - SimdUtils.ByteToNormalizedFloat(b, values.Component2); + PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, values.Component0, values.Component1, values.Component2, sourceRow); this.colorConverter.ConvertFromRgbInplace(values); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs index 0f1ea6b815..5f8a3e95f5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs @@ -36,6 +36,15 @@ internal override void PackFromRgbPlanes( SimdUtils.PackFromRgbPlanes(configuration, redChannel, greenChannel, blueChannel, destination); } + + /// + internal override void UnpackIntoRgbPlanes( + Configuration configuration, + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span source) + => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 701d63b55f..dcc3617957 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -209,9 +209,9 @@ internal virtual void PackFromRgbPlanes( /// A to the destination pixels. internal virtual void UnpackIntoRgbPlanes( Configuration configuration, - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, Span source) { Guard.NotNull(configuration, nameof(configuration)); @@ -219,15 +219,14 @@ internal virtual void UnpackIntoRgbPlanes( int count = redChannel.Length; Rgba32 rgba32 = default; - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); + ref float r = ref MemoryMarshal.GetReference(redChannel); + ref float g = ref MemoryMarshal.GetReference(greenChannel); + ref float b = ref MemoryMarshal.GetReference(blueChannel); ref TPixel d = ref MemoryMarshal.GetReference(source); for (int i = 0; i < count; i++) { // TODO: Create ToRgb24 method in IPixel - // TODO: create a fast intrinsic accelerated Rgb24 -> r/g/b planes overload Unsafe.Add(ref d, i).ToRgba32(ref rgba32); Unsafe.Add(ref r, i) = rgba32.R; Unsafe.Add(ref g, i) = rgba32.G; From 23e3bb889d056fff7163e625af4e8053333a0a33 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 11 May 2022 15:23:44 +0300 Subject: [PATCH 20/39] Fixed color converters --- .../JpegColorConverter.FromCmykAvx.cs | 17 +++++++------ .../JpegColorConverter.FromCmykScalar.cs | 16 ++++++------- .../JpegColorConverter.FromCmykVector.cs | 17 +++++++------ .../JpegColorConverter.FromGrayScaleAvx.cs | 24 ------------------- .../JpegColorConverter.FromGrayScaleScalar.cs | 14 ----------- .../JpegColorConverter.FromGrayScaleVector.cs | 14 ++--------- .../JpegColorConverter.FromRgbAvx.cs | 19 --------------- .../JpegColorConverter.FromRgbScalar.cs | 3 --- .../JpegColorConverter.FromRgbVector.cs | 22 ++--------------- .../JpegColorConverter.FromYCbCrAvx.cs | 1 - .../JpegColorConverter.FromYCbCrScalar.cs | 8 +++---- .../JpegColorConverter.FromYCbCrVector.cs | 8 +++---- .../JpegColorConverter.FromYccKAvx.cs | 5 ++-- .../JpegColorConverter.FromYccKScalar.cs | 10 ++++---- .../JpegColorConverter.FromYccKVector.cs | 6 +++-- 15 files changed, 46 insertions(+), 138 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 038fc8f9dd..0b170140ab 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -60,7 +60,6 @@ public override void ConvertFromRgbInplace(in ComponentValues values) // Used for the color conversion var scale = Vector256.Create(this.MaximumValue); - var one = Vector256.Create(1f); nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) @@ -70,21 +69,21 @@ public override void ConvertFromRgbInplace(in ComponentValues values) ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); ref Vector256 c3 = ref Unsafe.Add(ref c3Base, i); - Vector256 ctmp = Avx.Subtract(one, c0); - Vector256 mtmp = Avx.Subtract(one, c1); - Vector256 ytmp = Avx.Subtract(one, c2); + Vector256 ctmp = Avx.Subtract(scale, c0); + Vector256 mtmp = Avx.Subtract(scale, c1); + Vector256 ytmp = Avx.Subtract(scale, c2); Vector256 ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp)); - Vector256 kMask = Avx.CompareNotEqual(ktmp, one); + Vector256 kMask = Avx.CompareNotEqual(ktmp, scale); - ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(one, ktmp)), kMask); - mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(one, ktmp)), kMask); - ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(one, ktmp)), kMask); + ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); c0 = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); c1 = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); c2 = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); - c3 = Avx.Subtract(scale, Avx.Multiply(ktmp, scale)); + c3 = Avx.Subtract(scale, ktmp); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs index 33fe742471..13352ba7b5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -51,12 +51,12 @@ public static void ConvertFromRgbInplace(in ComponentValues values, float maxVal for (int i = 0; i < c0.Length; i++) { - float ctmp = 1f - c0[i]; - float mtmp = 1f - c1[i]; - float ytmp = 1f - c2[i]; + float ctmp = 255f - c0[i]; + float mtmp = 255f - c1[i]; + float ytmp = 255f - c2[i]; float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - if (1f - ktmp <= float.Epsilon) + if (255f - ktmp <= float.Epsilon) { ctmp = 0f; mtmp = 0f; @@ -64,15 +64,15 @@ public static void ConvertFromRgbInplace(in ComponentValues values, float maxVal } else { - ctmp = (ctmp - ktmp) / (1f - ktmp); - mtmp = (mtmp - ktmp) / (1f - ktmp); - ytmp = (ytmp - ktmp) / (1f - ktmp); + ctmp = (ctmp - ktmp) / (255f - ktmp); + mtmp = (mtmp - ktmp) / (255f - ktmp); + ytmp = (ytmp - ktmp) / (255f - ktmp); } c0[i] = maxValue - (ctmp * maxValue); c1[i] = maxValue - (mtmp * maxValue); c2[i] = maxValue - (ytmp * maxValue); - c3[i] = maxValue - (ktmp * maxValue); + c3[i] = maxValue - ktmp; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs index ff2bd2323f..d445546050 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -60,7 +60,6 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v // Used for the color conversion var scale = new Vector(this.MaximumValue); - var one = new Vector(1f); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) @@ -70,20 +69,20 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v ref Vector c2 = ref Unsafe.Add(ref c2Base, i); ref Vector c3 = ref Unsafe.Add(ref c3Base, i); - Vector ctmp = one - c0; - Vector mtmp = one - c1; - Vector ytmp = one - c2; + Vector ctmp = scale - c0; + Vector mtmp = scale - c1; + Vector ytmp = scale - c2; Vector ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp)); - var kMask = Vector.Equals(ktmp, Vector.One); - ctmp = Vector.AndNot((ctmp - ktmp) / (one - ktmp), kMask.As()); - mtmp = Vector.AndNot((mtmp - ktmp) / (one - ktmp), kMask.As()); - ytmp = Vector.AndNot((ytmp - ktmp) / (one - ktmp), kMask.As()); + var kMask = Vector.Equals(ktmp, scale); + ctmp = Vector.AndNot((ctmp - ktmp) / (scale - ktmp), kMask.As()); + mtmp = Vector.AndNot((mtmp - ktmp) / (scale - ktmp), kMask.As()); + ytmp = Vector.AndNot((ytmp - ktmp) / (scale - ktmp), kMask.As()); c0 = scale - (ctmp * scale); c1 = scale - (mtmp * scale); c2 = scale - (ytmp * scale); - c3 = scale - (ktmp * scale); + c3 = scale - ktmp; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index 778424a15d..c38545b41c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -20,34 +20,10 @@ public FromGrayscaleAvx(int precision) public override void ConvertToRgbInplace(in ComponentValues values) { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - // Used for the color conversion - var scale = Vector256.Create(1 / this.MaximumValue); - - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - c0 = Avx.Multiply(c0, scale); - } } public override void ConvertFromRgbInplace(in ComponentValues values) { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - // Used for the color conversion - var scale = Vector256.Create(this.MaximumValue); - - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - c0 = Avx.Multiply(c0, scale); - } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs index 027d762ea9..5981bf1ac7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -24,24 +24,10 @@ public override void ConvertFromRgbInplace(in ComponentValues values) internal static void ConvertCoreInplaceToRgb(Span values, float maxValue) { - ref float valuesRef = ref MemoryMarshal.GetReference(values); - float scale = 1 / maxValue; - - for (nint i = 0; i < values.Length; i++) - { - Unsafe.Add(ref valuesRef, i) *= scale; - } } internal static void ConvertCoreInplaceFromRgb(Span values, float maxValue) { - ref float valuesRef = ref MemoryMarshal.GetReference(values); - float scale = maxValue; - - for (nint i = 0; i < values.Length; i++) - { - Unsafe.Add(ref valuesRef, i) *= scale; - } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index e1d9178dd0..3ffacb909a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -36,21 +36,11 @@ protected override void ConvertCoreInplaceToRgb(in ComponentValues values) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) { - ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - var scale = new Vector(this.MaximumValue); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector c0 = ref Unsafe.Add(ref cBase, i); - c0 *= scale; - } } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) - => FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component0, this.MaximumValue); + { + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 987fe01e5f..070311e119 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -43,25 +43,6 @@ public override void ConvertToRgbInplace(in ComponentValues values) public override void ConvertFromRgbInplace(in ComponentValues values) { - ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - var scale = Vector256.Create(this.MaximumValue); - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref rBase, i); - ref Vector256 g = ref Unsafe.Add(ref gBase, i); - ref Vector256 b = ref Unsafe.Add(ref bBase, i); - r = Avx.Multiply(r, scale); - g = Avx.Multiply(g, scale); - b = Avx.Multiply(b, scale); - } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs index e92e5e34d8..db61359121 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -27,9 +27,6 @@ internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxVa internal static void ConvertCoreInplaceFromRgb(ComponentValues values, float maxValue) { - FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component0, maxValue); - FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component1, maxValue); - FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values.Component2, maxValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs index 2150c3459b..f16bf8178d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -44,29 +44,11 @@ protected override void ConvertCoreInplaceToRgb(in ComponentValues values) protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) { - ref Vector rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - var scale = new Vector(this.MaximumValue); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector r = ref Unsafe.Add(ref rBase, i); - ref Vector g = ref Unsafe.Add(ref gBase, i); - ref Vector b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) - => FromRgbScalar.ConvertCoreInplaceFromRgb(values, this.MaximumValue); + { + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 983fad8d09..6df2b714bc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -81,7 +81,6 @@ public override void ConvertFromRgbInplace(in ComponentValues values) // Used for the color conversion var chromaOffset = Vector256.Create(this.HalfValue); - var scale = Vector256.Create(this.MaximumValue); var f0299 = Vector256.Create(0.299f); var f0587 = Vector256.Create(0.587f); var f0114 = Vector256.Create(0.114f); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index d01f4c5f91..8e17c51300 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -55,13 +55,11 @@ public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float ma Span c1 = values.Component1; Span c2 = values.Component2; - float scale = maxValue; - for (int i = 0; i < c0.Length; i++) { - float r = c0[i] * scale; - float g = c1[i] * scale; - float b = c2[i] * scale; + float r = c0[i]; + float g = c1[i]; + float b = c2[i]; // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index 9fd372a312..34790e41b6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -81,8 +81,6 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v var chromaOffset = new Vector(this.HalfValue); - var scale = new Vector(this.MaximumValue); - var rYMult = new Vector(0.299f); var gYMult = new Vector(0.587f); var bYMult = new Vector(0.114f); @@ -102,9 +100,9 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v ref Vector c1 = ref Unsafe.Add(ref c1Base, i); ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - Vector r = c0 * scale; - Vector g = c1 * scale; - Vector b = c2 * scale; + Vector r = c0; + Vector g = c1; + Vector b = c2; // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs index d09e3f7473..d75d8277c8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -19,8 +19,6 @@ public FromYccKAvx(int precision) { } - public override void ConvertFromRgbInplace(in ComponentValues values) => throw new System.NotImplementedException(); - public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -78,6 +76,9 @@ public override void ConvertToRgbInplace(in ComponentValues values) c2 = b; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + => throw new System.NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs index 755839e244..353d085e8e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -14,11 +14,8 @@ public FromYccKScalar(int precision) { } - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - - public override void ConvertFromRgbInplace(in ComponentValues values) - => throw new NotImplementedException(); + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) { @@ -41,6 +38,9 @@ public static void ConvertToRgpInplace(in ComponentValues values, float maxValue c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; } } + + public override void ConvertFromRgbInplace(in ComponentValues values) + => throw new NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs index c4fd7ca326..c8967f1f10 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -71,9 +71,11 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromYccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + => throw new System.NotImplementedException(); - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + => throw new System.NotImplementedException(); } } } From d6111d36478043a0e120fbafd12a41ee2f953c50 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 12 May 2022 00:48:26 +0300 Subject: [PATCH 21/39] Imlemented avx accelerated subsampling --- .../Encoder/JpegComponentPostProcessor.cs | 108 ++++++++++++++++-- 1 file changed, 96 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index 9ef54d83f2..aa1c4b2c59 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -2,6 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -49,7 +54,11 @@ public void CopyColorBufferToBlocks(int spectralStep) Block8x8F workspaceBlock = default; // handle subsampling - this.PackColorBuffer(); + Size subsamplingFactors = this.component.SubSamplingDivisors; + if (subsamplingFactors.Width != 1 || subsamplingFactors.Height != 1) + { + this.PackColorBuffer(); + } for (int y = 0; y < spectralBuffer.Height; y++) { @@ -87,11 +96,7 @@ private void PackColorBuffer() { Size factors = this.component.SubSamplingDivisors; - if (factors.Width == 1 && factors.Height == 1) - { - return; - } - + float averageMultiplier = 1f / (factors.Width * factors.Height); for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) { Span targetBufferRow = this.ColorBuffer.DangerousGetRowSpan(i); @@ -106,20 +111,74 @@ private void PackColorBuffer() SumHorizontal(targetBufferRow, factors.Width); // calculate average - float multiplier = 1f / (factors.Width * factors.Height); - MultiplyToAverage(targetBufferRow, multiplier); + MultiplyToAverage(targetBufferRow, averageMultiplier); } static void SumVertical(Span target, Span source) { - for (int i = 0; i < target.Length; i++) + if (Avx.IsSupported) { - target[i] += source[i]; + ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + + // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed + nint count = source.Length / Vector256.Count; + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); + } + } + else + { + ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + + nint count = source.Length / Vector.Count; + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i); + } + + ref float targetRef = ref MemoryMarshal.GetReference(target); + ref float sourceRef = ref MemoryMarshal.GetReference(source); + for (nint i = count * Vector.Count; i < source.Length; i++) + { + Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i); + } } } static void SumHorizontal(Span target, int factor) { + if (Avx2.IsSupported) + { + ref Vector256 targetRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + + // Ideally we need to use log2: Numerics.Log2((uint)factor) + // but division by 2 works just fine in this case + // because factor value range is [1, 2, 4] + // log2(1) == 1 / 2 == 0 + // log2(2) == 2 / 2 == 1 + // log2(4) == 4 / 2 == 2 + int haddIterationsCount = (int)((uint)factor / 2); + int length = target.Length / Vector256.Count; + for (int i = 0; i < haddIterationsCount; i++) + { + length /= 2; + for (int j = 0; j < length; j++) + { + int indexLeft = j * 2; + int indexRight = indexLeft + 1; + Vector256 sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight)); + Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); + } + } + + int summedCount = length * factor * Vector256.Count; + target = target.Slice(summedCount); + } + + // scalar remainder for (int i = 0; i < target.Length / factor; i++) { target[i] = target[i * factor]; @@ -132,9 +191,34 @@ static void SumHorizontal(Span target, int factor) static void MultiplyToAverage(Span target, float multiplier) { - for (int i = 0; i < target.Length; i++) + if (Avx.IsSupported) + { + ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + + // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed + nint count = target.Length / Vector256.Count; + var multiplierVector = Vector256.Create(multiplier); + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); + } + } + else { - target[i] *= multiplier; + ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + + nint count = target.Length / Vector.Count; + var multiplierVector = new Vector(multiplier); + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) *= multiplierVector; + } + + ref float targetRef = ref MemoryMarshal.GetReference(target); + for (nint i = count * Vector.Count; i < target.Length; i++) + { + Unsafe.Add(ref targetRef, i) *= multiplier; + } } } } From 656482d9779609ab464c55221a4282c7595a1896 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 12 May 2022 02:31:50 +0300 Subject: [PATCH 22/39] Optimization, new jpeg metadata fields --- .../Decoder/ArithmeticScanDecoder.cs | 2 +- .../Components/Decoder/HuffmanScanDecoder.cs | 2 +- .../Jpeg/Components/Decoder/JpegFrame.cs | 6 +- .../Components/Encoder/HuffmanScanEncoder.cs | 132 ++++++++++++++---- .../Jpeg/Components/Encoder/JpegFrame.cs | 6 +- .../Formats/Jpeg/IJpegEncoderOptions.cs | 9 ++ .../Formats/Jpeg/JpegDecoderCore.cs | 5 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 3 + .../Formats/Jpeg/JpegEncoderCore.cs | 79 +++-------- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 21 ++- .../Formats/Jpg/SpectralJpegTests.cs | 2 +- 11 files changed, 167 insertions(+), 100 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs index d3a5ea15b0..15fdb3dc4b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs @@ -247,7 +247,7 @@ public void ParseEntropyCodedData(int scanComponentCount) this.scanBuffer = new JpegBitReader(this.stream); - bool fullScan = this.frame.Progressive || this.frame.MultiScan; + bool fullScan = this.frame.Progressive || !this.frame.Interleaved; this.frame.AllocateComponents(fullScan); if (this.frame.Progressive) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index da2d5da65a..308c52dbac 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -121,7 +121,7 @@ public void ParseEntropyCodedData(int scanComponentCount) this.scanBuffer = new JpegBitReader(this.stream); - bool fullScan = this.frame.Progressive || this.frame.MultiScan; + bool fullScan = this.frame.Progressive || !this.frame.Interleaved; this.frame.AllocateComponents(fullScan); if (!this.frame.Progressive) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index db1febd399..200096e193 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -12,7 +12,7 @@ internal sealed class JpegFrame : IDisposable { public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount) { - this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; + this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10; this.Precision = precision; @@ -27,7 +27,7 @@ public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height /// /// Gets a value indicating whether the frame uses the extended specification. /// - public bool Extended { get; private set; } + public bool IsExtended { get; private set; } /// /// Gets a value indicating whether the frame uses the progressive specification. @@ -40,7 +40,7 @@ public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height /// /// This is true for progressive and baseline non-interleaved images. /// - public bool MultiScan { get; set; } + public bool Interleaved { get; set; } /// /// Gets the precision. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index d271287bca..583b7225de 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -135,7 +135,65 @@ public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); } - public void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + switch (color) + { + case JpegEncodingColor.YCbCrRatio444: + case JpegEncodingColor.Rgb: + this.EncodeScanBaselineInterleaved444(frame, converter, cancellationToken); + break; + case JpegEncodingColor.YCbCrRatio420: + this.EncodeScanBaselineInterleaved420(frame, converter, cancellationToken); + break; + default: + this.EncodeScanBaselineInterleavedArbitrarySampling(frame, converter, cancellationToken); + break; + } + + this.FlushRemainingBytes(); + } + + public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + JpegComponent component = frame.Components[0]; + + int mcuLines = frame.McusPerColumn; + int w = component.WidthInBlocks; + + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + for (int i = 0; i < mcuLines; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); + + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int k = 0; k < w; k++) + { + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); + + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } + } + } + } + + private void EncodeScanBaselineInterleavedArbitrarySampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int mcu = 0; @@ -154,7 +212,7 @@ public void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConve { // Scan an interleaved mcu... process components in order int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < frame.ComponentCount; k++) + for (int k = 0; k < frame.Components.Length; k++) { JpegComponent component = frame.Components[k]; @@ -192,24 +250,30 @@ ref Unsafe.Add(ref blockRef, blockCol), } } } - - this.FlushRemainingBytes(); } - public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + private void EncodeScanBaselineInterleaved444(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - // DEBUG INITIALIZATION SETUP - frame.AllocateComponents(fullScan: false); + int mcusPerColumn = frame.McusPerColumn; + int mcusPerLine = frame.McusPerLine; - JpegComponent component = frame.Components[0]; - int mcuLines = frame.McusPerColumn; - int w = component.WidthInBlocks; - int h = component.SamplingFactors.Height; - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + JpegComponent c2 = frame.Components[2]; + JpegComponent c1 = frame.Components[1]; + JpegComponent c0 = frame.Components[0]; - for (int i = 0; i < mcuLines; i++) + ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; + ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; + ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId]; + ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId]; + ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId]; + ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId]; + + ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0)); + + for (int j = 0; j < mcusPerColumn; j++) { cancellationToken.ThrowIfCancellationRequested(); @@ -217,28 +281,40 @@ public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralC converter.ConvertStrideBaseline(); // Encode spectral to binary - for (int j = 0; j < h; j++) + for (int i = 0; i < mcusPerLine; i++) { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + this.WriteBlock( + c0, + ref Unsafe.Add(ref c0BlockRef, i), + ref c0dcHuffmanTable, + ref c0acHuffmanTable); + + this.WriteBlock( + c1, + ref Unsafe.Add(ref c1BlockRef, i), + ref c1dcHuffmanTable, + ref c1acHuffmanTable); + + this.WriteBlock( + c2, + ref Unsafe.Add(ref c2BlockRef, i), + ref c2dcHuffmanTable, + ref c2acHuffmanTable); - for (int k = 0; k < w; k++) + if (this.IsStreamFlushNeeded) { - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable, - ref acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } + this.FlushToStream(); } } } } + private void EncodeScanBaselineInterleaved420(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + throw new NotImplementedException(); + } + private void WriteBlock( JpegComponent component, ref Block8x8 block, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index c20e3d8635..a6a9bcb0a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -37,7 +37,7 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8); this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8); - for (int i = 0; i < this.ComponentCount; i++) + for (int i = 0; i < this.Components.Length; i++) { JpegComponent component = this.Components[i]; component.Init(this, maxSubFactorH, maxSubFactorV); @@ -50,8 +50,6 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i public int PixelWidth { get; private set; } - public int ComponentCount => this.Components.Length; - public JpegComponent[] Components { get; } public int McusPerLine { get; } @@ -70,7 +68,7 @@ public void Dispose() public void AllocateComponents(bool fullScan) { - for (int i = 0; i < this.ComponentCount; i++) + for (int i = 0; i < this.Components.Length; i++) { JpegComponent component = this.Components[i]; component.AllocateSpectral(fullScan); diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index ff87d88eb5..de55904212 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -14,5 +14,14 @@ internal interface IJpegEncoderOptions /// Defaults to 75. /// public int? Quality { get; set; } + + /// + /// Gets or sets the component encoding mode. + /// + /// + /// Interleaved encoding mode encodes all color components in a single scan. + /// Non-interleaved encoding mode encodes each color component in a separate scan. + /// + public bool? Interleaved { get; set; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 10e52066d1..25e31a2362 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -488,6 +488,8 @@ internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralC // Read on. fileMarker = FindNextFileMarker(this.markerBuffer, stream); } + + this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; } /// @@ -1178,6 +1180,7 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, } this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); + this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive; remaining -= length; @@ -1386,7 +1389,7 @@ private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining) // selectorsCount*2 bytes: component index + huffman tables indices stream.Read(this.temp, 0, selectorsBytes); - this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount; + this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount; for (int i = 0; i < selectorsBytes; i += 2) { // 1 byte: Component id diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 5d0e964a1e..c2a50b37e2 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -24,6 +24,9 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions /// public int? Quality { get; set; } + /// + public bool? Interleaved { get; set; } + /// /// Sets jpeg color for encoding. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4318f9e09c..fe3fbb4d94 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -38,9 +38,8 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// private readonly int? quality; - /// - /// Gets or sets the colorspace to use. - /// + private readonly bool? interleaved; + private JpegEncodingColor? colorType; private JpegFrameConfig frameConfig; @@ -60,6 +59,7 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) { this.quality = options.Quality; + this.interleaved = options.Interleaved; this.frameConfig = frameConfig; this.colorType = frameConfig.EncodingColor; @@ -121,20 +121,25 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the quantization tables. this.WriteDefineQuantizationTables(this.frameConfig.QuantizationTables, jpegMetadata); - // Write the scan header. - this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); - var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables, Configuration.Default); - // TODO: change this for non-interleaved scans - frame.AllocateComponents(fullScan: false); - if (frame.ComponentCount > 1) + if (frame.Components.Length == 1) + { + frame.AllocateComponents(fullScan: false); + + this.WriteStartOfScan(this.frameConfig.Components); + this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken); + } + else if (this.interleaved ?? jpegMetadata.Interleaved ?? true) { - this.scanEncoder.EncodeScanBaselineInterleaved(frame, spectralConverter, cancellationToken); + frame.AllocateComponents(fullScan: false); + + this.WriteStartOfScan(this.frameConfig.Components); + this.scanEncoder.EncodeScanBaselineInterleaved(this.frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); } else { - this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken); + throw new NotImplementedException(); } // Write the End Of Image marker. @@ -143,50 +148,6 @@ public void Encode(Image image, Stream stream, CancellationToken stream.Flush(); } - /// - /// 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 defering the field assignment - /// to . - /// - private static JpegEncodingColor? GetFallbackColorType(Image image) - where TPixel : unmanaged, IPixel - { - // First inspect the image metadata. - JpegEncodingColor? 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 = JpegEncodingColor.Luminance; - } - - return colorType; - } - - /// - /// Returns true, if the color type is supported by the encoder. - /// - /// The color type. - /// true, if color type is supported. - private static bool IsSupportedColorType(JpegEncodingColor? colorType) - => colorType == JpegEncodingColor.YCbCrRatio444 - || colorType == JpegEncodingColor.YCbCrRatio420 - || colorType == JpegEncodingColor.Luminance - || colorType == JpegEncodingColor.Rgb; - /// /// Write the start of image marker. /// @@ -611,7 +572,7 @@ private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) /// /// Writes the StartOfScan marker. /// - private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components) + private void WriteStartOfScan(Span components) { // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: // - the marker length "\x00\x0c", @@ -626,13 +587,13 @@ private void WriteStartOfScan(int componentCount, JpegComponentConfig[] componen this.buffer[1] = JpegConstants.Markers.SOS; // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) - int sosSize = 6 + (2 * componentCount); + int sosSize = 6 + (2 * components.Length); this.buffer[2] = 0x00; this.buffer[3] = (byte)sosSize; - this.buffer[4] = (byte)componentCount; // Number of components in a scan + this.buffer[4] = (byte)components.Length; // Number of components in a scan // Components data - for (int i = 0; i < componentCount; i++) + for (int i = 0; i < components.Length; i++) { int i2 = 2 * i; diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 6c34036793..b878d26fb8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -99,9 +99,26 @@ public int Quality } /// - /// Gets or sets the color type. + /// Gets the color type. /// - public JpegEncodingColor? ColorType { get; set; } + public JpegEncodingColor? ColorType { get; internal set; } + + /// + /// Gets the component encoding mode. + /// + /// + /// Interleaved encoding mode encodes all color components in a single scan. + /// Non-interleaved encoding mode encodes each color component in a separate scan. + /// + public bool? Interleaved { get; internal set; } + + /// + /// Gets the scan encoding mode. + /// + /// + /// Progressive jpeg images encode component data across multiple scans. + /// + public bool? Progressive { get; internal set; } /// public IDeepCloneable DeepClone() => new JpegMetadata(this); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index c4a448ff8e..c5b3b3da50 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -151,7 +151,7 @@ public LibJpegTools.SpectralData SpectralData { // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing // Progressive and multi-scan images must be loaded manually - if (this.frame.Progressive || this.frame.MultiScan) + if (this.frame.Progressive || !this.frame.Interleaved) { LibJpegTools.ComponentData[] components = this.spectralData.Components; for (int i = 0; i < components.Length; i++) From 399a10cd4f3f36a48c26878ae87557a8501d6f21 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 14 May 2022 17:06:42 +0300 Subject: [PATCH 23/39] Fixed color conversion --- .../JpegColorConverter.FromCmykAvx.cs | 37 +++++++++-------- .../JpegColorConverter.FromCmykScalar.cs | 32 +++++++-------- .../JpegColorConverter.FromCmykVector.cs | 41 ++++++++++--------- .../JpegColorConverter.FromGrayScaleAvx.cs | 29 ++++++++++++- .../JpegColorConverter.FromGrayScaleScalar.cs | 19 ++++++--- .../JpegColorConverter.FromGrayScaleVector.cs | 32 +++++++++++++-- .../JpegColorConverter.FromRgbAvx.cs | 6 ++- .../JpegColorConverter.FromRgbScalar.cs | 13 ++++-- .../JpegColorConverter.FromRgbVector.cs | 11 +++-- .../JpegColorConverter.FromYCbCrAvx.cs | 35 ++++++++-------- .../JpegColorConverter.FromYCbCrScalar.cs | 26 ++++++------ .../JpegColorConverter.FromYCbCrVector.cs | 36 ++++++++-------- .../JpegColorConverter.FromYccKAvx.cs | 5 ++- .../JpegColorConverter.FromYccKScalar.cs | 2 +- .../JpegColorConverter.FromYccKVector.cs | 5 ++- .../ColorConverters/JpegColorConverterBase.cs | 2 +- .../JpegColorConverterVector.cs | 34 +++++++++------ .../Encoder/JpegComponentPostProcessor.cs | 9 ++-- .../Encoder/SpectralConverter{TPixel}.cs | 28 +++++++++++-- 19 files changed, 260 insertions(+), 142 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 0b170140ab..989d155fcd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -47,31 +48,33 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values) + public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) { - ref Vector256 c0Base = + ref Vector256 destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = + ref Vector256 destM = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = + ref Vector256 destY = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = + ref Vector256 destK = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + ref Vector256 srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + // Used for the color conversion var scale = Vector256.Create(this.MaximumValue); nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector256 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector256 ctmp = Avx.Subtract(scale, c0); - Vector256 mtmp = Avx.Subtract(scale, c1); - Vector256 ytmp = Avx.Subtract(scale, c2); + Vector256 ctmp = Avx.Subtract(scale, Unsafe.Add(ref srcR, i)); + Vector256 mtmp = Avx.Subtract(scale, Unsafe.Add(ref srcG, i)); + Vector256 ytmp = Avx.Subtract(scale, Unsafe.Add(ref srcB, i)); Vector256 ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp)); Vector256 kMask = Avx.CompareNotEqual(ktmp, scale); @@ -80,10 +83,10 @@ public override void ConvertFromRgbInplace(in ComponentValues values) mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); - c0 = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); - c1 = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); - c2 = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); - c3 = Avx.Subtract(scale, ktmp); + Unsafe.Add(ref destC, i) = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); + Unsafe.Add(ref destM, i) = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); + Unsafe.Add(ref destY, i) = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); + Unsafe.Add(ref destK, i) = Avx.Subtract(scale, ktmp); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs index 13352ba7b5..af3decaad0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -17,8 +17,8 @@ public FromCmykScalar(int precision) public override void ConvertToRgbInplace(in ComponentValues values) => ConvertToRgbInplace(values, this.MaximumValue); - public override void ConvertFromRgbInplace(in ComponentValues values) - => ConvertFromRgbInplace(values, this.MaximumValue); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); public static void ConvertToRgbInplace(in ComponentValues values, float maxValue) { @@ -42,21 +42,21 @@ public static void ConvertToRgbInplace(in ComponentValues values, float maxValue } } - public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue) + public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue, Span r, Span g, Span b) { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; + Span k = values.Component3; - for (int i = 0; i < c0.Length; i++) + for (int i = 0; i < c.Length; i++) { - float ctmp = 255f - c0[i]; - float mtmp = 255f - c1[i]; - float ytmp = 255f - c2[i]; + float ctmp = 255f - r[i]; + float mtmp = 255f - g[i]; + float ytmp = 255f - b[i]; float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - if (255f - ktmp <= float.Epsilon) + if (ktmp >= 255f) { ctmp = 0f; mtmp = 0f; @@ -69,10 +69,10 @@ public static void ConvertFromRgbInplace(in ComponentValues values, float maxVal ytmp = (ytmp - ktmp) / (255f - ktmp); } - c0[i] = maxValue - (ctmp * maxValue); - c1[i] = maxValue - (mtmp * maxValue); - c2[i] = maxValue - (ytmp * maxValue); - c3[i] = maxValue - ktmp; + c[i] = maxValue - (ctmp * maxValue); + m[i] = maxValue - (mtmp * maxValue); + y[i] = maxValue - (ytmp * maxValue); + k[i] = maxValue - ktmp; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs index d445546050..6a6fd4f5f5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -47,31 +48,33 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromCmykScalar.ConvertToRgbInplace(values, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) { - ref Vector c0Base = + ref Vector destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector c1Base = + ref Vector destM = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector c2Base = + ref Vector destY = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector c3Base = + ref Vector destK = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(r)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(g)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(b)); + // Used for the color conversion var scale = new Vector(this.MaximumValue); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { - ref Vector c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector c3 = ref Unsafe.Add(ref c3Base, i); - - Vector ctmp = scale - c0; - Vector mtmp = scale - c1; - Vector ytmp = scale - c2; + Vector ctmp = scale - Unsafe.Add(ref srcR, i); + Vector mtmp = scale - Unsafe.Add(ref srcG, i); + Vector ytmp = scale - Unsafe.Add(ref srcB, i); Vector ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp)); var kMask = Vector.Equals(ktmp, scale); @@ -79,15 +82,15 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v mtmp = Vector.AndNot((mtmp - ktmp) / (scale - ktmp), kMask.As()); ytmp = Vector.AndNot((ytmp - ktmp) / (scale - ktmp), kMask.As()); - c0 = scale - (ctmp * scale); - c1 = scale - (mtmp * scale); - c2 = scale - (ytmp * scale); - c3 = scale - ktmp; + Unsafe.Add(ref destC, i) = scale - (ctmp * scale); + Unsafe.Add(ref destM, i) = scale - (mtmp * scale); + Unsafe.Add(ref destY, i) = scale - (ytmp * scale); + Unsafe.Add(ref destK, i) = scale - ktmp; } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) - => FromCmykScalar.ConvertFromRgbInplace(values, this.MaximumValue); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => FromCmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index c38545b41c..faa50ba476 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; +using static SixLabors.ImageSharp.SimdUtils; namespace SixLabors.ImageSharp.Formats.Jpeg.Components { @@ -22,8 +24,33 @@ public override void ConvertToRgbInplace(in ComponentValues values) { } - public override void ConvertFromRgbInplace(in ComponentValues values) + public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) { + ref Vector256 destLuminance = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + ref Vector256 srcRed = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcGreen = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcBlue = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + // Used for the color conversion + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 r = ref Unsafe.Add(ref srcRed, i); + ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); + ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); + + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs index 5981bf1ac7..2430606229 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Components { @@ -19,15 +17,26 @@ public FromGrayscaleScalar(int precision) public override void ConvertToRgbInplace(in ComponentValues values) => ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); - public override void ConvertFromRgbInplace(in ComponentValues values) - => ConvertCoreInplaceFromRgb(values.Component0, this.MaximumValue); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, r, g, b); internal static void ConvertCoreInplaceToRgb(Span values, float maxValue) { } - internal static void ConvertCoreInplaceFromRgb(Span values, float maxValue) + internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { + Span c0 = values.Component0; + + for (int i = 0; i < c0.Length; i++) + { + float r = rLane[i]; + float g = gLane[i]; + float b = bLane[i]; + + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + c0[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index 3ffacb909a..f92b283d42 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -34,13 +35,36 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { - } + ref Vector destLuma = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) - { + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + var rMult = new Vector(0.299f); + var gMult = new Vector(0.587f); + var bMult = new Vector(0.114f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = Unsafe.Add(ref srcR, i); + Vector g = Unsafe.Add(ref srcR, i); + Vector b = Unsafe.Add(ref srcR, i); + + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuma, i) = (rMult * r) + (gMult * g) + (bMult * b); + } } + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 070311e119..141f323ec8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -41,8 +42,11 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values) + public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) { + rLane.CopyTo(values.Component0); + gLane.CopyTo(values.Component1); + bLane.CopyTo(values.Component2); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs index db61359121..64b357d54e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase @@ -13,10 +15,10 @@ public FromRgbScalar(int precision) } public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplaceFromRgb(values, this.MaximumValue); + => ConvertCoreInplaceToRgb(values, this.MaximumValue); - public override void ConvertFromRgbInplace(in ComponentValues values) - => ConvertCoreInplaceFromRgb(values, this.MaximumValue); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, r, g, b); internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue) { @@ -25,8 +27,11 @@ internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxVa FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); } - internal static void ConvertCoreInplaceFromRgb(ComponentValues values, float maxValue) + internal static void ConvertCoreInplaceFromRgb(ComponentValues values, Span r, Span g, Span b) { + r.CopyTo(values.Component0); + g.CopyTo(values.Component1); + b.CopyTo(values.Component2); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs index f16bf8178d..72a8ef1298 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -42,13 +43,15 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromRgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) { + r.CopyTo(values.Component0); + g.CopyTo(values.Component1); + b.CopyTo(values.Component2); } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) - { - } + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => FromRgbScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 6df2b714bc..678df453c8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -69,15 +70,22 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values) + public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) { - ref Vector256 c0Base = + ref Vector256 destY = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = + ref Vector256 destCb = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = + ref Vector256 destCr = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + // Used for the color conversion var chromaOffset = Vector256.Create(this.HalfValue); @@ -93,16 +101,9 @@ public override void ConvertFromRgbInplace(in ComponentValues values) nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - - // Vector256 r = Avx.Multiply(c0, scale); - // Vector256 g = Avx.Multiply(c1, scale); - // Vector256 b = Avx.Multiply(c2, scale); - Vector256 r = c0; - Vector256 g = c1; - Vector256 b = c2; + Vector256 r = Unsafe.Add(ref srcR, i); + Vector256 g = Unsafe.Add(ref srcG, i); + Vector256 b = Unsafe.Add(ref srcB, i); // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) @@ -111,9 +112,9 @@ public override void ConvertFromRgbInplace(in ComponentValues values) Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - c0 = y; - c1 = cb; - c2 = cr; + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index 8e17c51300..424ab83eb3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -23,8 +23,8 @@ public FromYCbCrScalar(int precision) public override void ConvertToRgbInplace(in ComponentValues values) => ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); - public override void ConvertFromRgbInplace(in ComponentValues values) - => ConvertCoreInplaceFromRgb(values, this.MaximumValue, this.HalfValue); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); public static void ConvertCoreInplaceToRgb(in ComponentValues values, float maxValue, float halfValue) { @@ -49,24 +49,24 @@ public static void ConvertCoreInplaceToRgb(in ComponentValues values, float maxV } } - public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float maxValue, float halfValue) + public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; + Span y = values.Component0; + Span cb = values.Component1; + Span cr = values.Component2; - for (int i = 0; i < c0.Length; i++) + for (int i = 0; i < y.Length; i++) { - float r = c0[i]; - float g = c1[i]; - float b = c2[i]; + float r = rLane[i]; + float g = gLane[i]; + float b = bLane[i]; // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - c0[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - c1[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - c2[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index 34790e41b6..022c09b497 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -70,15 +71,22 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromYCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { - ref Vector c0Base = + ref Vector destY = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector c1Base = + ref Vector destCb = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector c2Base = + ref Vector destCr = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + var chromaOffset = new Vector(this.HalfValue); var rYMult = new Vector(0.299f); @@ -96,25 +104,21 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { - ref Vector c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - - Vector r = c0; - Vector g = c1; - Vector b = c2; + Vector r = Unsafe.Add(ref srcR, i); + Vector g = Unsafe.Add(ref srcG, i); + Vector b = Unsafe.Add(ref srcB, i); // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - c0 = (rYMult * r) + (gYMult * g) + (bYMult * b); - c1 = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); - c2 = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) - => FromYCbCrScalar.ConvertCoreInplaceFromRgb(values, this.MaximumValue, this.HalfValue); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => FromYCbCrScalar.ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs index d75d8277c8..6166f342b3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -77,8 +78,8 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values) - => throw new System.NotImplementedException(); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => throw new NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs index 353d085e8e..bf5d1c4140 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -39,7 +39,7 @@ public static void ConvertToRgpInplace(in ComponentValues values, float maxValue } } - public override void ConvertFromRgbInplace(in ComponentValues values) + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) => throw new NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs index c8967f1f10..cc4212d473 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -71,10 +72,10 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromYccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) => throw new System.NotImplementedException(); - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) => throw new System.NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index df1f8db8fc..500c25a951 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -79,7 +79,7 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); - public abstract void ConvertFromRgbInplace(in ComponentValues values); + public abstract void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b); /// /// Returns the s for all supported colorspaces and precisions. diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs index 6b7621d8fb..b5460d31aa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -35,13 +35,14 @@ public override void ConvertToRgbInplace(in ComponentValues values) int length = values.Component0.Length; int remainder = (int)((uint)length % (uint)Vector.Count); - // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide - // Thus there's no need to check whether simdCount is greater than zero int simdCount = length - remainder; - this.ConvertCoreVectorizedInplaceToRgb(values.Slice(0, simdCount)); + if (simdCount > 0) + { + this.ConvertCoreVectorizedInplaceToRgb(values.Slice(0, simdCount)); + } // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX implementations would never have + // so it's safe to say SSE/AVX1/AVX2 implementations would never have // 'remainder' pixels // But some exotic simd implementations e.g. AVX-512 can have // remainder pixels @@ -51,26 +52,35 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values) + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) { DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); int length = values.Component0.Length; int remainder = (int)((uint)length % (uint)Vector.Count); - // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide - // Thus there's no need to check whether simdCount is greater than zero int simdCount = length - remainder; - this.ConvertCoreVectorizedInplaceFromRgb(values.Slice(0, simdCount)); + if (simdCount > 0) + { + this.ConvertCoreVectorizedInplaceFromRgb( + values.Slice(0, simdCount), + r.Slice(0, simdCount), + g.Slice(0, simdCount), + b.Slice(0, simdCount)); + } // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX implementations would never have + // so it's safe to say SSE/AVX1/AVX2 implementations would never have // 'remainder' pixels // But some exotic simd implementations e.g. AVX-512 can have // remainder pixels if (remainder > 0) { - this.ConvertCoreInplaceFromRgb(values.Slice(simdCount, remainder)); + this.ConvertCoreInplaceFromRgb( + values.Slice(simdCount, remainder), + r.Slice(simdCount, remainder), + g.Slice(simdCount, remainder), + b.Slice(simdCount, remainder)); } } @@ -78,9 +88,9 @@ public override void ConvertFromRgbInplace(in ComponentValues values) protected abstract void ConvertCoreInplaceToRgb(in ComponentValues values); - protected abstract void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values); + protected abstract void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b); - protected abstract void ConvertCoreInplaceFromRgb(in ComponentValues values); + protected abstract void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs index aa1c4b2c59..fb9f72a1a1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs @@ -60,14 +60,15 @@ public void CopyColorBufferToBlocks(int spectralStep) this.PackColorBuffer(); } - for (int y = 0; y < spectralBuffer.Height; y++) + int blocksRowsPerStep = this.component.SamplingFactors.Height; + + for (int y = 0; y < blocksRowsPerStep; y++) { int yBuffer = y * this.blockAreaSize.Height; + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - // load 8x8 block from 8 pixel strides int xColorBufferStart = xBlock * 8; workspaceBlock.ScaledCopyFrom( diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index f645cb460e..3445072858 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -23,6 +23,12 @@ internal class SpectralConverter : SpectralConverter private Buffer2D pixelBuffer; + private IMemoryOwner redLane; + + private IMemoryOwner greenLane; + + private IMemoryOwner blueLane; + private int alignedPixelWidth; private JpegColorConverterBase colorConverter; @@ -55,6 +61,10 @@ public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequa this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, component, postProcessorBufferSize, dequantTables[component.QuantizationTableIndex]); } + this.redLane = allocator.Allocate(this.alignedPixelWidth); + this.greenLane = allocator.Allocate(this.alignedPixelWidth); + this.blueLane = allocator.Allocate(this.alignedPixelWidth); + // color converter from Rgb24 to YCbCr this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); } @@ -67,6 +77,15 @@ public void ConvertStrideBaseline() this.ConvertStride(spectralStep: 0); } + public void ConvertFull() + { + int steps = (int)Numerics.DivideCeil((uint)this.pixelBuffer.Height, (uint)this.pixelRowsPerStep); + for (int i = 0; i < steps; i++) + { + this.ConvertStride(i); + } + } + private void ConvertStride(int spectralStep) { // 1. Unpack from TPixel to r/g/b planes @@ -75,6 +94,9 @@ private void ConvertStride(int spectralStep) // 4. Convert color buffer to spectral blocks with component post processors int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); + Span rLane = this.redLane.GetSpan(); + Span gLane = this.greenLane.GetSpan(); + Span bLane = this.blueLane.GetSpan(); for (int yy = this.pixelRowCounter; yy < maxY; yy++) { int y = yy - this.pixelRowCounter; @@ -82,10 +104,10 @@ private void ConvertStride(int spectralStep) // unpack TPixel to r/g/b planes Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); - var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, values.Component0, values.Component1, values.Component2, sourceRow); + PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, rLane, gLane, bLane, sourceRow); - this.colorConverter.ConvertFromRgbInplace(values); + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + this.colorConverter.ConvertFromRgbInplace(values, rLane, gLane, bLane); } for (int i = 0; i < this.componentProcessors.Length; i++) From 97dc60d171d13ee61b8d98fdb432549e2866da8c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 14 May 2022 18:39:48 +0300 Subject: [PATCH 24/39] Implemented non-interleaved encoding --- .../Components/Encoder/HuffmanScanEncoder.cs | 47 +++++++++++++++++-- .../Formats/Jpeg/JpegEncoderCore.cs | 12 ++++- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 583b7225de..c989346fcb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -155,18 +155,16 @@ public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegF this.FlushRemainingBytes(); } - public void EncodeScanBaselineSingleComponent(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + public void EncodeScanBaselineSingleComponent(JpegComponent component, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - JpegComponent component = frame.Components[0]; - - int mcuLines = frame.McusPerColumn; + int h = component.HeightInBlocks; int w = component.WidthInBlocks; ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - for (int i = 0; i < mcuLines; i++) + for (int i = 0; i < h; i++) { cancellationToken.ThrowIfCancellationRequested(); @@ -193,6 +191,40 @@ ref Unsafe.Add(ref blockRef, k), } } + public void EncodeScanBaseline(JpegComponent component, CancellationToken cancellationToken) + { + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; + + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + for (int i = 0; i < h; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int k = 0; k < w; k++) + { + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); + + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); + } + } + } + + this.FlushRemainingBytes(); + } + private void EncodeScanBaselineInterleavedArbitrarySampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -608,6 +640,11 @@ private void FlushRemainingBytes() // Flush cached bytes to the output stream with padding bits int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; this.FlushToStream(lastByteIndex); + + // Clean huffman register + // This is needed for for images with multiples scans + this.bitCount = 0; + this.accumulatedBits = 0; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index fe3fbb4d94..ae6c44f51f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -128,7 +128,7 @@ public void Encode(Image image, Stream stream, CancellationToken frame.AllocateComponents(fullScan: false); this.WriteStartOfScan(this.frameConfig.Components); - this.scanEncoder.EncodeScanBaselineSingleComponent(frame, spectralConverter, cancellationToken); + this.scanEncoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); } else if (this.interleaved ?? jpegMetadata.Interleaved ?? true) { @@ -139,7 +139,15 @@ public void Encode(Image image, Stream stream, CancellationToken } else { - throw new NotImplementedException(); + frame.AllocateComponents(fullScan: true); + spectralConverter.ConvertFull(); + + Span components = this.frameConfig.Components; + for (int i = 0; i < frame.Components.Length; i++) + { + this.WriteStartOfScan(components.Slice(i, 1)); + this.scanEncoder.EncodeScanBaseline(frame.Components[i], cancellationToken); + } } // Write the End Of Image marker. From 97f200d4758c07ae751105e008a4f4ea5ed88d03 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 14 May 2022 23:53:13 +0300 Subject: [PATCH 25/39] Refactoring, fixes, tests --- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 8 +- .../Common/Helpers/SimdUtils.Pack.cs | 20 +- .../Jpeg/Components/Block8x8F.ScaledCopy.cs | 2 - ...ykAvx.cs => JpegColorConverter.CmykAvx.cs} | 4 +- ...ar.cs => JpegColorConverter.CmykScalar.cs} | 4 +- ...or.cs => JpegColorConverter.CmykVector.cs} | 8 +- ....cs => JpegColorConverter.GrayScaleAvx.cs} | 16 +- ... => JpegColorConverter.GrayScaleScalar.cs} | 16 +- ... => JpegColorConverter.GrayScaleVector.cs} | 8 +- ...RgbAvx.cs => JpegColorConverter.RgbAvx.cs} | 4 +- ...lar.cs => JpegColorConverter.RgbScalar.cs} | 10 +- ...tor.cs => JpegColorConverter.RgbVector.cs} | 8 +- ...rAvx.cs => JpegColorConverter.YCbCrAvx.cs} | 12 +- ...r.cs => JpegColorConverter.YCbCrScalar.cs} | 4 +- ...r.cs => JpegColorConverter.YCbCrVector.cs} | 16 +- ...cKAvx.cs => JpegColorConverter.YccKAvx.cs} | 12 +- ...ar.cs => JpegColorConverter.YccKScalar.cs} | 4 +- ...or.cs => JpegColorConverter.YccKVector.cs} | 14 +- .../ColorConverters/JpegColorConverterBase.cs | 32 +- .../{JpegComponent.cs => Component.cs} | 4 +- ...PostProcessor.cs => ComponentProcessor.cs} | 26 +- .../EncodingConfigs/JpegComponentConfig.cs | 30 ++ .../EncodingConfigs/JpegFrameConfig.cs | 42 +++ .../EncodingConfigs/JpegHuffmanTableConfig.cs | 21 ++ .../JpegQuantizationTableConfig.cs | 20 ++ .../Components/Encoder/HuffmanScanEncoder.cs | 71 ++-- .../Jpeg/Components/Encoder/JpegFrame.cs | 27 +- .../Encoder/SpectralConverter{TPixel}.cs | 87 +++-- .../Formats/Jpeg/IJpegEncoderOptions.cs | 5 + .../Formats/Jpeg/JpegDecoderCore.cs | 18 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 277 +--------------- .../Formats/Jpeg/JpegEncoderCore.cs | 311 ++++++++++++++---- src/ImageSharp/Formats/Jpeg/JpegMetadata.cs | 38 +-- .../PixelOperations/Rgb24.PixelOperations.cs | 11 +- .../PixelFormats/PixelOperations{TPixel}.cs | 19 +- .../ColorConversion/CmykColorConversion.cs | 6 +- .../GrayscaleColorConversion.cs | 4 +- .../ColorConversion/RgbColorConversion.cs | 6 +- .../ColorConversion/YCbCrColorConversion.cs | 6 +- .../ColorConversion/YccKColorConverter.cs | 6 +- .../Codecs/Jpeg/EncodeJpegFeatures.cs | 46 ++- .../Formats/Jpg/JpegColorConverterTests.cs | 49 ++- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 1 - .../Formats/Jpg/JpegEncoderTests.cs | 76 +---- 44 files changed, 722 insertions(+), 687 deletions(-) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromCmykAvx.cs => JpegColorConverter.CmykAvx.cs} (97%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromCmykScalar.cs => JpegColorConverter.CmykScalar.cs} (95%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromCmykVector.cs => JpegColorConverter.CmykVector.cs} (93%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromGrayScaleAvx.cs => JpegColorConverter.GrayScaleAvx.cs} (77%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromGrayScaleScalar.cs => JpegColorConverter.GrayScaleScalar.cs} (69%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromGrayScaleVector.cs => JpegColorConverter.GrayScaleVector.cs} (89%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromRgbAvx.cs => JpegColorConverter.RgbAvx.cs} (94%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromRgbScalar.cs => JpegColorConverter.RgbScalar.cs} (73%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromRgbVector.cs => JpegColorConverter.RgbVector.cs} (88%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromYCbCrAvx.cs => JpegColorConverter.YCbCrAvx.cs} (93%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromYCbCrScalar.cs => JpegColorConverter.YCbCrScalar.cs} (96%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromYCbCrVector.cs => JpegColorConverter.YCbCrVector.cs} (89%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromYccKAvx.cs => JpegColorConverter.YccKAvx.cs} (89%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromYccKScalar.cs => JpegColorConverter.YccKScalar.cs} (93%) rename src/ImageSharp/Formats/Jpeg/Components/ColorConverters/{JpegColorConverter.FromYccKVector.cs => JpegColorConverter.YccKVector.cs} (86%) rename src/ImageSharp/Formats/Jpeg/Components/Encoder/{JpegComponent.cs => Component.cs} (95%) rename src/ImageSharp/Formats/Jpeg/Components/Encoder/{JpegComponentPostProcessor.cs => ComponentProcessor.cs} (90%) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index e5494c7dc5..3d2a91a4fd 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -968,10 +968,10 @@ internal static void PackFromRgbPlanesAvx2Reduce( } internal static void UnpackToRgbPlanesAvx2Reduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span source) + ref Span redChannel, + ref Span greenChannel, + ref Span blueChannel, + ref ReadOnlySpan source) { ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); ref Vector256 destRRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs index b45c8c7d6f..b8c14698af 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs @@ -67,10 +67,10 @@ internal static void PackFromRgbPlanes( [MethodImpl(InliningOptions.ShortMethod)] internal static void UnpackToRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span source) + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) { DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); @@ -221,11 +221,15 @@ private static void PackFromRgbPlanesRemainder( } private static void UnpackToRgbPlanesScalar( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span source) + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + ref float r = ref MemoryMarshal.GetReference(redChannel); ref float g = ref MemoryMarshal.GetReference(greenChannel); ref float b = ref MemoryMarshal.GetReference(blueChannel); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index 7edcd95da4..fe92582f47 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -1,10 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; // ReSharper disable UseObjectOrCollectionInitializer // ReSharper disable InconsistentNaming diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs similarity index 97% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs index 989d155fcd..66173b0a5a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykAvx : JpegColorConverterAvx + internal sealed class CmykAvx : JpegColorConverterAvx { - public FromCmykAvx(int precision) + public CmykAvx(int precision) : base(JpegColorSpace.Cmyk, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs similarity index 95% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs index af3decaad0..44ad19687f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs @@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykScalar : JpegColorConverterScalar + internal sealed class CmykScalar : JpegColorConverterScalar { - public FromCmykScalar(int precision) + public CmykScalar(int precision) : base(JpegColorSpace.Cmyk, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs index 6a6fd4f5f5..196f64ead3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykVector : JpegColorConverterVector + internal sealed class CmykVector : JpegColorConverterVector { - public FromCmykVector(int precision) + public CmykVector(int precision) : base(JpegColorSpace.Cmyk, precision) { } @@ -46,7 +46,7 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => FromCmykScalar.ConvertToRgbInplace(values, this.MaximumValue); + => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) { @@ -90,7 +90,7 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => FromCmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); + => CmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs similarity index 77% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs index faa50ba476..89d51586b8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs @@ -13,15 +13,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayscaleAvx : JpegColorConverterAvx + internal sealed class GrayscaleAvx : JpegColorConverterAvx { - public FromGrayscaleAvx(int precision) + public GrayscaleAvx(int precision) : base(JpegColorSpace.Grayscale, precision) { } public override void ConvertToRgbInplace(in ComponentValues values) { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + // Used for the color conversion + var scale = Vector256.Create(1 / this.MaximumValue); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + c0 = Avx.Multiply(c0, scale); + } } public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs similarity index 69% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs index 2430606229..4029830d5e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -2,14 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayscaleScalar : JpegColorConverterScalar + internal sealed class GrayscaleScalar : JpegColorConverterScalar { - public FromGrayscaleScalar(int precision) + public GrayscaleScalar(int precision) : base(JpegColorSpace.Grayscale, precision) { } @@ -22,6 +24,13 @@ public override void ConvertFromRgbInplace(in ComponentValues values, Span values, float maxValue) { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = 1 / maxValue; + + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; + } } internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) @@ -35,7 +44,8 @@ internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); + => GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { @@ -64,7 +64,7 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => FromGrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); + => GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs similarity index 94% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs index 141f323ec8..6731d535ee 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbAvx : JpegColorConverterAvx + internal sealed class RgbAvx : JpegColorConverterAvx { - public FromRgbAvx(int precision) + public RgbAvx(int precision) : base(JpegColorSpace.RGB, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs similarity index 73% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs index 64b357d54e..42cd002427 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs @@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbScalar : JpegColorConverterScalar + internal sealed class RgbScalar : JpegColorConverterScalar { - public FromRgbScalar(int precision) + public RgbScalar(int precision) : base(JpegColorSpace.RGB, precision) { } @@ -22,9 +22,9 @@ public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs similarity index 88% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs index 72a8ef1298..f4c4fa379c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs @@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbVector : JpegColorConverterVector + internal sealed class RgbVector : JpegColorConverterVector { - public FromRgbVector(int precision) + public RgbVector(int precision) : base(JpegColorSpace.RGB, precision) { } @@ -41,7 +41,7 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => FromRgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); + => RgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) { @@ -51,7 +51,7 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => FromRgbScalar.ConvertCoreInplaceFromRgb(values, r, g, b); + => RgbScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs index 678df453c8..46e9fd033e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrAvx : JpegColorConverterAvx + internal sealed class YCbCrAvx : JpegColorConverterAvx { - public FromYCbCrAvx(int precision) + public YCbCrAvx(int precision) : base(JpegColorSpace.YCbCr, precision) { } @@ -33,10 +33,10 @@ public override void ConvertToRgbInplace(in ComponentValues values) // Used for the color conversion var chromaOffset = Vector256.Create(-this.HalfValue); var scale = Vector256.Create(1 / this.MaximumValue); - var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); - var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); - var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); - var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); + var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); // Walking 8 elements at one step: nint n = values.Component0.Length / Vector256.Count; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs similarity index 96% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs index 424ab83eb3..e5f76dadfd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrScalar : JpegColorConverterScalar + internal sealed class YCbCrScalar : JpegColorConverterScalar { // TODO: comments, derived from ITU-T Rec. T.871 internal const float RCrMult = 1.402f; @@ -15,7 +15,7 @@ internal sealed class FromYCbCrScalar : JpegColorConverterScalar internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); internal const float BCbMult = 1.772f; - public FromYCbCrScalar(int precision) + public YCbCrScalar(int precision) : base(JpegColorSpace.YCbCr, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs index 022c09b497..bf0aa4a341 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs @@ -11,9 +11,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrVector : JpegColorConverterVector + internal sealed class YCbCrVector : JpegColorConverterVector { - public FromYCbCrVector(int precision) + public YCbCrVector(int precision) : base(JpegColorSpace.YCbCr, precision) { } @@ -30,10 +30,10 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val var chromaOffset = new Vector(-this.HalfValue); var scale = new Vector(1 / this.MaximumValue); - var rCrMult = new Vector(FromYCbCrScalar.RCrMult); - var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); - var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); - var bCbMult = new Vector(FromYCbCrScalar.BCbMult); + var rCrMult = new Vector(YCbCrScalar.RCrMult); + var gCbMult = new Vector(-YCbCrScalar.GCbMult); + var gCrMult = new Vector(-YCbCrScalar.GCrMult); + var bCbMult = new Vector(YCbCrScalar.BCbMult); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) @@ -69,7 +69,7 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => FromYCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); + => YCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { @@ -118,7 +118,7 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => FromYCbCrScalar.ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); + => YCbCrScalar.ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs index 6166f342b3..58d024e376 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKAvx : JpegColorConverterAvx + internal sealed class YccKAvx : JpegColorConverterAvx { - public FromYccKAvx(int precision) + public YccKAvx(int precision) : base(JpegColorSpace.Ycck, precision) { } @@ -35,10 +35,10 @@ public override void ConvertToRgbInplace(in ComponentValues values) var chromaOffset = Vector256.Create(-this.HalfValue); var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); var max = Vector256.Create(this.MaximumValue); - var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); - var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); - var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); - var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); + var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); // Walking 8 elements at one step: nint n = values.Component0.Length / Vector256.Count; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index bf5d1c4140..f49e819b9e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKScalar : JpegColorConverterScalar + internal sealed class YccKScalar : JpegColorConverterScalar { - public FromYccKScalar(int precision) + public YccKScalar(int precision) : base(JpegColorSpace.Ycck, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs similarity index 86% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index cc4212d473..7a5597c463 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKVector : JpegColorConverterVector + internal sealed class YccKVector : JpegColorConverterVector { - public FromYccKVector(int precision) + public YccKVector(int precision) : base(JpegColorSpace.Ycck, precision) { } @@ -31,10 +31,10 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val var chromaOffset = new Vector(-this.HalfValue); var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); var max = new Vector(this.MaximumValue); - var rCrMult = new Vector(FromYCbCrScalar.RCrMult); - var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); - var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); - var bCbMult = new Vector(FromYCbCrScalar.BCbMult); + var rCrMult = new Vector(YCbCrScalar.RCrMult); + var gCbMult = new Vector(-YCbCrScalar.GCbMult); + var gCrMult = new Vector(-YCbCrScalar.GCrMult); + var bCbMult = new Vector(YCbCrScalar.BCbMult); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) @@ -70,7 +70,7 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => - FromYccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) => throw new System.NotImplementedException(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 500c25a951..744c178e89 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -110,9 +110,9 @@ private static JpegColorConverterBase[] CreateConverters() /// private static IEnumerable GetYCbCrConverters(int precision) { - yield return new FromYCbCrAvx(precision); - yield return new FromYCbCrVector(precision); - yield return new FromYCbCrScalar(precision); + yield return new YCbCrAvx(precision); + yield return new YCbCrVector(precision); + yield return new YCbCrScalar(precision); } /// @@ -120,9 +120,9 @@ private static IEnumerable GetYCbCrConverters(int precis /// private static IEnumerable GetYccKConverters(int precision) { - yield return new FromYccKAvx(precision); - yield return new FromYccKVector(precision); - yield return new FromYccKScalar(precision); + yield return new YccKAvx(precision); + yield return new YccKVector(precision); + yield return new YccKScalar(precision); } /// @@ -130,9 +130,9 @@ private static IEnumerable GetYccKConverters(int precisi /// private static IEnumerable GetCmykConverters(int precision) { - yield return new FromCmykAvx(precision); - yield return new FromCmykVector(precision); - yield return new FromCmykScalar(precision); + yield return new CmykAvx(precision); + yield return new CmykVector(precision); + yield return new CmykScalar(precision); } /// @@ -140,9 +140,9 @@ private static IEnumerable GetCmykConverters(int precisi /// private static IEnumerable GetGrayScaleConverters(int precision) { - yield return new FromGrayscaleAvx(precision); - yield return new FromGrayScaleVector(precision); - yield return new FromGrayscaleScalar(precision); + yield return new GrayscaleAvx(precision); + yield return new GrayScaleVector(precision); + yield return new GrayscaleScalar(precision); } /// @@ -150,9 +150,9 @@ private static IEnumerable GetGrayScaleConverters(int pr /// private static IEnumerable GetRgbConverters(int precision) { - yield return new FromRgbAvx(precision); - yield return new FromRgbVector(precision); - yield return new FromRgbScalar(precision); + yield return new RgbAvx(precision); + yield return new RgbVector(precision); + yield return new RgbScalar(precision); } /// @@ -230,7 +230,7 @@ public ComponentValues(IReadOnlyList process /// /// List of component color processors. /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) + public ComponentValues(IReadOnlyList processors, int row) { DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs similarity index 95% rename from src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs rename to src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs index 49c7d4257e..ccb4413abd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Represents a single frame component. /// - internal class JpegComponent : IDisposable + internal class Component : IDisposable { private readonly MemoryAllocator memoryAllocator; - public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) + public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) { this.memoryAllocator = memoryAllocator; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs similarity index 90% rename from src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs rename to src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index fb9f72a1a1..400ce41e24 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -11,26 +11,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { - internal class JpegComponentPostProcessor : IDisposable + internal class ComponentProcessor : IDisposable { private readonly Size blockAreaSize; - private readonly JpegComponent component; + private readonly Component component; private Block8x8F quantTable; - public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegComponent component, Size postProcessorBufferSize, Block8x8F quantTable) + public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable) { this.component = component; this.quantTable = quantTable; - FastFloatingPointDCT.AdjustToFDCT(ref this.quantTable); this.component = component; this.blockAreaSize = component.SubSamplingDivisors * 8; + + // alignment of 8 so each block stride can be sampled from a single 'ref pointer' this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( postProcessorBufferSize.Width, postProcessorBufferSize.Height, - 8 * component.SubSamplingDivisors.Height, + 8, AllocationOptions.Clean); } @@ -47,7 +48,7 @@ public void CopyColorBufferToBlocks(int spectralStep) // but 12-bit jpegs are not supported currently float normalizationValue = -128f; - int destAreaStride = this.ColorBuffer.Width * this.component.SubSamplingDivisors.Height; + int destAreaStride = this.ColorBuffer.Width; int yBlockStart = spectralStep * this.component.SamplingFactors.Height; @@ -97,22 +98,27 @@ private void PackColorBuffer() { Size factors = this.component.SubSamplingDivisors; + int packedWidth = this.ColorBuffer.Width / factors.Width; + float averageMultiplier = 1f / (factors.Width * factors.Height); for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) { - Span targetBufferRow = this.ColorBuffer.DangerousGetRowSpan(i); + Span sourceRow = this.ColorBuffer.DangerousGetRowSpan(i); // vertical sum for (int j = 1; j < factors.Height; j++) { - SumVertical(targetBufferRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); + SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); } // horizontal sum - SumHorizontal(targetBufferRow, factors.Width); + SumHorizontal(sourceRow, factors.Width); // calculate average - MultiplyToAverage(targetBufferRow, averageMultiplier); + MultiplyToAverage(sourceRow, averageMultiplier); + + // copy to the first 8 slots + sourceRow.Slice(0, packedWidth).CopyTo(this.ColorBuffer.DangerousGetRowSpan(i / factors.Height)); } static void SumVertical(Span target, Span source) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs new file mode 100644 index 0000000000..b9a589e21f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegComponentConfig + { + public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + { + this.Id = id; + this.HorizontalSampleFactor = hsf; + this.VerticalSampleFactor = vsf; + this.QuantizatioTableIndex = quantIndex; + this.DcTableSelector = dcIndex; + this.AcTableSelector = acIndex; + } + + public byte Id { get; } + + public int HorizontalSampleFactor { get; } + + public int VerticalSampleFactor { get; } + + public int QuantizatioTableIndex { get; } + + public int DcTableSelector { get; } + + public int AcTableSelector { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs new file mode 100644 index 0000000000..0bb0f17d13 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegFrameConfig + { + public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) + { + this.ColorType = colorType; + this.EncodingColor = encodingColor; + this.Components = components; + this.HuffmanTables = huffmanTables; + this.QuantizationTables = quantTables; + + this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; + this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; + for (int i = 1; i < components.Length; i++) + { + JpegComponentConfig component = components[i]; + this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); + this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); + } + } + + public JpegColorSpace ColorType { get; } + + public JpegEncodingColor EncodingColor { get; } + + public JpegComponentConfig[] Components { get; } + + public JpegHuffmanTableConfig[] HuffmanTables { get; } + + public JpegQuantizationTableConfig[] QuantizationTables { get; } + + public int MaxHorizontalSamplingFactor { get; } + + public int MaxVerticalSamplingFactor { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs new file mode 100644 index 0000000000..d0c3038db7 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegHuffmanTableConfig + { + public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) + { + this.Class = @class; + this.DestinationIndex = destIndex; + this.Table = table; + } + + public int Class { get; } + + public int DestinationIndex { get; } + + public HuffmanSpec Table { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs new file mode 100644 index 0000000000..29f0c05db8 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal class JpegQuantizationTableConfig + { + public JpegQuantizationTableConfig(int destIndex, ReadOnlySpan quantizationTable) + { + this.DestinationIndex = destIndex; + this.Table = Block8x8.Load(quantizationTable); + } + + public int DestinationIndex { get; } + + public Block8x8 Table { get; } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index c989346fcb..08beb46331 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -135,6 +135,13 @@ public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); } + /// + /// Encodes scan in baseline interleaved mode. + /// + /// Output color space. + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { @@ -142,20 +149,21 @@ public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegF { case JpegEncodingColor.YCbCrRatio444: case JpegEncodingColor.Rgb: - this.EncodeScanBaselineInterleaved444(frame, converter, cancellationToken); - break; - case JpegEncodingColor.YCbCrRatio420: - this.EncodeScanBaselineInterleaved420(frame, converter, cancellationToken); + this.EncodeThreeComponentScanBaselineInterleaved444(frame, converter, cancellationToken); break; default: - this.EncodeScanBaselineInterleavedArbitrarySampling(frame, converter, cancellationToken); + this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken); break; } - - this.FlushRemainingBytes(); } - public void EncodeScanBaselineSingleComponent(JpegComponent component, SpectralConverter converter, CancellationToken cancellationToken) + /// + /// Encodes grayscale scan in baseline interleaved mode. + /// + /// Component with grayscale data. + /// Converter from color to spectral. + /// The token to request cancellation. + public void EncodeScanBaselineSingleComponent(Component component, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int h = component.HeightInBlocks; @@ -189,9 +197,16 @@ ref Unsafe.Add(ref blockRef, k), } } } + + this.FlushRemainingBytes(); } - public void EncodeScanBaseline(JpegComponent component, CancellationToken cancellationToken) + /// + /// Encodes scan with a single component in baseline non-interleaved mode. + /// + /// Component with grayscale data. + /// The token to request cancellation. + public void EncodeScanBaseline(Component component, CancellationToken cancellationToken) { int h = component.HeightInBlocks; int w = component.WidthInBlocks; @@ -225,7 +240,13 @@ ref Unsafe.Add(ref blockRef, k), this.FlushRemainingBytes(); } - private void EncodeScanBaselineInterleavedArbitrarySampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + /// + /// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors. + /// + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int mcu = 0; @@ -246,7 +267,7 @@ private void EncodeScanBaselineInterleavedArbitrarySampling(JpegFrame fr int mcuCol = mcu % mcusPerLine; for (int k = 0; k < frame.Components.Length; k++) { - JpegComponent component = frame.Components[k]; + Component component = frame.Components[k]; ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; @@ -282,17 +303,25 @@ ref Unsafe.Add(ref blockRef, blockCol), } } } + + this.FlushRemainingBytes(); } - private void EncodeScanBaselineInterleaved444(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + /// + /// Encodes scan in baseline interleaved mode with exactly 3 components with 4:4:4 sampling. + /// + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + private void EncodeThreeComponentScanBaselineInterleaved444(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int mcusPerColumn = frame.McusPerColumn; int mcusPerLine = frame.McusPerLine; - JpegComponent c2 = frame.Components[2]; - JpegComponent c1 = frame.Components[1]; - JpegComponent c0 = frame.Components[0]; + Component c2 = frame.Components[2]; + Component c1 = frame.Components[1]; + Component c0 = frame.Components[0]; ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; @@ -339,16 +368,12 @@ ref Unsafe.Add(ref c2BlockRef, i), } } } - } - private void EncodeScanBaselineInterleaved420(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - throw new NotImplementedException(); + this.FlushRemainingBytes(); } private void WriteBlock( - JpegComponent component, + Component component, ref Block8x8 block, ref HuffmanLut dcTable, ref HuffmanLut acTable) @@ -611,7 +636,7 @@ private void FlushToStream(int endIndex) /// /// Flushes spectral data bytes after encoding all channel blocks - /// in a single jpeg macroblock using . + /// in a single jpeg macroblock using . /// /// /// This must be called only if is true @@ -641,7 +666,7 @@ private void FlushRemainingBytes() int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; this.FlushToStream(lastByteIndex); - // Clean huffman register + // Clear huffman register // This is needed for for images with multiples scans this.bitCount = 0; this.accumulatedBits = 0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index a6a9bcb0a9..d45de2c16a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder @@ -11,19 +12,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal sealed class JpegFrame : IDisposable { - public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, JpegColorSpace colorSpace) + public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved) { - this.ColorSpace = colorSpace; + this.ColorSpace = frameConfig.ColorType; + + this.Interleaved = interleaved; this.PixelWidth = image.Width; this.PixelHeight = image.Height; + MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; + JpegComponentConfig[] componentConfigs = frameConfig.Components; - this.Components = new JpegComponent[componentConfigs.Length]; + this.Components = new Component[componentConfigs.Length]; for (int i = 0; i < this.Components.Length; i++) { JpegComponentConfig componentConfig = componentConfigs[i]; - this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) + this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) { DcTableId = componentConfig.DcTableSelector, AcTableId = componentConfig.AcTableSelector, @@ -39,18 +44,20 @@ public JpegFrame(JpegFrameConfig frameConfig, MemoryAllocator allocator, Image i for (int i = 0; i < this.Components.Length; i++) { - JpegComponent component = this.Components[i]; + Component component = this.Components[i]; component.Init(this, maxSubFactorH, maxSubFactorV); } } public JpegColorSpace ColorSpace { get; } - public int PixelHeight { get; private set; } + public bool Interleaved { get; } + + public int PixelHeight { get; } - public int PixelWidth { get; private set; } + public int PixelWidth { get; } - public JpegComponent[] Components { get; } + public Component[] Components { get; } public int McusPerLine { get; } @@ -62,7 +69,7 @@ public void Dispose() { for (int i = 0; i < this.Components.Length; i++) { - this.Components[i]?.Dispose(); + this.Components[i].Dispose(); } } @@ -70,7 +77,7 @@ public void AllocateComponents(bool fullScan) { for (int i = 0; i < this.Components.Length; i++) { - JpegComponent component = this.Components[i]; + Component component = this.Components[i]; component.AllocateSpectral(fullScan); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 3445072858..18c0e63cfc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -4,40 +4,37 @@ using System; using System.Buffers; using System.Linq; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// - internal class SpectralConverter : SpectralConverter + internal class SpectralConverter : SpectralConverter, IDisposable where TPixel : unmanaged, IPixel { - private readonly Configuration configuration; + private readonly ComponentProcessor[] componentProcessors; - private JpegComponentPostProcessor[] componentProcessors; - - private int pixelRowsPerStep; + private readonly int pixelRowsPerStep; private int pixelRowCounter; - private Buffer2D pixelBuffer; + private readonly Buffer2D pixelBuffer; - private IMemoryOwner redLane; + private readonly IMemoryOwner redLane; - private IMemoryOwner greenLane; + private readonly IMemoryOwner greenLane; - private IMemoryOwner blueLane; + private readonly IMemoryOwner blueLane; - private int alignedPixelWidth; + private readonly int alignedPixelWidth; - private JpegColorConverterBase colorConverter; + private readonly JpegColorConverterBase colorConverter; - public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables, Configuration configuration) + public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables) { - this.configuration = configuration; - - MemoryAllocator allocator = this.configuration.MemoryAllocator; + MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; // iteration data int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); @@ -47,23 +44,26 @@ public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequa this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; // pixel buffer of the image - // currently codec only supports encoding single frame jpegs this.pixelBuffer = image.GetRootFramePixelBuffer(); - // component processors from spectral to Rgba32 + // component processors from spectral to Rgb24 const int blockPixelWidth = 8; this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); - this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; + this.componentProcessors = new ComponentProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { - JpegComponent component = frame.Components[i]; - this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, component, postProcessorBufferSize, dequantTables[component.QuantizationTableIndex]); + Component component = frame.Components[i]; + this.componentProcessors[i] = new ComponentProcessor( + allocator, + component, + postProcessorBufferSize, + dequantTables[component.QuantizationTableIndex]); } - this.redLane = allocator.Allocate(this.alignedPixelWidth); - this.greenLane = allocator.Allocate(this.alignedPixelWidth); - this.blueLane = allocator.Allocate(this.alignedPixelWidth); + this.redLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + this.greenLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + this.blueLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); // color converter from Rgb24 to YCbCr this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); @@ -71,10 +71,15 @@ public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequa public void ConvertStrideBaseline() { + // Codestyle suggests expression body but it + // also requires empty line before comments + // which looks ugly with expression bodies thus this warning disable +#pragma warning disable IDE0022 // Convert next pixel stride using single spectral `stride' // Note that zero passing eliminates the need of virtual call // from JpegComponentPostProcessor this.ConvertStride(spectralStep: 0); +#pragma warning restore IDE0022 } public void ConvertFull() @@ -88,34 +93,48 @@ public void ConvertFull() private void ConvertStride(int spectralStep) { - // 1. Unpack from TPixel to r/g/b planes - // 2. Byte r/g/b planes to normalized float r/g/b planes - // 3. Convert from r/g/b planes to target pixel type with JpegColorConverter - // 4. Convert color buffer to spectral blocks with component post processors - int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); + int start = this.pixelRowCounter; + int end = start + this.pixelRowsPerStep; + + int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1; Span rLane = this.redLane.GetSpan(); Span gLane = this.greenLane.GetSpan(); Span bLane = this.blueLane.GetSpan(); - for (int yy = this.pixelRowCounter; yy < maxY; yy++) + for (int yy = start; yy < end; yy++) { int y = yy - this.pixelRowCounter; - // unpack TPixel to r/g/b planes - Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(yy); - - PixelOperations.Instance.UnpackIntoRgbPlanes(this.configuration, rLane, gLane, bLane, sourceRow); + // Unpack TPixel to r/g/b planes + int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex); + Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); + PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); + // Convert from rgb24 to target pixel type var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); this.colorConverter.ConvertFromRgbInplace(values, rLane, gLane, bLane); } + // Convert pixels to spectral for (int i = 0; i < this.componentProcessors.Length; i++) { this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); } - this.pixelRowCounter += this.pixelRowsPerStep; + this.pixelRowCounter = end; + } + + /// + public void Dispose() + { + foreach (ComponentProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } + + this.redLane.Dispose(); + this.greenLane.Dispose(); + this.blueLane.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index de55904212..449db9d81b 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -23,5 +23,10 @@ internal interface IJpegEncoderOptions /// Non-interleaved encoding mode encodes each color component in a separate scan. /// public bool? Interleaved { get; set; } + + /// + /// Gets or sets jpeg color for encoding. + /// + public JpegEncodingColor? ColorType { get; set; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 25e31a2362..0efbedff1d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -564,17 +564,17 @@ private JpegEncodingColor DeduceJpegColorType() { return JpegEncodingColor.YCbCrRatio444; } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && + else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegEncodingColor.YCbCrRatio420; + return JpegEncodingColor.YCbCrRatio422; } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2) + else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) { - return JpegEncodingColor.YCbCrRatio422; + return JpegEncodingColor.YCbCrRatio420; } else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && @@ -595,7 +595,13 @@ private JpegEncodingColor DeduceJpegColorType() case JpegColorSpace.Cmyk: return JpegEncodingColor.Cmyk; + case JpegColorSpace.Ycck: + // TODO: change this after YccK encoding is implemented + // We are deliberately mapping YccK color space to Cmyk color space at metadata + // level so encoder can fallback to cmyk color space from it. + // YccK -> Cmyk is the closest conversion logically wise + return JpegEncodingColor.Cmyk; default: return JpegEncodingColor.YCbCrRatio420; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index c2a50b37e2..5817eaa335 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.PixelFormats; @@ -17,36 +16,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { /// - /// The available encodable frame configs. + /// Backing field for . /// - private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); + private int? quality; /// - public int? Quality { get; set; } - - /// - public bool? Interleaved { get; set; } - - /// - /// Sets jpeg color for encoding. - /// - public JpegEncodingColor ColorType + public int? Quality { + get => this.quality; set { - JpegFrameConfig frameConfig = Array.Find( - FrameConfigs, - cfg => cfg.EncodingColor == value); - - if (frameConfig is null) + if (value is < 1 or > 100) { - throw new ArgumentException(nameof(value)); + throw new ArgumentException("Quality factor must be in [1..100] range."); } - this.FrameConfig = frameConfig; + this.quality = value; } } + /// + public bool? Interleaved { get; set; } + + /// + public JpegEncodingColor? ColorType { get; set; } + internal JpegFrameConfig FrameConfig { get; set; } /// @@ -58,7 +52,7 @@ public JpegEncodingColor ColorType public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.FrameConfig); + var encoder = new JpegEncoderCore(this); encoder.Encode(image, stream); } @@ -73,249 +67,8 @@ public void Encode(Image image, Stream stream) public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this, this.FrameConfig); + var encoder = new JpegEncoderCore(this); return encoder.EncodeAsync(image, stream, cancellationToken); } - - private static JpegFrameConfig[] CreateFrameConfigs() - { - var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[0]); - var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[1]); - var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[2]); - var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[3]); - - var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Block8x8.Load(Quantization.LuminanceTable)); - var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Block8x8.Load(Quantization.ChrominanceTable)); - - var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC, - defaultChrominanceHuffmanDC, - defaultChrominanceHuffmanAC, - }; - - var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable, - defaultChrominanceQuantTable, - }; - - return new JpegFrameConfig[] - { - // YCbCr 4:4:4 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio444, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:2:2 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio422, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:2:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio420, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:1:1 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio411, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:1:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio410, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // Luminance - new JpegFrameConfig( - JpegColorSpace.Grayscale, - JpegEncodingColor.Luminance, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }), - - // Rgb - new JpegFrameConfig( - JpegColorSpace.RGB, - JpegEncodingColor.Rgb, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }), - - // Cmyk - new JpegFrameConfig( - JpegColorSpace.Cmyk, - JpegEncodingColor.Cmyk, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }), - }; - } - } - - internal class JpegFrameConfig - { - public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) - { - this.ColorType = colorType; - this.EncodingColor = encodingColor; - this.Components = components; - this.HuffmanTables = huffmanTables; - this.QuantizationTables = quantTables; - - this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; - this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; - for (int i = 1; i < components.Length; i++) - { - JpegComponentConfig component = components[i]; - this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); - this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); - } - } - - public JpegColorSpace ColorType { get; } - - public JpegEncodingColor EncodingColor { get; } - - public JpegComponentConfig[] Components { get; } - - public JpegHuffmanTableConfig[] HuffmanTables { get; } - - public JpegQuantizationTableConfig[] QuantizationTables { get; } - - public int MaxHorizontalSamplingFactor { get; } - - public int MaxVerticalSamplingFactor { get; } - } - - internal class JpegComponentConfig - { - public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) - { - this.Id = id; - this.HorizontalSampleFactor = hsf; - this.VerticalSampleFactor = vsf; - this.QuantizatioTableIndex = quantIndex; - this.DcTableSelector = dcIndex; - this.AcTableSelector = acIndex; - } - - public byte Id { get; } - - public int HorizontalSampleFactor { get; } - - public int VerticalSampleFactor { get; } - - public int QuantizatioTableIndex { get; } - - public int DcTableSelector { get; } - - public int AcTableSelector { get; } - } - - internal class JpegHuffmanTableConfig - { - public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) - { - this.Class = @class; - this.DestinationIndex = destIndex; - this.Table = table; - } - - public int Class { get; } - - public int DestinationIndex { get; } - - public HuffmanSpec Table { get; } - } - - internal class JpegQuantizationTableConfig - { - public JpegQuantizationTableConfig(int destIndex, Block8x8 table) - { - this.DestinationIndex = destIndex; - this.Table = table; - } - - public int DestinationIndex { get; } - - public Block8x8 Table { get; } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index ae6c44f51f..fc13876213 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -24,27 +24,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals { /// - /// The number of quantization tables. + /// The available encodable frame configs. /// - private const int QuantizationTableCount = 2; + private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); /// /// A scratch buffer to reduce allocations. /// private readonly byte[] buffer = new byte[20]; - /// - /// The quality, that will be used to encode the image. - /// - private readonly int? quality; - - private readonly bool? interleaved; - - private JpegEncodingColor? colorType; - - private JpegFrameConfig frameConfig; - - private HuffmanScanEncoder scanEncoder; + private readonly IJpegEncoderOptions options; /// /// The output stream. All attempted writes after the first error become no-ops. @@ -55,15 +44,8 @@ internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals /// Initializes a new instance of the class. /// /// The options. - /// Frame config. - public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) - { - this.quality = options.Quality; - this.interleaved = options.Interleaved; - - this.frameConfig = frameConfig; - this.colorType = frameConfig.EncodingColor; - } + public JpegEncoderCore(IJpegEncoderOptions options) + => this.options = options; public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; @@ -87,68 +69,46 @@ public void Encode(Image image, Stream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); - var frame = new JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, this.frameConfig.ColorType); - this.scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); - this.outputStream = stream; + ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); + JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata); + + bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true; + using var frame = new JpegFrame(image, frameConfig, interleaved); // Write the Start Of Image marker. this.WriteStartOfImage(); - // Do not write APP0 marker for RGB colorspace. - if (this.colorType != JpegEncodingColor.Rgb) + // Write APP0 marker for any non-RGB colorspace image. + if (frameConfig.EncodingColor != JpegEncodingColor.Rgb) { this.WriteJfifApplicationHeader(metadata); } - // Write Exif, XMP, ICC and IPTC profiles - this.WriteProfiles(metadata); - - if (this.colorType == JpegEncodingColor.Rgb) + // Else write App14 marker to indicate RGB color space. + else { - // Write App14 marker to indicate RGB color space. this.WriteApp14Marker(); } + // Write Exif, XMP, ICC and IPTC profiles + this.WriteProfiles(metadata); + // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); + this.WriteStartOfFrame(image.Width, image.Height, frameConfig); // Write the Huffman tables. - this.WriteDefineHuffmanTables(this.frameConfig.HuffmanTables); + var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); + this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder); // Write the quantization tables. - this.WriteDefineQuantizationTables(this.frameConfig.QuantizationTables, jpegMetadata); + this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata); - var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables, Configuration.Default); - - if (frame.Components.Length == 1) - { - frame.AllocateComponents(fullScan: false); - - this.WriteStartOfScan(this.frameConfig.Components); - this.scanEncoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); - } - else if (this.interleaved ?? jpegMetadata.Interleaved ?? true) - { - frame.AllocateComponents(fullScan: false); - - this.WriteStartOfScan(this.frameConfig.Components); - this.scanEncoder.EncodeScanBaselineInterleaved(this.frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); - } - else - { - frame.AllocateComponents(fullScan: true); - spectralConverter.ConvertFull(); - - Span components = this.frameConfig.Components; - for (int i = 0; i < frame.Components.Length; i++) - { - this.WriteStartOfScan(components.Slice(i, 1)); - this.scanEncoder.EncodeScanBaseline(frame.Components[i], cancellationToken); - } - } + // Write scans with actual pixel data + using var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables); + this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); @@ -216,7 +176,7 @@ private void WriteJfifApplicationHeader(ImageMetadata meta) /// /// Writes the Define Huffman Table marker and tables. /// - private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs) + private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder) { if (tableConfigs is null) { @@ -240,7 +200,7 @@ private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs) this.outputStream.Write(tableConfig.Table.Count); this.outputStream.Write(tableConfig.Table.Values); - this.scanEncoder.BuildHuffmanTable(tableConfig); + scanEncoder.BuildHuffmanTable(tableConfig); } } @@ -629,6 +589,40 @@ private void WriteEndOfImageMarker() this.outputStream.Write(this.buffer, 0, 2); } + /// + /// Writes scans for given config. + /// + private void WriteHuffmanScans(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + if (frame.Components.Length == 1) + { + frame.AllocateComponents(fullScan: false); + + this.WriteStartOfScan(frameConfig.Components); + encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); + } + else if (frame.Interleaved) + { + frame.AllocateComponents(fullScan: false); + + this.WriteStartOfScan(frameConfig.Components); + encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); + } + else + { + frame.AllocateComponents(fullScan: true); + spectralConverter.ConvertFull(); + + Span components = frameConfig.Components; + for (int i = 0; i < frame.Components.Length; i++) + { + this.WriteStartOfScan(components.Slice(i, 1)); + encoder.EncodeScanBaseline(frame.Components[i], cancellationToken); + } + } + } + /// /// Writes the header for a marker with the given length. /// @@ -656,8 +650,9 @@ private void WriteMarkerHeader(byte marker, int length) /// /// /// Quantization tables configs. + /// Optional quality value from the options. /// Jpeg metadata instance. - private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata) + private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata) { int dataLen = configs.Length * (1 + Block8x8.Size); @@ -668,11 +663,13 @@ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs byte[] buffer = new byte[dataLen]; int offset = 0; + Block8x8F workspaceBlock = default; + for (int i = 0; i < configs.Length; i++) { JpegQuantizationTableConfig config = configs[i]; - int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata); + int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata); Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); // write to the output stream @@ -683,8 +680,11 @@ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; } - // apply scaling and save into buffer - this.QuantizationTables[config.DestinationIndex].LoadFromInt16Scalar(ref scaledTable); + // apply FDCT multipliers and inject to the destination index + workspaceBlock.LoadFrom(ref scaledTable); + FastFloatingPointDCT.AdjustToFDCT(ref workspaceBlock); + + this.QuantizationTables[config.DestinationIndex] = workspaceBlock; } // write filled buffer to the stream @@ -692,10 +692,177 @@ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch { - 0 => encoderQuality ?? metadata.LuminanceQuality, - 1 => encoderQuality ?? metadata.ChrominanceQuality, + 0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor, + 1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor, _ => encoderQuality ?? metadata.Quality, }; } + + private JpegFrameConfig GetFrameConfig(JpegMetadata metadata) + { + JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420; + JpegFrameConfig frameConfig = Array.Find( + FrameConfigs, + cfg => cfg.EncodingColor == color); + + if (frameConfig == null) + { + throw new ArgumentException(nameof(color)); + } + + return frameConfig; + } + + private static JpegFrameConfig[] CreateFrameConfigs() + { + var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[0]); + var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[1]); + var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[2]); + var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[3]); + + var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable); + var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable); + + var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC, + defaultChrominanceHuffmanDC, + defaultChrominanceHuffmanAC, + }; + + var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable, + defaultChrominanceQuantTable, + }; + + return new JpegFrameConfig[] + { + // YCbCr 4:4:4 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio444, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:2 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio422, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio420, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:1 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio411, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio410, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // Luminance + new JpegFrameConfig( + JpegColorSpace.Grayscale, + JpegEncodingColor.Luminance, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + + // Rgb + new JpegFrameConfig( + JpegColorSpace.RGB, + JpegEncodingColor.Rgb, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + + // Cmyk + new JpegFrameConfig( + JpegColorSpace.Cmyk, + JpegEncodingColor.Cmyk, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + }; + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index b878d26fb8..ef253cfebc 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -11,16 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public class JpegMetadata : IDeepCloneable { - /// - /// Backing field for - /// - private int? luminanceQuality; - - /// - /// Backing field for - /// - private int? chrominanceQuality; - /// /// Initializes a new instance of the class. /// @@ -36,8 +26,8 @@ private JpegMetadata(JpegMetadata other) { this.ColorType = other.ColorType; - this.luminanceQuality = other.luminanceQuality; - this.chrominanceQuality = other.chrominanceQuality; + this.LuminanceQuality = other.LuminanceQuality; + this.ChrominanceQuality = other.ChrominanceQuality; } /// @@ -47,11 +37,7 @@ private JpegMetadata(JpegMetadata other) /// This value might not be accurate if it was calculated during jpeg decoding /// with non-complient ITU quantization tables. /// - internal int LuminanceQuality - { - get => this.luminanceQuality ?? Quantization.DefaultQualityFactor; - set => this.luminanceQuality = value; - } + internal int? LuminanceQuality { get; set; } /// /// Gets or sets the jpeg chrominance quality. @@ -60,11 +46,7 @@ internal int LuminanceQuality /// This value might not be accurate if it was calculated during jpeg decoding /// with non-complient ITU quantization tables. /// - internal int ChrominanceQuality - { - get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor; - set => this.chrominanceQuality = value; - } + internal int? ChrominanceQuality { get; set; } /// /// Gets the encoded quality. @@ -77,20 +59,20 @@ public int Quality { get { - if (this.luminanceQuality.HasValue) + if (this.LuminanceQuality.HasValue) { - if (this.chrominanceQuality.HasValue) + if (this.ChrominanceQuality.HasValue) { - return Math.Max(this.luminanceQuality.Value, this.chrominanceQuality.Value); + return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value); } - return this.luminanceQuality.Value; + return this.LuminanceQuality.Value; } else { - if (this.chrominanceQuality.HasValue) + if (this.ChrominanceQuality.HasValue) { - return this.chrominanceQuality.Value; + return this.ChrominanceQuality.Value; } return Quantization.DefaultQualityFactor; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs index 5f8a3e95f5..3e96c9fb46 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs @@ -39,12 +39,11 @@ internal override void PackFromRgbPlanes( /// internal override void UnpackIntoRgbPlanes( - Configuration configuration, - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span source) - => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index dcc3617957..e753f24aa3 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -202,32 +202,27 @@ internal virtual void PackFromRgbPlanes( /// Bulk operation that unpacks pixels from /// into 3 seperate RGB channels. The destination must have a padding of 3. /// - /// A to configure internal operations. /// A to the red values. /// A to the green values. /// A to the blue values. /// A to the destination pixels. internal virtual void UnpackIntoRgbPlanes( - Configuration configuration, - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span source) + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) { - Guard.NotNull(configuration, nameof(configuration)); - int count = redChannel.Length; Rgba32 rgba32 = default; + ref float r = ref MemoryMarshal.GetReference(redChannel); ref float g = ref MemoryMarshal.GetReference(greenChannel); ref float b = ref MemoryMarshal.GetReference(blueChannel); - ref TPixel d = ref MemoryMarshal.GetReference(source); - + ref TPixel src = ref MemoryMarshal.GetReference(source); for (int i = 0; i < count; i++) { - // TODO: Create ToRgb24 method in IPixel - Unsafe.Add(ref d, i).ToRgba32(ref rgba32); + Unsafe.Add(ref src, i).ToRgba32(ref rgba32); Unsafe.Add(ref r, i) = rgba32.R; Unsafe.Add(ref g, i) = rgba32.G; Unsafe.Add(ref b, i) = rgba32.B; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 11125357ca..9d1e5c13f1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.CmykVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 48e6332eed..ef20570d2f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromGrayscaleScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.GrayscaleScalar(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -28,7 +28,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromGrayscaleAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 438626b4b6..225ecbba9e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.RgbVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.RgbAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 6f65f1b853..b5113797ec 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index d03fa5e83e..1b73b26a10 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -19,7 +19,7 @@ public void Scalar() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKScalar(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -27,7 +27,7 @@ public void SimdVector8() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKVector(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YccKVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -36,7 +36,7 @@ public void SimdVectorAvx2() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKAvx(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values); } #endif } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs index b115550f93..6844883486 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -22,8 +22,13 @@ public class EncodeJpegFeatures // No metadata private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - public static IEnumerable ColorSpaceValues => - new[] { JpegEncodingColor.Luminance, JpegEncodingColor.Rgb, JpegEncodingColor.YCbCrRatio420, JpegEncodingColor.YCbCrRatio444 }; + public static IEnumerable ColorSpaceValues => new[] + { + JpegEncodingColor.Luminance, + JpegEncodingColor.Rgb, + JpegEncodingColor.YCbCrRatio420, + JpegEncodingColor.YCbCrRatio444, + }; [Params(75, 90, 100)] public int Quality; @@ -41,7 +46,12 @@ public void Setup() { using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.bmpCore = Image.Load(imageBinaryStream); - this.encoder = new JpegEncoder { Quality = this.Quality, ColorType = this.TargetColorSpace }; + this.encoder = new JpegEncoder + { + Quality = this.Quality, + ColorType = this.TargetColorSpace, + Interleaved = true, + }; this.destinationStream = new MemoryStream(); } @@ -67,23 +77,23 @@ public void Benchmark() /* BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores -.NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT - DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT +.NET SDK=6.0.202 + [Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT + DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT | Method | TargetColorSpace | Quality | Mean | Error | StdDev | |---------- |----------------- |-------- |----------:|----------:|----------:| -| Benchmark | Luminance | 75 | 7.055 ms | 0.1411 ms | 0.3297 ms | -| Benchmark | Rgb | 75 | 12.139 ms | 0.0645 ms | 0.0538 ms | -| Benchmark | YCbCrRatio420 | 75 | 6.463 ms | 0.0282 ms | 0.0235 ms | -| Benchmark | YCbCrRatio444 | 75 | 8.616 ms | 0.0422 ms | 0.0374 ms | -| Benchmark | Luminance | 90 | 7.011 ms | 0.0361 ms | 0.0301 ms | -| Benchmark | Rgb | 90 | 13.119 ms | 0.0947 ms | 0.0886 ms | -| Benchmark | YCbCrRatio420 | 90 | 6.786 ms | 0.0328 ms | 0.0274 ms | -| Benchmark | YCbCrRatio444 | 90 | 8.672 ms | 0.0772 ms | 0.0722 ms | -| Benchmark | Luminance | 100 | 9.554 ms | 0.1211 ms | 0.1012 ms | -| Benchmark | Rgb | 100 | 19.475 ms | 0.1080 ms | 0.0958 ms | -| Benchmark | YCbCrRatio420 | 100 | 10.146 ms | 0.0585 ms | 0.0519 ms | -| Benchmark | YCbCrRatio444 | 100 | 15.317 ms | 0.0709 ms | 0.0592 ms | +| Benchmark | Luminance | 75 | 4.575 ms | 0.0233 ms | 0.0207 ms | +| Benchmark | Rgb | 75 | 12.477 ms | 0.1051 ms | 0.0932 ms | +| Benchmark | YCbCrRatio420 | 75 | 6.421 ms | 0.0464 ms | 0.0434 ms | +| Benchmark | YCbCrRatio444 | 75 | 8.449 ms | 0.1246 ms | 0.1166 ms | +| Benchmark | Luminance | 90 | 4.863 ms | 0.0120 ms | 0.0106 ms | +| Benchmark | Rgb | 90 | 13.287 ms | 0.0548 ms | 0.0513 ms | +| Benchmark | YCbCrRatio420 | 90 | 7.012 ms | 0.0533 ms | 0.0499 ms | +| Benchmark | YCbCrRatio444 | 90 | 8.916 ms | 0.1285 ms | 0.1202 ms | +| Benchmark | Luminance | 100 | 6.665 ms | 0.0136 ms | 0.0113 ms | +| Benchmark | Rgb | 100 | 19.734 ms | 0.0477 ms | 0.0446 ms | +| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0925 ms | 0.0865 ms | +| Benchmark | YCbCrRatio444 | 100 | 15.587 ms | 0.1695 ms | 0.1586 ms | */ diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 4db718e12a..3536a05171 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -22,11 +22,7 @@ public class JpegColorConverterTests private const int TestBufferLength = 40; -#if SUPPORTS_RUNTIME_INTRINSICS private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; -#else - private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll; -#endif private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); @@ -52,7 +48,8 @@ public void GetConverterThrowsExceptionOnInvalidColorSpace() public void GetConverterThrowsExceptionOnInvalidPrecision() { // Valid precisions: 8 & 12 bit - Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 9)); + int invalidPrecision = 9; + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); } [Theory] @@ -94,13 +91,13 @@ internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int compon [Theory] [MemberData(nameof(Seeds))] public void FromYCbCrBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromYCbCrVector(int seed) { - var converter = new JpegColorConverterBase.FromYCbCrVector(8); + var converter = new JpegColorConverterBase.YCbCrVector(8); if (!converter.IsAvailable) { @@ -116,7 +113,7 @@ public void FromYCbCrVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromYCbCrVector(8), + new JpegColorConverterBase.YCbCrVector(8), 3, FeatureTestRunner.Deserialize(arg)); } @@ -124,13 +121,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromCmykBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed); + this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] public void FromCmykVector(int seed) { - var converter = new JpegColorConverterBase.FromCmykVector(8); + var converter = new JpegColorConverterBase.CmykVector(8); if (!converter.IsAvailable) { @@ -146,7 +143,7 @@ public void FromCmykVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromCmykVector(8), + new JpegColorConverterBase.CmykVector(8), 4, FeatureTestRunner.Deserialize(arg)); } @@ -154,13 +151,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed); + this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed); [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleVector(int seed) { - var converter = new JpegColorConverterBase.FromGrayScaleVector(8); + var converter = new JpegColorConverterBase.GrayScaleVector(8); if (!converter.IsAvailable) { @@ -176,7 +173,7 @@ public void FromGrayscaleVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromGrayScaleVector(8), + new JpegColorConverterBase.GrayScaleVector(8), 1, FeatureTestRunner.Deserialize(arg)); } @@ -184,13 +181,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromRgbBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromRgbVector(int seed) { - var converter = new JpegColorConverterBase.FromRgbVector(8); + var converter = new JpegColorConverterBase.RgbVector(8); if (!converter.IsAvailable) { @@ -206,7 +203,7 @@ public void FromRgbVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromRgbVector(8), + new JpegColorConverterBase.RgbVector(8), 3, FeatureTestRunner.Deserialize(arg)); } @@ -214,13 +211,13 @@ static void RunTest(string arg) => [Theory] [MemberData(nameof(Seeds))] public void FromYccKBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed); + this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] public void FromYccKVector(int seed) { - var converter = new JpegColorConverterBase.FromYccKVector(8); + var converter = new JpegColorConverterBase.YccKVector(8); if (!converter.IsAvailable) { @@ -236,37 +233,35 @@ public void FromYccKVector(int seed) static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromYccKVector(8), + new JpegColorConverterBase.YccKVector(8), 4, FeatureTestRunner.Deserialize(arg)); } -#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] public void FromYCbCrAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromCmykAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); + this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); + this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed); [Theory] [MemberData(nameof(Seeds))] public void FromRgbAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); + this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] public void FromYccKAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed); -#endif + this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed); private void TestConverter( JpegColorConverterBase converter, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 7a5dcedef9..e44c75927d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -145,7 +145,6 @@ public void Decode_VerifyQuality(string imagePath, int quality) [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)] [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)] [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegEncodingColor.YCbCrRatio422)] [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 9f36fcc0f1..cf61e57830 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -85,53 +85,6 @@ public void Encode_PreservesColorType(TestImageProvider provider Assert.Equal(expectedColorType, meta.ColorType); } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg411, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg422, PixelTypes.Rgba32)] - public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image input = provider.GetImage(JpegDecoder); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, new JpegEncoder() - { - Quality = 75 - }); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegEncodingColor.YCbCrRatio420, meta.ColorType); - } - - [Theory] - [InlineData(JpegEncodingColor.Cmyk)] - [InlineData(JpegEncodingColor.YCbCrRatio410)] - [InlineData(JpegEncodingColor.YCbCrRatio411)] - [InlineData(JpegEncodingColor.YCbCrRatio422)] - public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegEncodingColor colorType) - { - // arrange - var jpegEncoder = new JpegEncoder() { ColorType = colorType }; - using var input = new Image(10, 10); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, jpegEncoder); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(JpegEncodingColor.YCbCrRatio420, meta.ColorType); - } - [Theory] [MemberData(nameof(QualityFiles))] public void Encode_PreservesQuality(string imagePath, int quality) @@ -178,7 +131,7 @@ public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider(TestImageProvider provider, JpegEncodingColor colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.15f)); [Theory] [WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)] @@ -265,34 +218,11 @@ private static void TestJpegEncoderCore( } [Fact] - public void Quality_0_And_1_Are_Identical() - { - var options = new JpegEncoder - { - Quality = 0 - }; - - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); - - options.Quality = 1; - input.SaveAsJpeg(memStream1, options); - - Assert.Equal(memStream0.ToArray(), memStream1.ToArray()); - } - } - - [Fact] - public void Quality_0_And_100_Are_Not_Identical() + public void Quality_1_And_100_Are_Not_Identical() { var options = new JpegEncoder { - Quality = 0 + Quality = 1 }; var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); From a7bea836809c74a71089a03332f40500631c6a66 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 22 May 2022 16:21:37 +0300 Subject: [PATCH 26/39] Implemented YccK encoding --- .../JpegColorConverter.CmykAvx.cs | 5 +- .../JpegColorConverter.CmykVector.cs | 12 +++- .../JpegColorConverter.YCbCrScalar.cs | 2 +- .../JpegColorConverter.YccKAvx.cs | 52 ++++++++++++++++- .../JpegColorConverter.YccKScalar.cs | 42 ++++++++++++-- .../JpegColorConverter.YccKVector.cs | 58 ++++++++++++++++++- .../EncodingConfigs/JpegFrameConfig.cs | 2 + .../Formats/Jpeg/JpegDecoderCore.cs | 7 +-- .../Formats/Jpeg/JpegEncoderCore.cs | 48 ++++++++++++--- .../Formats/Jpeg/JpegEncodingColor.cs | 5 ++ 10 files changed, 203 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs index 66173b0a5a..158886b054 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -49,6 +49,9 @@ public override void ConvertToRgbInplace(in ComponentValues values) } public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + => ConvertFromRgbInplace(in values, this.MaximumValue, rLane, gLane, bLane); + + public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) { ref Vector256 destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -67,7 +70,7 @@ public override void ConvertFromRgbInplace(in ComponentValues values, Span>(ref MemoryMarshal.GetReference(bLane)); // Used for the color conversion - var scale = Vector256.Create(this.MaximumValue); + var scale = Vector256.Create(maxValue); nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs index 196f64ead3..e75d61e4c2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -49,6 +49,12 @@ protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b); + + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b); + + public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span r, Span g, Span b) { ref Vector destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -67,7 +73,7 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v ref Unsafe.As>(ref MemoryMarshal.GetReference(b)); // Used for the color conversion - var scale = new Vector(this.MaximumValue); + var scale = new Vector(maxValue); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) @@ -89,8 +95,8 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => CmykScalar.ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); + public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span r, Span g, Span b) + => CmykScalar.ConvertFromRgbInplace(values, maxValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs index e5f76dadfd..418fa02073 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -9,7 +9,7 @@ internal abstract partial class JpegColorConverterBase { internal sealed class YCbCrScalar : JpegColorConverterScalar { - // TODO: comments, derived from ITU-T Rec. T.871 + // derived from ITU-T Rec. T.871 internal const float RCrMult = 1.402f; internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs index 58d024e376..b558f94cb9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -78,8 +78,56 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => throw new NotImplementedException(); + public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykAvx.ConvertFromRgbInplace(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector256 srcR = ref destY; + ref Vector256 srcG = ref destCb; + ref Vector256 srcB = ref destCr; + + // Used for the color conversion + var maxSampleValue = Vector256.Create(this.MaximumValue); + + var chromaOffset = Vector256.Create(this.HalfValue); + + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + Vector256 r = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i)); + Vector256 g = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i)); + Vector256 b = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i)); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index f49e819b9e..2868ffc993 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -9,6 +9,12 @@ internal abstract partial class JpegColorConverterBase { internal sealed class YccKScalar : JpegColorConverterScalar { + // derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; + public YccKScalar(int precision) : base(JpegColorSpace.Ycck, precision) { @@ -17,6 +23,9 @@ public YccKScalar(int precision) public override void ConvertToRgbInplace(in ComponentValues values) => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); + public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) { Span c0 = values.Component0; @@ -33,14 +42,37 @@ public static void ConvertToRgpInplace(in ComponentValues values, float maxValue float cr = c2[i] - halfValue; float scaledK = c3[i] * scale; - c0[i] = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK; + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scaledK; + c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scaledK; + c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scaledK; } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => throw new NotImplementedException(); + public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykScalar.ConvertFromRgbInplace(in values, maxValue, rLane, gLane, bLane); + + // cmyk -> ycck + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; + + for (int i = 0; i < y.Length; i++) + { + float r = maxValue - c[i]; + float g = maxValue - m[i]; + float b = maxValue - y[i]; + + // k value is passed untouched from rgb -> cmyk conversion + c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); + } + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index 7a5597c463..1bae94c71c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -72,11 +72,63 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => throw new System.NotImplementedException(); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector srcR = ref destY; + ref Vector srcG = ref destCb; + ref Vector srcB = ref destCr; + + var maxSampleValue = new Vector(this.MaximumValue); + + var chromaOffset = new Vector(this.HalfValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = maxSampleValue - Unsafe.Add(ref srcR, i); + Vector g = maxSampleValue - Unsafe.Add(ref srcG, i); + Vector b = maxSampleValue - Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); + } + } protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => throw new System.NotImplementedException(); + { + // rgb -> cmyk + CmykScalar.ConvertFromRgbInplace(in values, this.MaximumValue, r, g, b); + + // cmyk -> ycck + YccKScalar.ConvertCoreInplaceFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs index 0bb0f17d13..efff92b2bb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs @@ -38,5 +38,7 @@ public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor public int MaxHorizontalSamplingFactor { get; } public int MaxVerticalSamplingFactor { get; } + + public byte? AdobeColorTransformMarkerFlag { get; set; } = null; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 0efbedff1d..3c2aa6d981 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -596,12 +596,7 @@ private JpegEncodingColor DeduceJpegColorType() case JpegColorSpace.Cmyk: return JpegEncodingColor.Cmyk; case JpegColorSpace.Ycck: - - // TODO: change this after YccK encoding is implemented - // We are deliberately mapping YccK color space to Cmyk color space at metadata - // level so encoder can fallback to cmyk color space from it. - // YccK -> Cmyk is the closest conversion logically wise - return JpegEncodingColor.Cmyk; + return JpegEncodingColor.Ycck; default: return JpegEncodingColor.YCbCrRatio420; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index fc13876213..c8dd763e71 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -81,16 +81,16 @@ public void Encode(Image image, Stream stream, CancellationToken // Write the Start Of Image marker. this.WriteStartOfImage(); - // Write APP0 marker for any non-RGB colorspace image. - if (frameConfig.EncodingColor != JpegEncodingColor.Rgb) + // Write APP0 marker + if (frameConfig.AdobeColorTransformMarkerFlag is null) { this.WriteJfifApplicationHeader(metadata); } - // Else write App14 marker to indicate RGB color space. + // Write APP14 marker with adobe color extension else { - this.WriteApp14Marker(); + this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value); } // Write Exif, XMP, ICC and IPTC profiles @@ -207,7 +207,7 @@ private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, Huf /// /// Writes the APP14 marker to indicate the image is in RGB color space. /// - private void WriteApp14Marker() + private void WriteApp14Marker(byte colorTransform) { this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length); @@ -227,8 +227,8 @@ private void WriteApp14Marker() // Flags1 BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0); - // Transform byte, 0 in combination with three components means the image is in RGB colorspace. - this.buffer[11] = 0; + // Color transform byte + this.buffer[11] = colorTransform; this.outputStream.Write(this.buffer.AsSpan(0, 12)); } @@ -840,7 +840,10 @@ private static JpegFrameConfig[] CreateFrameConfigs() new JpegQuantizationTableConfig[] { defaultLuminanceQuantTable - }), + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + }, // Cmyk new JpegFrameConfig( @@ -861,7 +864,34 @@ private static JpegFrameConfig[] CreateFrameConfigs() new JpegQuantizationTableConfig[] { defaultLuminanceQuantTable - }), + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, + }, + + // YccK + new JpegFrameConfig( + JpegColorSpace.Ycck, + JpegEncodingColor.Ycck, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, + }, }; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs index 995b6036c8..f803c830e5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs @@ -54,5 +54,10 @@ public enum JpegEncodingColor : byte /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. /// Cmyk = 7, + + /// + /// YCCK colorspace (Y, Cb, Cr, and key black). + /// + Ycck = 8, } } From dd0327ac50368c91958addd2979ba380c8b85a92 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 22 May 2022 16:44:31 +0300 Subject: [PATCH 27/39] Added docs to the color sonverters --- .../JpegColorConverter.CmykAvx.cs | 9 ++-- .../JpegColorConverter.CmykScalar.cs | 8 ++-- .../JpegColorConverter.CmykVector.cs | 14 ++++-- .../JpegColorConverter.GrayScaleAvx.cs | 4 +- .../JpegColorConverter.GrayScaleScalar.cs | 8 ++-- .../JpegColorConverter.GrayScaleVector.cs | 14 ++++-- .../JpegColorConverter.RgbAvx.cs | 4 +- .../JpegColorConverter.RgbScalar.cs | 18 +++---- .../JpegColorConverter.RgbVector.cs | 16 ++++--- .../JpegColorConverter.YCbCrAvx.cs | 4 +- .../JpegColorConverter.YCbCrScalar.cs | 12 +++-- .../JpegColorConverter.YCbCrVector.cs | 16 ++++--- .../JpegColorConverter.YccKAvx.cs | 6 ++- .../JpegColorConverter.YccKScalar.cs | 10 ++-- .../JpegColorConverter.YccKVector.cs | 16 ++++--- .../ColorConverters/JpegColorConverterAvx.cs | 1 + .../ColorConverters/JpegColorConverterBase.cs | 9 +++- .../JpegColorConverterScalar.cs | 1 + .../JpegColorConverterVector.cs | 48 +++++++++++++++---- .../Encoder/SpectralConverter{TPixel}.cs | 2 +- 20 files changed, 148 insertions(+), 72 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs index 158886b054..f65c74d782 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -19,6 +19,7 @@ public CmykAvx(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -48,10 +49,11 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbInplace(in values, this.MaximumValue, rLane, gLane, bLane); + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) + public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) { ref Vector256 destC = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -69,7 +71,6 @@ public static void ConvertFromRgbInplace(in ComponentValues values, float maxVal ref Vector256 srcB = ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - // Used for the color conversion var scale = Vector256.Create(maxValue); nint n = values.Component0.Length / Vector256.Count; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs index 44ad19687f..2cca688dc4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs @@ -14,11 +14,13 @@ public CmykScalar(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) => ConvertToRgbInplace(values, this.MaximumValue); - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgbInplace(values, this.MaximumValue, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.MaximumValue, r, g, b); public static void ConvertToRgbInplace(in ComponentValues values, float maxValue) { @@ -42,7 +44,7 @@ public static void ConvertToRgbInplace(in ComponentValues values, float maxValue } } - public static void ConvertFromRgbInplace(in ComponentValues values, float maxValue, Span r, Span g, Span b) + public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span r, Span g, Span b) { Span c = values.Component0; Span m = values.Component1; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs index e75d61e4c2..1e62484e32 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -17,7 +17,8 @@ public CmykVector(int precision) { } - protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) { ref Vector cBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -45,13 +46,16 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } } - protected override void ConvertCoreInplaceToRgb(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) => ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b); - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) => ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b); public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span r, Span g, Span b) @@ -96,7 +100,7 @@ public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, fl } public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span r, Span g, Span b) - => CmykScalar.ConvertFromRgbInplace(values, maxValue, r, g, b); + => CmykScalar.ConvertFromRgb(values, maxValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs index 89d51586b8..a3999ffda3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs @@ -20,6 +20,7 @@ public GrayscaleAvx(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -36,7 +37,8 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { ref Vector256 destLuminance = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs index 4029830d5e..65a201d946 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -16,13 +16,15 @@ public GrayscaleScalar(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); + => ConvertToRgbInplace(values.Component0, this.MaximumValue); - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) => ConvertCoreInplaceFromRgb(values, r, g, b); - internal static void ConvertCoreInplaceToRgb(Span values, float maxValue) + internal static void ConvertToRgbInplace(Span values, float maxValue) { ref float valuesRef = ref MemoryMarshal.GetReference(values); float scale = 1 / maxValue; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs index ef51ff2b0f..fa0a9b27a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs @@ -17,7 +17,8 @@ public GrayScaleVector(int precision) { } - protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) { ref Vector cBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -32,10 +33,12 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } } - protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, this.MaximumValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => GrayscaleScalar.ConvertToRgbInplace(values.Component0, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) { ref Vector destLuma = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -63,7 +66,8 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) => GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs index 6731d535ee..aa57162d3c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs @@ -19,6 +19,7 @@ public RgbAvx(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 rBase = @@ -42,7 +43,8 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { rLane.CopyTo(values.Component0); gLane.CopyTo(values.Component1); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs index 42cd002427..e798196861 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs @@ -14,20 +14,22 @@ public RgbScalar(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplaceToRgb(values, this.MaximumValue); + => ConvertToRgbInplace(values, this.MaximumValue); - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => ConvertCoreInplaceFromRgb(values, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, r, g, b); - internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue) + internal static void ConvertToRgbInplace(ComponentValues values, float maxValue) { - GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue); - GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue); - GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component0, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component1, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component2, maxValue); } - internal static void ConvertCoreInplaceFromRgb(ComponentValues values, Span r, Span g, Span b) + internal static void ConvertFromRgb(ComponentValues values, Span r, Span g, Span b) { r.CopyTo(values.Component0); g.CopyTo(values.Component1); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs index f4c4fa379c..251e970154 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs @@ -17,7 +17,8 @@ public RgbVector(int precision) { } - protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) { ref Vector rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -40,18 +41,21 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } } - protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => RgbScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => RgbScalar.ConvertToRgbInplace(values, this.MaximumValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) { r.CopyTo(values.Component0); g.CopyTo(values.Component1); b.CopyTo(values.Component2); } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => RgbScalar.ConvertCoreInplaceFromRgb(values, r, g, b); + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => RgbScalar.ConvertFromRgb(values, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs index 46e9fd033e..517fbdbeb9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs @@ -21,6 +21,7 @@ public YCbCrAvx(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -70,7 +71,8 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { ref Vector256 destY = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs index 418fa02073..0286df5813 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -20,13 +20,15 @@ public YCbCrScalar(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); + => ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.HalfValue, r, g, b); - public static void ConvertCoreInplaceToRgb(in ComponentValues values, float maxValue, float halfValue) + public static void ConvertToRgbInplace(in ComponentValues values, float maxValue, float halfValue) { Span c0 = values.Component0; Span c1 = values.Component1; @@ -49,7 +51,7 @@ public static void ConvertCoreInplaceToRgb(in ComponentValues values, float maxV } } - public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) + public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) { Span y = values.Component0; Span cb = values.Component1; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs index bf0aa4a341..e0c44a31e3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs @@ -18,7 +18,8 @@ public YCbCrVector(int precision) { } - protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) { ref Vector c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -68,10 +69,12 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } } - protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => YCbCrScalar.ConvertCoreInplaceToRgb(values, this.MaximumValue, this.HalfValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) { ref Vector destY = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -117,8 +120,9 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) - => YCbCrScalar.ConvertCoreInplaceFromRgb(values, this.HalfValue, r, g, b); + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs index b558f94cb9..1e641b0671 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -20,6 +20,7 @@ public YccKAvx(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -78,10 +79,11 @@ public override void ConvertToRgbInplace(in ComponentValues values) } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) { // rgb -> cmyk - CmykAvx.ConvertFromRgbInplace(in values, this.MaximumValue, rLane, gLane, bLane); + CmykAvx.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); // cmyk -> ycck ref Vector256 destY = diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index 2868ffc993..3b40dff8af 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -20,11 +20,13 @@ public YccKScalar(int precision) { } + /// public override void ConvertToRgbInplace(in ComponentValues values) => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) - => ConvertCoreInplaceFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) { @@ -51,10 +53,10 @@ public static void ConvertToRgpInplace(in ComponentValues values, float maxValue } } - public static void ConvertCoreInplaceFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) + public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) { // rgb -> cmyk - CmykScalar.ConvertFromRgbInplace(in values, maxValue, rLane, gLane, bLane); + CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); // cmyk -> ycck Span c = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index 1bae94c71c..1f5944bd8c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -17,7 +17,8 @@ public YccKVector(int precision) { } - protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) { ref Vector c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -69,10 +70,12 @@ protected override void ConvertCoreVectorizedInplaceToRgb(in ComponentValues val } } - protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) { // rgb -> cmyk CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane); @@ -121,13 +124,14 @@ protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues v } } - protected override void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b) + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) { // rgb -> cmyk - CmykScalar.ConvertFromRgbInplace(in values, this.MaximumValue, r, g, b); + CmykScalar.ConvertFromRgb(in values, this.MaximumValue, r, g, b); // cmyk -> ycck - YccKScalar.ConvertCoreInplaceFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); + YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs index 90e5df88f8..d7b619fca9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs @@ -25,6 +25,7 @@ protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) { } + /// public override bool IsAvailable => Avx.IsSupported; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 744c178e89..34b48c2fe6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -79,7 +79,14 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int /// The input/ouptut as a stack-only struct public abstract void ConvertToRgbInplace(in ComponentValues values); - public abstract void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b); + /// + /// Converts RGB lanes to jpeg component values. + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + public abstract void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane); /// /// Returns the s for all supported colorspaces and precisions. diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs index 44046f8e85..d962f434f0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs @@ -16,6 +16,7 @@ protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) { } + /// public override bool IsAvailable => true; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs index b5460d31aa..3c8b16943b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -26,9 +26,11 @@ protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) { } + /// public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; - public override void ConvertToRgbInplace(in ComponentValues values) + /// + public sealed override void ConvertToRgbInplace(in ComponentValues values) { DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); @@ -38,7 +40,7 @@ public override void ConvertToRgbInplace(in ComponentValues values) int simdCount = length - remainder; if (simdCount > 0) { - this.ConvertCoreVectorizedInplaceToRgb(values.Slice(0, simdCount)); + this.ConvertToRgbInplaceVectorized(values.Slice(0, simdCount)); } // Jpeg images width is always divisible by 8 without a remainder @@ -48,11 +50,12 @@ public override void ConvertToRgbInplace(in ComponentValues values) // remainder pixels if (remainder > 0) { - this.ConvertCoreInplaceToRgb(values.Slice(simdCount, remainder)); + this.ConvertToRgbInplaceScalarRemainder(values.Slice(simdCount, remainder)); } } - public override void ConvertFromRgbInplace(in ComponentValues values, Span r, Span g, Span b) + /// + public sealed override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) { DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); @@ -62,7 +65,7 @@ public override void ConvertFromRgbInplace(in ComponentValues values, Span 0) { - this.ConvertCoreVectorizedInplaceFromRgb( + this.ConvertFromRgbVectorized( values.Slice(0, simdCount), r.Slice(0, simdCount), g.Slice(0, simdCount), @@ -76,7 +79,7 @@ public override void ConvertFromRgbInplace(in ComponentValues values, Span 0) { - this.ConvertCoreInplaceFromRgb( + this.ConvertFromRgbScalarRemainder( values.Slice(simdCount, remainder), r.Slice(simdCount, remainder), g.Slice(simdCount, remainder), @@ -84,13 +87,38 @@ public override void ConvertFromRgbInplace(in ComponentValues values, Span + /// Converts planar jpeg component values in + /// to RGB color space inplace using API. + /// + /// The input/ouptut as a stack-only struct + protected abstract void ConvertToRgbInplaceVectorized(in ComponentValues values); - protected abstract void ConvertCoreInplaceToRgb(in ComponentValues values); + /// + /// Converts remainder of the planar jpeg component values after + /// conversion in . + /// + /// The input/ouptut as a stack-only struct + protected abstract void ConvertToRgbInplaceScalarRemainder(in ComponentValues values); - protected abstract void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b); + /// + /// Converts RGB lanes to jpeg component values using API. + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - protected abstract void ConvertCoreInplaceFromRgb(in ComponentValues values, Span r, Span g, Span b); + /// + /// Converts remainder of RGB lanes to jpeg component values after + /// conversion in . + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 18c0e63cfc..44271248f5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -112,7 +112,7 @@ private void ConvertStride(int spectralStep) // Convert from rgb24 to target pixel type var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - this.colorConverter.ConvertFromRgbInplace(values, rLane, gLane, bLane); + this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane); } // Convert pixels to spectral From 503253e568e19f99ea814dd057f27da622dfe378 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 22 May 2022 17:10:25 +0300 Subject: [PATCH 28/39] Fixed scalar YccK conversion --- .../ColorConverters/JpegColorConverter.YccKScalar.cs | 6 +++--- .../ColorConverters/JpegColorConverter.YccKVector.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index 3b40dff8af..1c03c4eef3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -47,9 +47,9 @@ public static void ConvertToRgpInplace(in ComponentValues values, float maxValue // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scaledK; - c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scaledK; - c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scaledK; + c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index 1f5944bd8c..c84416fc40 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -71,8 +71,8 @@ protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) } /// - protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) => - YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); /// protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) From 893bc201bd0fe152dd90abf1956ab82a5610f4b8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 19 Jun 2022 12:56:22 +0300 Subject: [PATCH 29/39] gfoidl review, removed obsolete enum --- .../Common/Helpers/SimdUtils.HwIntrinsics.cs | 2 +- .../Jpeg/Components/Block8x8F.ScaledCopy.cs | 13 ++++---- .../Jpeg/Components/Encoder/Component.cs | 6 ++-- .../Components/Encoder/ComponentProcessor.cs | 11 ++++--- .../Components/Encoder/HuffmanScanEncoder.cs | 32 ++++++++++--------- .../Jpeg/Components/Encoder/QuantIndex.cs | 21 ------------ 6 files changed, 33 insertions(+), 52 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 3d2a91a4fd..b1d84c3e06 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -984,7 +984,7 @@ internal static void UnpackToRgbPlanesAvx2Reduce( Vector256 r, g, b; const int bytesPerRgbStride = 24; - int count = source.Length / 8; + int count = (int)((uint)source.Length / 8); for (int i = 0; i < count; i++) { rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index fe92582f47..c258602562 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -36,7 +36,7 @@ public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalSca private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) { ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); - int destStride = areaStride / 2; + int destStride = (int)((uint)areaStride / 2); WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride); WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride); @@ -48,12 +48,12 @@ private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride); [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, int row, int destStride) + static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nint row, nint destStride) { ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row); ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - int offset = 2 * row * destStride; + nint offset = 2 * row * destStride; ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); @@ -98,12 +98,11 @@ private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizo int xx = x * horizontalScale; float value = this[y8 + x]; + nint baseIdx = (yy * areaStride) + xx; - for (int i = 0; i < verticalScale; i++) + for (nint i = 0; i < verticalScale; i++, baseIdx += areaStride) { - int baseIdx = ((yy + i) * areaStride) + xx; - - for (int j = 0; j < horizontalScale; j++) + for (nint j = 0; j < horizontalScale; j++) { // area[xx + j, yy + i] = value; Unsafe.Add(ref areaOrigin, baseIdx + j) = value; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs index ccb4413abd..59c5c69c80 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs @@ -19,7 +19,7 @@ public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int vert this.HorizontalSamplingFactor = horizontalFactor; this.VerticalSamplingFactor = verticalFactor; - this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + this.SamplingFactors = new Size(horizontalFactor, verticalFactor); this.QuantizationTableIndex = quantizationTableIndex; } @@ -85,10 +85,10 @@ public void Dispose() public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) { this.WidthInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); + ((uint)frame.PixelWidth + 7) / 8 * this.HorizontalSamplingFactor / maxSubFactorH); this.HeightInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); + ((uint)frame.PixelHeight + 7) / 8 * this.VerticalSamplingFactor / maxSubFactorV); int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index 400ce41e24..8c9f826a10 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -168,20 +168,21 @@ static void SumHorizontal(Span target, int factor) // log2(2) == 2 / 2 == 1 // log2(4) == 4 / 2 == 2 int haddIterationsCount = (int)((uint)factor / 2); - int length = target.Length / Vector256.Count; + uint length = (uint)target.Length / (uint)Vector256.Count; for (int i = 0; i < haddIterationsCount; i++) { length /= 2; - for (int j = 0; j < length; j++) + + for (nuint j = 0; j < length; j++) { - int indexLeft = j * 2; - int indexRight = indexLeft + 1; + nuint indexLeft = j * 2; + nuint indexRight = indexLeft + 1; Vector256 sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight)); Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); } } - int summedCount = length * factor * Vector256.Count; + int summedCount = (int)(length * factor * Vector256.Count); target = target.Slice(summedCount); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 08beb46331..4268f862c2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -183,7 +183,7 @@ public void EncodeScanBaselineSingleComponent(Component component, Spect Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int k = 0; k < w; k++) + for (nint k = 0; k < w; k++) { this.WriteBlock( component, @@ -222,7 +222,7 @@ public void EncodeScanBaseline(Component component, CancellationToken cancellati Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int k = 0; k < w; k++) + for (nint k = 0; k < w; k++) { this.WriteBlock( component, @@ -249,9 +249,9 @@ ref Unsafe.Add(ref blockRef, k), private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - int mcu = 0; - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; + nint mcu = 0; + nint mcusPerColumn = frame.McusPerColumn; + nint mcusPerLine = frame.McusPerLine; for (int j = 0; j < mcusPerColumn; j++) { @@ -261,20 +261,22 @@ private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConv converter.ConvertStrideBaseline(); // Encode spectral to binary - for (int i = 0; i < mcusPerLine; i++) + for (nint i = 0; i < mcusPerLine; i++) { // Scan an interleaved mcu... process components in order - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < frame.Components.Length; k++) + nint mcuCol = mcu % mcusPerLine; + for (nint k = 0; k < frame.Components.Length; k++) { Component component = frame.Components[k]; ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - int h = component.HorizontalSamplingFactor; + nint h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; + nint blockColBase = mcuCol * h; + // Scan out an mcu's worth of this component; that's just determined // by the basic H and V specified for the component for (int y = 0; y < v; y++) @@ -282,9 +284,9 @@ private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConv Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int x = 0; x < h; x++) + for (nint x = 0; x < h; x++) { - int blockCol = (mcuCol * h) + x; + nint blockCol = blockColBase + x; this.WriteBlock( component, @@ -316,8 +318,8 @@ ref Unsafe.Add(ref blockRef, blockCol), private void EncodeThreeComponentScanBaselineInterleaved444(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - int mcusPerColumn = frame.McusPerColumn; - int mcusPerLine = frame.McusPerLine; + nint mcusPerColumn = frame.McusPerColumn; + nint mcusPerLine = frame.McusPerLine; Component c2 = frame.Components[2]; Component c1 = frame.Components[1]; @@ -334,7 +336,7 @@ private void EncodeThreeComponentScanBaselineInterleaved444(JpegFrame fr ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0)); ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0)); - for (int j = 0; j < mcusPerColumn; j++) + for (nint j = 0; j < mcusPerColumn; j++) { cancellationToken.ThrowIfCancellationRequested(); @@ -342,7 +344,7 @@ private void EncodeThreeComponentScanBaselineInterleaved444(JpegFrame fr converter.ConvertStrideBaseline(); // Encode spectral to binary - for (int i = 0; i < mcusPerLine; i++) + for (nint i = 0; i < mcusPerLine; i++) { this.WriteBlock( c0, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs deleted file mode 100644 index f9d0fba57f..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder -{ - /// - /// Enumerates the quantization tables. - /// - internal enum QuantIndex - { - /// - /// The luminance quantization table index. - /// - Luminance = 0, - - /// - /// The chrominance quantization table index. - /// - Chrominance = 1, - } -} From d351389c27441d4d23783898c1617317fbe59d52 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 19 Jun 2022 15:39:44 +0300 Subject: [PATCH 30/39] Removed redundant color converters creation, updated benchmarks --- .../ColorConverters/JpegColorConverterAvx.cs | 7 +- .../ColorConverters/JpegColorConverterBase.cs | 107 ++++++++++++------ .../JpegColorConverterVector.cs | 7 +- .../Components/Encoder/HuffmanScanEncoder.cs | 6 +- .../Codecs/Jpeg/EncodeJpegFeatures.cs | 24 ++-- 5 files changed, 102 insertions(+), 49 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs index d7b619fca9..429b8677b6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs @@ -25,8 +25,13 @@ protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) { } + /// + /// Gets a value indicating whether this converter is supported on current hardware. + /// + public static bool IsSupported => Avx.IsSupported; + /// - public override bool IsAvailable => Avx.IsSupported; + public override bool IsAvailable => IsSupported; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 34b48c2fe6..8048f3233d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -93,73 +93,116 @@ public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int /// private static JpegColorConverterBase[] CreateConverters() { - var converters = new List(); + // 5 color types with 2 supported precisions: 8 bit & 12 bit + const int colorConvertersCount = 5 * 2; + + var converters = new JpegColorConverterBase[colorConvertersCount]; // 8-bit converters - converters.AddRange(GetYCbCrConverters(8)); - converters.AddRange(GetYccKConverters(8)); - converters.AddRange(GetCmykConverters(8)); - converters.AddRange(GetGrayScaleConverters(8)); - converters.AddRange(GetRgbConverters(8)); + converters[0] = GetYCbCrConverter(8); + converters[1] = GetYccKConverter(8); + converters[2] = GetCmykConverter(8); + converters[3] = GetGrayScaleConverter(8); + converters[4] = GetRgbConverter(8); // 12-bit converters - converters.AddRange(GetYCbCrConverters(12)); - converters.AddRange(GetYccKConverters(12)); - converters.AddRange(GetCmykConverters(12)); - converters.AddRange(GetGrayScaleConverters(12)); - converters.AddRange(GetRgbConverters(12)); + converters[5] = GetYCbCrConverter(12); + converters[6] = GetYccKConverter(12); + converters[7] = GetCmykConverter(12); + converters[8] = GetGrayScaleConverter(12); + converters[9] = GetRgbConverter(12); - return converters.Where(x => x.IsAvailable).ToArray(); + return converters; } /// /// Returns the s for the YCbCr colorspace. /// - private static IEnumerable GetYCbCrConverters(int precision) + private static JpegColorConverterBase GetYCbCrConverter(int precision) { - yield return new YCbCrAvx(precision); - yield return new YCbCrVector(precision); - yield return new YCbCrScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new YCbCrAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new YCbCrVector(precision); + } + + return new YCbCrScalar(precision); } /// /// Returns the s for the YccK colorspace. /// - private static IEnumerable GetYccKConverters(int precision) + private static JpegColorConverterBase GetYccKConverter(int precision) { - yield return new YccKAvx(precision); - yield return new YccKVector(precision); - yield return new YccKScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new YccKAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new YccKVector(precision); + } + + return new YccKScalar(precision); } /// /// Returns the s for the CMYK colorspace. /// - private static IEnumerable GetCmykConverters(int precision) + private static JpegColorConverterBase GetCmykConverter(int precision) { - yield return new CmykAvx(precision); - yield return new CmykVector(precision); - yield return new CmykScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new CmykAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new CmykVector(precision); + } + + return new CmykScalar(precision); } /// /// Returns the s for the gray scale colorspace. /// - private static IEnumerable GetGrayScaleConverters(int precision) + private static JpegColorConverterBase GetGrayScaleConverter(int precision) { - yield return new GrayscaleAvx(precision); - yield return new GrayScaleVector(precision); - yield return new GrayscaleScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new GrayscaleAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new GrayScaleVector(precision); + } + + return new GrayscaleScalar(precision); } /// /// Returns the s for the RGB colorspace. /// - private static IEnumerable GetRgbConverters(int precision) + private static JpegColorConverterBase GetRgbConverter(int precision) { - yield return new RgbAvx(precision); - yield return new RgbVector(precision); - yield return new RgbScalar(precision); + if (JpegColorConverterAvx.IsSupported) + { + return new RgbAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new RgbScalar(precision); + } + + return new GrayscaleScalar(precision); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs index 3c8b16943b..92d388fc8f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -26,8 +26,13 @@ protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) { } + /// + /// Gets a value indicating whether this converter is supported on current hardware. + /// + public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; + /// - public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; + public sealed override bool IsAvailable => IsSupported; /// public sealed override void ConvertToRgbInplace(in ComponentValues values) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 4268f862c2..7274b51961 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -149,7 +149,7 @@ public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegF { case JpegEncodingColor.YCbCrRatio444: case JpegEncodingColor.Rgb: - this.EncodeThreeComponentScanBaselineInterleaved444(frame, converter, cancellationToken); + this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken); break; default: this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken); @@ -310,12 +310,12 @@ ref Unsafe.Add(ref blockRef, blockCol), } /// - /// Encodes scan in baseline interleaved mode with exactly 3 components with 4:4:4 sampling. + /// Encodes scan in baseline interleaved mode with exactly 3 components with no subsampling. /// /// Frame to encode. /// Converter from color to spectral. /// The token to request cancellation. - private void EncodeThreeComponentScanBaselineInterleaved444(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + private void EncodeThreeComponentBaselineInterleavedScanNoSubsampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { nint mcusPerColumn = frame.McusPerColumn; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs index 6844883486..a593f0489a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -84,16 +84,16 @@ public void Benchmark() | Method | TargetColorSpace | Quality | Mean | Error | StdDev | |---------- |----------------- |-------- |----------:|----------:|----------:| -| Benchmark | Luminance | 75 | 4.575 ms | 0.0233 ms | 0.0207 ms | -| Benchmark | Rgb | 75 | 12.477 ms | 0.1051 ms | 0.0932 ms | -| Benchmark | YCbCrRatio420 | 75 | 6.421 ms | 0.0464 ms | 0.0434 ms | -| Benchmark | YCbCrRatio444 | 75 | 8.449 ms | 0.1246 ms | 0.1166 ms | -| Benchmark | Luminance | 90 | 4.863 ms | 0.0120 ms | 0.0106 ms | -| Benchmark | Rgb | 90 | 13.287 ms | 0.0548 ms | 0.0513 ms | -| Benchmark | YCbCrRatio420 | 90 | 7.012 ms | 0.0533 ms | 0.0499 ms | -| Benchmark | YCbCrRatio444 | 90 | 8.916 ms | 0.1285 ms | 0.1202 ms | -| Benchmark | Luminance | 100 | 6.665 ms | 0.0136 ms | 0.0113 ms | -| Benchmark | Rgb | 100 | 19.734 ms | 0.0477 ms | 0.0446 ms | -| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0925 ms | 0.0865 ms | -| Benchmark | YCbCrRatio444 | 100 | 15.587 ms | 0.1695 ms | 0.1586 ms | +| Benchmark | Luminance | 75 | 4.618 ms | 0.0263 ms | 0.0233 ms | +| Benchmark | Rgb | 75 | 12.543 ms | 0.0650 ms | 0.0608 ms | +| Benchmark | YCbCrRatio420 | 75 | 6.639 ms | 0.0778 ms | 0.1256 ms | +| Benchmark | YCbCrRatio444 | 75 | 8.590 ms | 0.0570 ms | 0.0505 ms | +| Benchmark | Luminance | 90 | 4.902 ms | 0.0307 ms | 0.0288 ms | +| Benchmark | Rgb | 90 | 13.447 ms | 0.0468 ms | 0.0415 ms | +| Benchmark | YCbCrRatio420 | 90 | 7.218 ms | 0.0586 ms | 0.0548 ms | +| Benchmark | YCbCrRatio444 | 90 | 9.150 ms | 0.0779 ms | 0.0729 ms | +| Benchmark | Luminance | 100 | 6.731 ms | 0.0325 ms | 0.0304 ms | +| Benchmark | Rgb | 100 | 19.831 ms | 0.1009 ms | 0.0788 ms | +| Benchmark | YCbCrRatio420 | 100 | 10.541 ms | 0.0423 ms | 0.0396 ms | +| Benchmark | YCbCrRatio444 | 100 | 15.345 ms | 0.3276 ms | 0.3065 ms | */ From c6d127ee22f0848baad31e4541100df85e515272 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 20 Jun 2022 00:06:27 +0300 Subject: [PATCH 31/39] Added tests for different output color types (except ycck) --- .../JpegColorConverter.GrayScaleScalar.cs | 6 +- .../ColorConverters/JpegColorConverterBase.cs | 1 - .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 66 ++++++ .../Formats/Jpg/JpegEncoderTests.cs | 205 ++++++++---------- .../Formats/Png/PngEncoderTests.cs | 45 ++-- ...lColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg | 3 + ...rTypes_Rgb24_Calliphora_Luminance-Q100.jpg | 3 + ...llColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg | 3 + ...es_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg | 3 + ...es_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg | 3 + ...es_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg | 3 + ...es_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg | 3 + ...es_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg | 3 + 13 files changed, 199 insertions(+), 148 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg create mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs index 65a201d946..40e2da0beb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -41,12 +41,8 @@ internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span(1, 1); + input.Metadata.IptcProfile = new IptcProfile(); + input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IptcProfile actual = output.Metadata.IptcProfile; + Assert.NotNull(actual); + IEnumerable values = input.Metadata.IptcProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesExifProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.ExifProfile = new ExifProfile(); + input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile actual = output.Metadata.ExifProfile; + Assert.NotNull(actual); + IReadOnlyList values = input.Metadata.ExifProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesIccProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IccProfile actual = output.Metadata.IccProfile; + Assert.NotNull(actual); + IccProfile values = input.Metadata.IccProfile; + Assert.Equal(values.Entries, actual.Entries); + } + [Theory] [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index cf61e57830..2ae993b8fb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,15 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -63,6 +59,29 @@ public partial class JpegEncoderTests { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } }; + [Fact] + public void Quality_1_And_100_Are_Not_Identical() + { + var options = new JpegEncoder + { + Quality = 1 + }; + + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + + using (Image input = testFile.CreateRgba32Image()) + using (var memStream0 = new MemoryStream()) + using (var memStream1 = new MemoryStream()) + { + input.SaveAsJpeg(memStream0, options); + + options.Quality = 100; + input.SaveAsJpeg(memStream1, options); + + Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); + } + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingColor.Luminance)] [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] @@ -169,36 +188,21 @@ public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvid TestJpegEncoderCore(provider, colorType, 100, comparer); } - /// - /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation - /// - private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) - { - float tolerance = 0.015f; // ~1.5% - - if (quality < 50) - { - tolerance *= 4.5f; - } - else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) - { - tolerance *= 2.0f; - if (colorType == JpegEncodingColor.YCbCrRatio420) - { - tolerance *= 2.0f; - } - } - - return ImageComparer.Tolerant(tolerance); - } - - private static void TestJpegEncoderCore( - TestImageProvider provider, - JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, - int quality = 100, - ImageComparer comparer = null) + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.L8, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio422)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio411)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio410)] + public void EncodeBaseline_WorksWithAllColorTypes(TestImageProvider provider, JpegEncodingColor colorType) where TPixel : unmanaged, IPixel { + // all reference output images are saved with quality=100 + const int quality = 100; + using Image image = provider.GetImage(); // There is no alpha in Jpeg! @@ -211,33 +215,10 @@ private static void TestJpegEncoderCore( }; string info = $"{colorType}-Q{quality}"; - comparer ??= GetComparer(quality, colorType); + ImageComparer comparer = GetComparer(quality, colorType); // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); - } - - [Fact] - public void Quality_1_And_100_Are_Not_Identical() - { - var options = new JpegEncoder - { - Quality = 1 - }; - - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); - - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); - - options.Quality = 100; - input.SaveAsJpeg(memStream1, options); - - Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); - } + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); } [Theory] @@ -263,68 +244,6 @@ public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolut } } - [Fact] - public void Encode_PreservesIptcProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IptcProfile = new IptcProfile(); - input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IptcProfile actual = output.Metadata.IptcProfile; - Assert.NotNull(actual); - IEnumerable values = input.Metadata.IptcProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesExifProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.ExifProfile = new ExifProfile(); - input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - ExifProfile actual = output.Metadata.ExifProfile; - Assert.NotNull(actual); - IReadOnlyList values = input.Metadata.ExifProfile.Values; - Assert.Equal(values, actual.Values); - } - - [Fact] - public void Encode_PreservesIccProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IccProfile actual = output.Metadata.IccProfile; - Assert.NotNull(actual); - IccProfile values = input.Metadata.IccProfile; - Assert.Equal(values.Entries, actual.Entries); - } - [Theory] [InlineData(JpegEncodingColor.YCbCrRatio420)] [InlineData(JpegEncodingColor.YCbCrRatio444)] @@ -354,5 +273,53 @@ await Assert.ThrowsAsync(async () => await image.SaveAsync(pausedStream, encoder, cts.Token); }); } + + /// + /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation + /// + private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) + { + float tolerance = 0.015f; // ~1.5% + + if (quality < 50) + { + tolerance *= 4.5f; + } + else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) + { + tolerance *= 2.0f; + if (colorType == JpegEncodingColor.YCbCrRatio420) + { + tolerance *= 2.0f; + } + } + + return ImageComparer.Tolerant(tolerance); + } + + private static void TestJpegEncoderCore( + TestImageProvider provider, + JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, + int quality = 100, + ImageComparer comparer = null) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder + { + Quality = quality, + ColorType = colorType + }; + string info = $"{colorType}-Q{quality}"; + + comparer ??= GetComparer(quality, colorType); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index c9b0b3c55e..0a4cd34d2c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -129,13 +129,13 @@ public void IsNotBoundToSinglePixelType(TestImageProvider provid foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - pngColorType, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - appendPixelType: true, - appendPngColorType: true); + provider, + pngColorType, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); } } @@ -204,14 +204,14 @@ public void WorksWithAllBitDepths(TestImageProvider provider, Pn foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - pngColorType, - (PngFilterMethod)filterMethod[0], - pngBitDepth, - interlaceMode, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true); + provider, + pngColorType, + (PngFilterMethod)filterMethod[0], + pngBitDepth, + interlaceMode, + appendPngColorType: true, + appendPixelType: true, + appendPngBitDepth: true); } } } @@ -314,13 +314,13 @@ public void PaletteColorType_WuQuantizer(TestImageProvider provi foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( - provider, - PngColorType.Palette, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - paletteSize: paletteSize, - appendPaletteSize: true); + provider, + PngColorType.Palette, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + paletteSize: paletteSize, + appendPaletteSize: true); } } @@ -615,7 +615,6 @@ private static void TestPngEncoderCore( string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - // Compare to the Magick reference decoder. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); // We compare using both our decoder and the reference decoder as pixel transformation diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg new file mode 100644 index 0000000000..5d9f955e6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acf150308d03dcf9e538eca4f5b1a4895e217c8e7fe7a3bbb7f94a32bc54c794 +size 1600914 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg new file mode 100644 index 0000000000..610b91655d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe9bcfac7e958c5195000a5c935766690c87a161e2ad87b765c0ba5c4d7a6ce8 +size 425003 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg new file mode 100644 index 0000000000..e06a83144b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fc1c157e3170aa13d39a7c8429f70c32c200d84122295b346e5ca7b4808e172 +size 757505 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg new file mode 100644 index 0000000000..9d9c930d50 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8be680ee7a03074c8747dbfffa9175d19120d43ada9406b99110370dd3cda228 +size 491941 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg new file mode 100644 index 0000000000..dde66b6f59 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b594036290443faedb7ec11ec57ab4727d3ddc68fd09b7ac5a0a121691b499a4 +size 542610 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg new file mode 100644 index 0000000000..c2389a98ef --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9df47aa652d31050034e301df65ec327aee9d2ee2ea051a3ef7f4f5915641681 +size 561591 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg new file mode 100644 index 0000000000..6e6b9f4437 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06e0b522c1e8bedf5d03f7a0bec00ef474a886def8bbd514c6f4eba6af97880d +size 652375 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg new file mode 100644 index 0000000000..767bd0c50b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9bc290239d1739febabeda84191c6fadb206638c7f1cc13240681a0c6596ecb +size 810044 From d1df701983d985c33c6dcfd976ef3d380dd7d30e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 20 Jun 2022 00:16:34 +0300 Subject: [PATCH 32/39] Added tests for non-interleaved mode --- .../Formats/Jpg/JpegEncoderTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 2ae993b8fb..5e38f4455c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -221,6 +221,40 @@ public void EncodeBaseline_WorksWithAllColorTypes(TestImageProvider(TestImageProvider provider, JpegEncodingColor colorType) + where TPixel : unmanaged, IPixel + { + // all reference output images are saved with quality=100 + const int quality = 100; + + using Image image = provider.GetImage(); + + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder + { + ColorType = colorType, + Interleaved = false + }; + string info = $"{colorType}-Q{quality}"; + + ImageComparer comparer = GetComparer(quality, colorType); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } + [Theory] [MemberData(nameof(RatioFiles))] public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) From c9b5e77430045cc5471dac58677f46309850bfdb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 4 Jul 2022 09:05:09 +0300 Subject: [PATCH 33/39] Fixed non-interleaved encoding with subsampling --- .../Jpeg/Components/Encoder/Component.cs | 7 ++++-- .../Components/Encoder/ComponentProcessor.cs | 25 +++++++++++-------- .../Formats/Jpg/JpegEncoderTests.cs | 1 - 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs index 59c5c69c80..cf02865241 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs @@ -84,11 +84,14 @@ public void Dispose() /// Maximal vertical subsampling factor among all the components. public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) { + uint widthInBlocks = ((uint)frame.PixelWidth + 7) / 8; + uint heightInBlocks = ((uint)frame.PixelHeight + 7) / 8; + this.WidthInBlocks = (int)MathF.Ceiling( - ((uint)frame.PixelWidth + 7) / 8 * this.HorizontalSamplingFactor / maxSubFactorH); + (float)widthInBlocks * this.HorizontalSamplingFactor / maxSubFactorH); this.HeightInBlocks = (int)MathF.Ceiling( - ((uint)frame.PixelHeight + 7) / 8 * this.VerticalSamplingFactor / maxSubFactorV); + (float)heightInBlocks * this.VerticalSamplingFactor / maxSubFactorV); int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index 8c9f826a10..9795ab9534 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -157,18 +157,24 @@ static void SumVertical(Span target, Span source) static void SumHorizontal(Span target, int factor) { + Span source = target; if (Avx2.IsSupported) { ref Vector256 targetRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); // Ideally we need to use log2: Numerics.Log2((uint)factor) // but division by 2 works just fine in this case - // because factor value range is [1, 2, 4] - // log2(1) == 1 / 2 == 0 - // log2(2) == 2 / 2 == 1 - // log2(4) == 4 / 2 == 2 int haddIterationsCount = (int)((uint)factor / 2); - uint length = (uint)target.Length / (uint)Vector256.Count; + + // Transform spans so that it only contains 'remainder' + // values for the scalar fallback code + int scalarRemainder = target.Length % (Vector.Count * factor); + int touchedCount = target.Length - scalarRemainder; + source = source.Slice(touchedCount); + target = target.Slice(touchedCount / factor); + + uint length = (uint)touchedCount / (uint)Vector256.Count; + for (int i = 0; i < haddIterationsCount; i++) { length /= 2; @@ -181,18 +187,15 @@ static void SumHorizontal(Span target, int factor) Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); } } - - int summedCount = (int)(length * factor * Vector256.Count); - target = target.Slice(summedCount); } // scalar remainder - for (int i = 0; i < target.Length / factor; i++) + for (int i = 0; i < source.Length / factor; i++) { - target[i] = target[i * factor]; + target[i] = source[i * factor]; for (int j = 1; j < factor; j++) { - target[i] += target[(i * factor) + j]; + target[i] += source[(i * factor) + j]; } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 5e38f4455c..52bddd27f1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -143,7 +143,6 @@ public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider Date: Sat, 6 Aug 2022 17:56:34 +0300 Subject: [PATCH 34/39] Fixed spectral block border pixel padding --- .../Jpeg/Components/Encoder/ComponentProcessor.cs | 8 +------- .../Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs | 9 +++++++++ tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index 9795ab9534..5d858f84d1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -43,13 +43,7 @@ public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, public void CopyColorBufferToBlocks(int spectralStep) { Buffer2D spectralBuffer = this.component.SpectralBlocks; - - // should be this.frame.MaxColorChannelValue - // but 12-bit jpegs are not supported currently - float normalizationValue = -128f; - int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = spectralStep * this.component.SamplingFactors.Height; Block8x8F workspaceBlock = default; @@ -77,7 +71,7 @@ public void CopyColorBufferToBlocks(int spectralStep) destAreaStride); // level shift via -128f - workspaceBlock.AddInPlace(normalizationValue); + workspaceBlock.AddInPlace(-128f); // FDCT FastFloatingPointDCT.TransformFDCT(ref workspaceBlock); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 44271248f5..a1c335b4f1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -98,9 +98,14 @@ private void ConvertStride(int spectralStep) int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1; + // Pixel strides must be padded with the last pixel of the stride + int paddingStartIndex = this.pixelBuffer.Width; + int paddedPixelsCount = this.alignedPixelWidth - this.pixelBuffer.Width; + Span rLane = this.redLane.GetSpan(); Span gLane = this.greenLane.GetSpan(); Span bLane = this.blueLane.GetSpan(); + for (int yy = start; yy < end; yy++) { int y = yy - this.pixelRowCounter; @@ -110,6 +115,10 @@ private void ConvertStride(int spectralStep) Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); + rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]); + gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]); + bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]); + // Convert from rgb24 to target pixel type var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 52bddd27f1..d9097ed16e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -148,6 +148,7 @@ public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider(TestImageProvider provider, JpegEncodingColor colorType, int quality) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.15f)); From a9c7f4bb3cb24ffff4b56d5c682d3a4323038d80 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 6 Aug 2022 19:33:16 +0300 Subject: [PATCH 35/39] Merge fixes --- shared-infrastructure | 2 +- .../Jpeg/Components/Block8x8F.ScaledCopy.cs | 2 +- .../ColorConverters/JpegColorConverter.CmykAvx.cs | 2 +- .../JpegColorConverter.CmykScalar.cs | 2 +- .../JpegColorConverter.CmykVector.cs | 2 +- .../JpegColorConverter.GrayScaleAvx.cs | 2 +- .../JpegColorConverter.GrayScaleScalar.cs | 2 +- .../JpegColorConverter.GrayScaleVector.cs | 2 +- .../JpegColorConverter.RgbScalar.cs | 2 +- .../JpegColorConverter.YCbCrAvx.cs | 2 +- .../JpegColorConverter.YCbCrScalar.cs | 2 +- .../JpegColorConverter.YCbCrVector.cs | 2 +- .../ColorConverters/JpegColorConverter.YccKAvx.cs | 2 +- .../JpegColorConverter.YccKScalar.cs | 2 +- .../JpegColorConverter.YccKVector.cs | 2 +- .../ColorConverters/JpegColorConverterAvx.cs | 4 +++- .../ColorConverters/JpegColorConverterBase.cs | 2 +- .../ColorConverters/JpegColorConverterVector.cs | 4 +++- .../Components/Decoder/ArithmeticScanDecoder.cs | 3 +-- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 3 +-- .../Formats/Jpeg/Components/Decoder/JpegFrame.cs | 2 +- .../Formats/Jpeg/Components/Encoder/Component.cs | 2 +- .../Jpeg/Components/Encoder/ComponentProcessor.cs | 4 ++-- .../EncodingConfigs/JpegComponentConfig.cs | 2 +- .../Encoder/EncodingConfigs/JpegFrameConfig.cs | 2 +- .../EncodingConfigs/JpegHuffmanTableConfig.cs | 2 +- .../JpegQuantizationTableConfig.cs | 2 +- .../Formats/Jpeg/Components/Encoder/JpegFrame.cs | 2 +- .../Jpeg/Components/Encoder/SpectralConverter.cs | 2 +- .../Encoder/SpectralConverter{TPixel}.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 11 ----------- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 2 +- .../Decompressors/RgbJpegSpectralConverter.cs | 2 +- .../TiffJpegSpectralConverter{TPixel}.cs | 2 +- .../LoadResizeSave/LoadResizeSaveStressRunner.cs | 4 +++- .../ImageSharp.Tests.ProfilingSandbox/Program.cs | 15 ++++++++------- .../Formats/Jpg/JpegDecoderTests.Internal.cs | 15 ++------------- ...ceImplementationsTests.FastFloatingPointDCT.cs | 4 ++-- .../Formats/Jpg/SpectralConverterTests.cs | 2 +- 39 files changed, 54 insertions(+), 71 deletions(-) diff --git a/shared-infrastructure b/shared-infrastructure index c0e0353c1e..59ce17f5a4 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit c0e0353c1ee89398def0ccdc3e945380034fbea8 +Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index c258602562..e50175ffa0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System.Numerics; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs index f65c74d782..8edaa2efe3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. #if SUPPORTS_RUNTIME_INTRINSICS using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs index 2cca688dc4..70d47b9b79 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs index 1e62484e32..6d7688bcd8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Numerics; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs index a3999ffda3..26e8791853 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. #if SUPPORTS_RUNTIME_INTRINSICS using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs index 40e2da0beb..2e5129f328 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Runtime.CompilerServices; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs index fa0a9b27a9..0f903c0519 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Numerics; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs index e798196861..a3de4493fd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs index 517fbdbeb9..219584b05e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. #if SUPPORTS_RUNTIME_INTRINSICS using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs index 0286df5813..7fd6283287 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs index e0c44a31e3..f747d5523d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Numerics; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs index 1e641b0671..f4549911ef 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. #if SUPPORTS_RUNTIME_INTRINSICS using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index 1c03c4eef3..dc58a70edf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index c84416fc40..ef43dca408 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Numerics; diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs index 6cd04c2fc2..4af31abac9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs @@ -26,7 +26,9 @@ protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) { } - public sealed override bool IsAvailable => Avx.IsSupported; + public static bool IsSupported => Avx.IsSupported; + + public sealed override bool IsAvailable => IsSupported; public sealed override int ElementsPerBatch => Vector256.Count; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index db2159ba05..c2e01df998 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -269,7 +269,7 @@ public ComponentValues(IReadOnlyList> componentBuffers, int row) /// /// List of component color processors. /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) + public ComponentValues(IReadOnlyList processors, int row) { DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs index 92d388fc8f..e618652989 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Numerics; @@ -34,6 +34,8 @@ protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) /// public sealed override bool IsAvailable => IsSupported; + public override int ElementsPerBatch => Vector.Count; + /// public sealed override void ConvertToRgbInplace(in ComponentValues values) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs index 592b4bbaea..ae97c7e54a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs @@ -247,8 +247,7 @@ public void ParseEntropyCodedData(int scanComponentCount) this.scanBuffer = new JpegBitReader(this.stream); - bool fullScan = this.frame.Progressive || !this.frame.Interleaved; - this.frame.AllocateComponents(fullScan); + this.frame.AllocateComponents(); if (this.frame.Progressive) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 8013e51f8f..6f57dff99c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -118,8 +118,7 @@ public void ParseEntropyCodedData(int scanComponentCount) this.scanBuffer = new JpegBitReader(this.stream); - bool fullScan = this.frame.Progressive || !this.frame.Interleaved; - this.frame.AllocateComponents(fullScan); + this.frame.AllocateComponents(); if (!this.frame.Progressive) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 95fe6ee58d..2b98746546 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -141,7 +141,7 @@ public void Init(int maxSubFactorH, int maxSubFactorV) public void AllocateComponents() { - bool fullScan = this.Progressive || this.MultiScan; + bool fullScan = this.Progressive || this.Interleaved; for (int i = 0; i < this.ComponentCount; i++) { IJpegComponent component = this.Components[i]; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs index cf02865241..c489520abf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using SixLabors.ImageSharp.Memory; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index 5d858f84d1..f13eedb813 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Numerics; @@ -74,7 +74,7 @@ public void CopyColorBufferToBlocks(int spectralStep) workspaceBlock.AddInPlace(-128f); // FDCT - FastFloatingPointDCT.TransformFDCT(ref workspaceBlock); + FloatingPointDCT.TransformFDCT(ref workspaceBlock); // Quantize and save to spectral blocks Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs index b9a589e21f..519b8c0b8b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs index efff92b2bb..18afff7383 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs index d0c3038db7..cf7c152cb0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs index 29f0c05db8..ada2c464a6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index d45de2c16a..990c218a8c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using SixLabors.ImageSharp.Advanced; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs index b85cf1d371..5cc8a0e34f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index a1c335b4f1..07f9e2e49f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; using System.Buffers; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 7f23d1ac85..b2ad00e077 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -31,17 +31,6 @@ public Image Decode(Configuration configuration, Stream stream, public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); - /// - /// Placeholder summary. - /// - /// Placeholder2 - /// Placeholder3 - /// Placeholder4 - /// Placeholder5 - /// Placeholder6 - internal Image DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken) - => this.DecodeInto(configuration, stream, targetSize, cancellationToken); - /// /// Decodes and downscales the image from the specified stream if possible. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index eb1005cb59..0336d71d32 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -682,7 +682,7 @@ private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs // apply FDCT multipliers and inject to the destination index workspaceBlock.LoadFrom(ref scaledTable); - FastFloatingPointDCT.AdjustToFDCT(ref workspaceBlock); + FloatingPointDCT.AdjustToFDCT(ref workspaceBlock); this.QuantizationTables[config.DestinationIndex] = workspaceBlock; } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index f303dc2e2b..10ded4db65 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs index ced6b9027c..3b86d1e87a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index c26de91590..50abcf89d8 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -10,10 +10,12 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; using SkiaSharp; @@ -210,7 +212,7 @@ public void ImageSharpResize(string input) // Resize it to fit a 150x150 square var targetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize); var decoder = new JpegDecoder(); - using ImageSharpImage image = decoder.DecodeInto(Configuration.Default, inputStream, targetSize, default); + using ImageSharpImage image = decoder.DecodeInto(Configuration.Default, inputStream, targetSize, CancellationToken.None); this.LogImageProcessed(image.Width, image.Height); image.Mutate(i => i.Resize(new ResizeOptions diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index cf8436d802..4a0a728ad9 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -38,14 +38,15 @@ private class ConsoleOutput : ITestOutputHelper public static void Main(string[] args) { //string imageName = "Calliphora_aligned_size"; - string imageName = "Calliphora"; + //string imageName = "Calliphora"; + string imageName = "1x1"; //string imageName = "bw_check"; //string imageName = "bw_check_color"; - //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio444, 100); - //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio422, 100); - //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio420, 100); - //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio411, 100); - //ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio410, 100); + ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio444, 100); + ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio422, 100); + ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio420, 100); + ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio411, 100); + ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio410, 100); //ReEncodeImage(imageName, JpegEncodingColor.Luminance, 100); //ReEncodeImage(imageName, JpegEncodingColor.Rgb, 100); //ReEncodeImage(imageName, JpegEncodingColor.Cmyk, 100); @@ -53,7 +54,7 @@ public static void Main(string[] args) // Encoding q=75 | color=YCbCrRatio444 // Elapsed: 4901ms across 500 iterations // Average: 9,802ms - BenchmarkEncoder(imageName, 500, 75, JpegEncodingColor.YCbCrRatio444); + //BenchmarkEncoder(imageName, 500, 75, JpegEncodingColor.YCbCrRatio444); } private static void BenchmarkEncoder(string fileName, int iterations, int quality, JpegEncodingColor color) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs index 6bf7ae88fa..564e191bbd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -1,22 +1,11 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 50c2a08bbd..31acb3b882 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class ReferenceImplementationsTests { - public class FastFloatingPointDCT : JpegFixture + public class FloatingPointDCT : JpegFixture { - public FastFloatingPointDCT(ITestOutputHelper output) + public FloatingPointDCT(ITestOutputHelper output) : base(output) { } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs index 008ca20c3f..6ca3a728f5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs @@ -1,5 +1,5 @@ // Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. +// Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using Xunit; From 2ee191343cd1de7d043e0e6ba873f0c9570e9ef8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 7 Aug 2022 03:43:54 +0300 Subject: [PATCH 36/39] shared-infrastructure --- shared-infrastructure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-infrastructure b/shared-infrastructure index 59ce17f5a4..c0e0353c1e 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 +Subproject commit c0e0353c1ee89398def0ccdc3e945380034fbea8 From ade236e8723f2ca6b19578d10a1b0dea2f904801 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 7 Aug 2022 04:25:23 +0300 Subject: [PATCH 37/39] Huffman table cleanup --- .../Jpeg/Components/Encoder/HuffmanLut.cs | 26 -- .../Jpeg/Components/Encoder/HuffmanSpec.cs | 203 ++++++++-------- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 2 - .../Jpeg/JpegEncoderCore.FrameConfig.cs | 196 +++++++++++++++ .../Formats/Jpeg/JpegEncoderCore.cs | 226 ++---------------- 5 files changed, 325 insertions(+), 328 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index c371a199e1..bd4cc5545f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -26,32 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal readonly struct HuffmanLut { - /// - /// The compiled representations of theHuffmanSpec. - /// - public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4]; - - public static readonly HuffmanLut[] DcHuffmanLut = new HuffmanLut[2]; - public static readonly HuffmanLut[] AcHuffmanLut = new HuffmanLut[2]; - - /// - /// Initializes static members of the struct. - /// - static HuffmanLut() - { - // Initialize the Huffman tables - for (int i = 0; i < HuffmanSpec.TheHuffmanSpecs.Length; i++) - { - TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]); - } - - // TODO: REWRITE THIS - DcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[0]); - DcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[2]); - AcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[1]); - AcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[3]); - } - /// /// Initializes a new instance of the struct. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 5ec8fd198e..97f051c76c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -8,109 +8,120 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// public readonly struct HuffmanSpec { -#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines + /// + /// Huffman talbe specification for luminance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec LuminanceDC = new( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }); /// - /// The Huffman encoding specifications. - /// This encoder uses the same Huffman encoding for all images. + /// Huffman talbe specification for luminance AC. /// - public static readonly HuffmanSpec[] TheHuffmanSpecs = + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec LuminanceAC = new( + new byte[] { - // Luminance DC. - new HuffmanSpec( - new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, + 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, + 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, + 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }); - // Luminance AC. - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, - 0, 1, 125 - }, - new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, - 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, - 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, - 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, - 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, - 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, - 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, - 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, - 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, - 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, - 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, - 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, - 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - }), + /// + /// Huffman talbe specification for chrominance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec ChrominanceDC = new( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }); - // Chrominance DC. - new HuffmanSpec( - new byte[] - { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }), + /// + /// Huffman talbe specification for chrominance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec ChrominanceAC = new( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, + 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, + 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, + 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, + 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }); - // Chrominance AC. - new HuffmanSpec( - new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, - 1, 2, 119 - }, - new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, - 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, - 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, - 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, - 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, - 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, - 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, - 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, - 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, - 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, - 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, - 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, - 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, - 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, - 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - }) - }; -#pragma warning restore SA1118 // ParameterMustNotSpanMultipleLines /// /// Gets count[i] - The number of codes of length i bits. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index c86f37dd8a..5bad2a5b04 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -41,8 +41,6 @@ public int? Quality /// public JpegEncodingColor? ColorType { get; set; } - internal JpegFrameConfig FrameConfig { get; set; } - /// /// Encodes the image to the specified stream from the . /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs new file mode 100644 index 0000000000..2397c75639 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs @@ -0,0 +1,196 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Image encoder for writing an image to a stream as a jpeg. + /// + internal sealed unsafe partial class JpegEncoderCore + { + private static JpegFrameConfig[] CreateFrameConfigs() + { + var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.LuminanceDC); + var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.LuminanceAC); + var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.ChrominanceDC); + var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.ChrominanceAC); + + var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable); + var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable); + + var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC, + defaultChrominanceHuffmanDC, + defaultChrominanceHuffmanAC, + }; + + var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable, + defaultChrominanceQuantTable, + }; + + return new JpegFrameConfig[] + { + // YCbCr 4:4:4 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio444, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:2 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio422, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:2:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio420, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:1 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio411, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // YCbCr 4:1:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio410, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), + + // Luminance + new JpegFrameConfig( + JpegColorSpace.Grayscale, + JpegEncodingColor.Luminance, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), + + // Rgb + new JpegFrameConfig( + JpegColorSpace.RGB, + JpegEncodingColor.Rgb, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + }, + + // Cmyk + new JpegFrameConfig( + JpegColorSpace.Cmyk, + JpegEncodingColor.Cmyk, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, + }, + + // YccK + new JpegFrameConfig( + JpegColorSpace.Ycck, + JpegEncodingColor.Ycck, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, + }, + }; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 0336d71d32..ea29e071ce 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Image encoder for writing an image to a stream as a jpeg. /// - internal sealed unsafe class JpegEncoderCore : IImageEncoderInternals + internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals { /// /// The available encodable frame configs. @@ -244,8 +244,8 @@ private void WriteExifProfile(ExifProfile exifProfile) return; } - const int MaxBytesApp1 = 65533; // 64k - 2 padding bytes - const int MaxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. + const int maxBytesApp1 = 65533; // 64k - 2 padding bytes + const int maxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. byte[] data = exifProfile.ToByteArray(); @@ -257,7 +257,7 @@ private void WriteExifProfile(ExifProfile exifProfile) // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length; int remaining = exifMarkerLength + data.Length; - int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; + int bytesToWrite = remaining > maxBytesApp1 ? maxBytesApp1 : remaining; int app1Length = bytesToWrite + 2; // Write the app marker, EXIF marker, and data @@ -267,9 +267,9 @@ private void WriteExifProfile(ExifProfile exifProfile) remaining -= bytesToWrite; // If the exif data exceeds 64K, write it in multiple APP1 Markers - for (int idx = MaxBytesWithExifId; idx < data.Length; idx += MaxBytesWithExifId) + for (int idx = maxBytesWithExifId; idx < data.Length; idx += maxBytesWithExifId) { - bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining; + bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining; app1Length = bytesToWrite + 2 + exifMarkerLength; this.WriteApp1Header(app1Length); @@ -293,7 +293,7 @@ private void WriteExifProfile(ExifProfile exifProfile) /// private void WriteIptcProfile(IptcProfile iptcProfile) { - const int Max = 65533; + const int maxBytes = 65533; if (iptcProfile is null || !iptcProfile.Values.Any()) { return; @@ -306,9 +306,9 @@ private void WriteIptcProfile(IptcProfile iptcProfile) return; } - if (data.Length > Max) + if (data.Length > maxBytes) { - throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes"); + throw new ImageFormatException($"Iptc profile size exceeds limit of {maxBytes} bytes"); } int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length + @@ -340,9 +340,9 @@ private void WriteXmpProfile(XmpProfile xmpProfile) return; } - const int XmpOverheadLength = 29; - const int Max = 65533; - const int MaxData = Max - XmpOverheadLength; + const int xmpOverheadLength = 29; + const int maxBytes = 65533; + const int maxData = maxBytes - xmpOverheadLength; byte[] data = xmpProfile.Data; @@ -358,9 +358,9 @@ private void WriteXmpProfile(XmpProfile xmpProfile) { int length = dataLength; // Number of bytes to write. - if (length > MaxData) + if (length > maxData) { - length = MaxData; + length = maxData; } dataLength -= length; @@ -410,9 +410,9 @@ private void WriteIccProfile(IccProfile iccProfile) return; } - const int IccOverheadLength = 14; - const int Max = 65533; - const int MaxData = Max - IccOverheadLength; + const int iccOverheadLength = 14; + const int maxBytes = 65533; + const int maxData = maxBytes - iccOverheadLength; byte[] data = iccProfile.ToByteArray(); @@ -423,9 +423,9 @@ private void WriteIccProfile(IccProfile iccProfile) // Calculate the number of markers we'll need, rounding up of course. int dataLength = data.Length; - int count = dataLength / MaxData; + int count = dataLength / maxData; - if (count * MaxData != dataLength) + if (count * maxData != dataLength) { count++; } @@ -438,9 +438,9 @@ private void WriteIccProfile(IccProfile iccProfile) { int length = dataLength; // Number of bytes to write. - if (length > MaxData) + if (length > maxData) { - length = MaxData; + length = maxData; } dataLength -= length; @@ -468,7 +468,7 @@ private void WriteIccProfile(IccProfile iccProfile) this.buffer[12] = (byte)current; // The position within the collection. this.buffer[13] = (byte)count; // The total number of profiles. - this.outputStream.Write(this.buffer, 0, IccOverheadLength); + this.outputStream.Write(this.buffer, 0, iccOverheadLength); this.outputStream.Write(data, offset, length); current++; @@ -712,187 +712,5 @@ private JpegFrameConfig GetFrameConfig(JpegMetadata metadata) return frameConfig; } - - private static JpegFrameConfig[] CreateFrameConfigs() - { - var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[0]); - var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.TheHuffmanSpecs[1]); - var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[2]); - var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.TheHuffmanSpecs[3]); - - var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable); - var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable); - - var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC, - defaultChrominanceHuffmanDC, - defaultChrominanceHuffmanAC, - }; - - var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable, - defaultChrominanceQuantTable, - }; - - return new JpegFrameConfig[] - { - // YCbCr 4:4:4 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio444, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:2:2 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio422, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:2:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio420, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:1:1 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio411, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // YCbCr 4:1:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio410, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), - - // Luminance - new JpegFrameConfig( - JpegColorSpace.Grayscale, - JpegEncodingColor.Luminance, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }), - - // Rgb - new JpegFrameConfig( - JpegColorSpace.RGB, - JpegEncodingColor.Rgb, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown - }, - - // Cmyk - new JpegFrameConfig( - JpegColorSpace.Cmyk, - JpegEncodingColor.Cmyk, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, - }, - - // YccK - new JpegFrameConfig( - JpegColorSpace.Ycck, - JpegEncodingColor.Ycck, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, - }, - }; - } } } From 4ab4ae23526c8b34489076d20ca43e3c07fc920d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 7 Aug 2022 04:29:02 +0300 Subject: [PATCH 38/39] Tests, fixed interleaved decoding I messed up during main merge --- .../Jpeg/Components/Decoder/JpegFrame.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 1 - .../Program.cs | 208 +---------- .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 82 +++++ .../Formats/Jpg/JpegEncoderTests.cs | 332 ++++++------------ .../JpegProfilingBenchmarks.cs | 137 -------- 6 files changed, 214 insertions(+), 548 deletions(-) delete mode 100644 tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 2b98746546..5c6535bd37 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -141,7 +141,7 @@ public void Init(int maxSubFactorH, int maxSubFactorV) public void AllocateComponents() { - bool fullScan = this.Progressive || this.Interleaved; + bool fullScan = this.Progressive || !this.Interleaved; for (int i = 0; i < this.ComponentCount; i++) { IJpegComponent component = this.Components[i]; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 5bad2a5b04..7fe6a4990d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 4a0a728ad9..5b5dedaab3 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,18 +2,10 @@ // Licensed under the Six Labors Split License. using System; -using System.Diagnostics; -using System.IO; using System.Reflection; using System.Threading; -using PhotoSauce.MagicScaler; -using PhotoSauce.MagicScaler.Interpolators; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; using Xunit.Abstractions; @@ -33,173 +25,31 @@ private class ConsoleOutput : ITestOutputHelper public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); } - const string pathTemplate = "C:\\Users\\pl4nu\\Downloads\\{0}.jpg"; - + /// + /// The main entry point. Useful for executing benchmarks and performance unit tests manually, + /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. + /// + /// + /// The arguments to pass to the program. + /// public static void Main(string[] args) { - //string imageName = "Calliphora_aligned_size"; - //string imageName = "Calliphora"; - string imageName = "1x1"; - //string imageName = "bw_check"; - //string imageName = "bw_check_color"; - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio444, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio422, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio420, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio411, 100); - ReEncodeImage(imageName, JpegEncodingColor.YCbCrRatio410, 100); - //ReEncodeImage(imageName, JpegEncodingColor.Luminance, 100); - //ReEncodeImage(imageName, JpegEncodingColor.Rgb, 100); - //ReEncodeImage(imageName, JpegEncodingColor.Cmyk, 100); - - // Encoding q=75 | color=YCbCrRatio444 - // Elapsed: 4901ms across 500 iterations - // Average: 9,802ms - //BenchmarkEncoder(imageName, 500, 75, JpegEncodingColor.YCbCrRatio444); - } - - private static void BenchmarkEncoder(string fileName, int iterations, int quality, JpegEncodingColor color) - { - string loadPath = String.Format(pathTemplate, fileName); - - using var inputStream = new FileStream(loadPath, FileMode.Open); - using var saveStream = new MemoryStream(); - - var decoder = new JpegDecoder { IgnoreMetadata = true }; - using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); - - var encoder = new JpegEncoder() - { - Quality = quality, - ColorType = color, - }; - - Stopwatch sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) - { - img.SaveAsJpeg(saveStream, encoder); - saveStream.Position = 0; - } - sw.Stop(); - - Console.WriteLine($"// Encoding q={quality} | color={color}\n" + - $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + - $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); - } - - private static void BenchmarkDecoder(string fileName, int iterations) - { - string loadPath = String.Format(pathTemplate, fileName); - - using var fileStream = new FileStream(loadPath, FileMode.Open); - using var inputStream = new MemoryStream(); - fileStream.CopyTo(inputStream); - - var decoder = new JpegDecoder { IgnoreMetadata = true }; - - var sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) + try { - inputStream.Position = 0; - using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); + LoadResizeSaveParallelMemoryStress.Run(args); } - sw.Stop(); - - Console.WriteLine($"// Decoding\n" + - $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + - $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); - } - - private static void BenchmarkResizingLoop__explicit(string fileName, Size targetSize, int iterations) - { - string loadPath = String.Format(pathTemplate, fileName); - - using var fileStream = new FileStream(loadPath, FileMode.Open); - using var saveStream = new MemoryStream(); - using var inputStream = new MemoryStream(); - fileStream.CopyTo(inputStream); - - var decoder = new JpegDecoder { IgnoreMetadata = true }; - var encoder = new JpegEncoder { ColorType = JpegEncodingColor.YCbCrRatio444 }; - - var sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < iterations; i++) + catch (Exception ex) { - inputStream.Position = 0; - using Image img = decoder.Decode(Configuration.Default, inputStream, CancellationToken.None); - img.Mutate(ctx => ctx.Resize(targetSize, KnownResamplers.Box, false)); - img.SaveAsJpeg(saveStream, encoder); + Console.WriteLine(ex); } - sw.Stop(); - - Console.WriteLine($"// Decode-Resize-Encode w/ Mutate()\n" + - $"// Elapsed: {sw.ElapsedMilliseconds}ms across {iterations} iterations\n" + - $"// Average: {(double)sw.ElapsedMilliseconds / iterations}ms"); - } - private static void ReEncodeImage(string fileName, JpegEncodingColor mode, int? quality = null) - { - string loadPath = String.Format(pathTemplate, fileName); - using Image img = Image.Load(loadPath); - - string savePath = String.Format(pathTemplate, $"q{quality}_{mode}_test_{fileName}"); - var encoder = new JpegEncoder() - { - Quality = quality, - ColorType = mode, - }; - img.SaveAsJpeg(savePath, encoder); - } - - private static void ReencodeImageResize__explicit(string fileName, Size targetSize, IResampler sampler, int? quality = null) - { - string loadPath = String.Format(pathTemplate, fileName); - string savePath = String.Format(pathTemplate, $"is_res_{sampler.GetType().Name}[{targetSize.Width}x{targetSize.Height}]_{fileName}"); - - var decoder = new JpegDecoder(); - var encoder = new JpegEncoder() - { - Quality = quality, - ColorType = JpegEncodingColor.YCbCrRatio444 - }; - - using Image img = decoder.Decode(Configuration.Default, File.OpenRead(loadPath), CancellationToken.None); - img.Mutate(ctx => ctx.Resize(targetSize, sampler, compand: false)); - img.SaveAsJpeg(savePath, encoder); - } - - private static void ReencodeImageResize__Netvips(string fileName, Size targetSize, int? quality) - { - string loadPath = String.Format(pathTemplate, fileName); - string savePath = String.Format(pathTemplate, $"netvips_resize_{fileName}"); - - using var thumb = NetVips.Image.Thumbnail(loadPath, targetSize.Width, targetSize.Height); - - // Save the results - thumb.Jpegsave(savePath, q: quality, strip: true, subsampleMode: NetVips.Enums.ForeignSubsample.Off); - } - - private static void ReencodeImageResize__MagicScaler(string fileName, Size targetSize, int quality) - { - string loadPath = String.Format(pathTemplate, fileName); - string savePath = String.Format(pathTemplate, $"magicscaler_resize_{fileName}"); + // RunJpegEncoderProfilingTests(); + // RunJpegColorProfilingTests(); + // RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); + // RunResizeProfilingTest(); - var settings = new ProcessImageSettings() - { - Width = targetSize.Width, - Height = targetSize.Height, - SaveFormat = FileFormat.Jpeg, - JpegQuality = quality, - JpegSubsampleMode = ChromaSubsampleMode.Subsample444, - Sharpen = false, - ColorProfileMode = ColorProfileMode.Ignore, - HybridMode = HybridScaleMode.Turbo, - }; - - using var output = new FileStream(savePath, FileMode.Create); - MagicImageProcessor.ProcessImage(loadPath, output, settings); + // Console.ReadLine(); } private static Version GetNetCoreVersion() @@ -216,12 +66,6 @@ private static Version GetNetCoreVersion() return null; } - private static void RunJpegEncoderProfilingTests() - { - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - benchmarks.EncodeJpeg_SingleMidSize(); - } - private static void RunResizeProfilingTest() { var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); @@ -233,19 +77,5 @@ private static void RunToVector4ProfilingTest() var tests = new PixelOperationsTests.Rgba32_OperationsTests(new ConsoleOutput()); tests.Benchmark_ToVector4(); } - - private static void RunDecodeJpegProfilingTests() - { - Console.WriteLine("RunDecodeJpegProfilingTests..."); - var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); - foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) - { - string fileName = (string)data[0]; - int executionCount = (int)data[1]; - benchmarks.DecodeJpeg(fileName, executionCount); - } - - Console.WriteLine("DONE."); - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index af07338fb5..60f45664d3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; @@ -17,6 +18,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class JpegEncoderTests { + public static readonly TheoryData RatioFiles = + new() + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; + + public static readonly TheoryData QualityFiles = + new() + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; + [Fact] public void Encode_PreservesIptcProfile() { @@ -95,5 +111,71 @@ public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageP Assert.Null(ex); } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + } + } + } + + [Theory] + [MemberData(nameof(QualityFiles))] + public void Encode_PreservesQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } + } + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(JpegDecoder); + using var memoryStream = new MemoryStream(); + + // act + input.Save(memoryStream, JpegEncoder); + + // assert + memoryStream.Position = 0; + using var output = Image.Load(memoryStream); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 4f16282f47..8a78ef6485 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -22,139 +21,122 @@ public partial class JpegEncoderTests private static JpegDecoder JpegDecoder => new(); - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; - - public static readonly TheoryData BitsPerPixel_Quality = - new() - { - { JpegEncodingColor.YCbCrRatio420, 40 }, - { JpegEncodingColor.YCbCrRatio420, 60 }, - { JpegEncodingColor.YCbCrRatio420, 100 }, - { JpegEncodingColor.YCbCrRatio444, 40 }, - { JpegEncodingColor.YCbCrRatio444, 60 }, - { JpegEncodingColor.YCbCrRatio444, 100 }, - { JpegEncodingColor.Rgb, 40 }, - { JpegEncodingColor.Rgb, 60 }, - { JpegEncodingColor.Rgb, 100 } - }; + private static readonly TheoryData TestQualities = new() + { + 40, + 80, + 100, + }; - public static readonly TheoryData Grayscale_Quality = - new() - { - { 40 }, - { 60 }, - { 100 } - }; + public static readonly TheoryData NonSubsampledEncodingSetups = new() + { + { JpegEncodingColor.Rgb, 100, 0.0238f / 100 }, + { JpegEncodingColor.Rgb, 80, 1.3044f / 100 }, + { JpegEncodingColor.Rgb, 40, 2.9879f / 100 }, - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; + { JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 80, 1.4585f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 40, 3.1413f / 100 }, + }; - [Fact] - public void Quality_1_And_100_Are_Not_Identical() + public static readonly TheoryData SubsampledEncodingSetups = new() { - var options = new JpegEncoder - { - Quality = 1 - }; + { JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 80, 1.6043f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 40, 3.1996f / 100 }, - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + { JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 80, 1.6692f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 40, 3.2324f / 100 }, - using (Image input = testFile.CreateRgba32Image()) - using (var memStream0 = new MemoryStream()) - using (var memStream1 = new MemoryStream()) - { - input.SaveAsJpeg(memStream0, options); + { JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 80, 1.7139f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 40, 3.2634f / 100 }, - options.Quality = 100; - input.SaveAsJpeg(memStream1, options); + { JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 80, 1.7495f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 40, 3.2911f / 100 }, + }; - Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); - } - } + public static readonly TheoryData CmykEncodingSetups = new() + { + { JpegEncodingColor.Cmyk, 100, 0.0159f / 100 }, + { JpegEncodingColor.Cmyk, 80, 0.3922f / 100 }, + { JpegEncodingColor.Cmyk, 40, 0.6488f / 100 }, + }; - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegEncodingColor.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) - where TPixel : unmanaged, IPixel + public static readonly TheoryData YcckEncodingSetups = new() { - // arrange - using Image input = provider.GetImage(JpegDecoder); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, JpegEncoder); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + { JpegEncodingColor.Ycck, 100, 0.0356f / 100 }, + { JpegEncodingColor.Ycck, 80, 0.1245f / 100 }, + { JpegEncodingColor.Ycck, 40, 0.2663f / 100 }, + }; + + public static readonly TheoryData LuminanceEncodingSetups = new() + { + { JpegEncodingColor.Luminance, 100, 0.0175f / 100 }, + { JpegEncodingColor.Luminance, 80, 0.6730f / 100 }, + { JpegEncodingColor.Luminance, 40, 0.9941f / 100 }, + }; + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_Interleaved(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, tolerance); [Theory] - [MemberData(nameof(QualityFiles))] - public void Encode_PreservesQuality(string imagePath, int quality) + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_NonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using Image image = provider.GetImage(); + + var encoder = new JpegEncoder { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - } - } - } + Quality = quality, + ColorType = colorType, + Interleaved = false, + }; + string info = $"{colorType}-Q{quality}"; - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - public void EncodeBaseline_CalliphoraPartial(TestImageProvider provider, JpegEncodingColor colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); + ImageComparer comparer = new TolerantImageComparer(tolerance); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] - [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.15f)); + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)] + public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f)); [Theory] - [WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgba32, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] @@ -163,20 +145,21 @@ public void EncodeBaseline_Grayscale(TestImageProvider provider, where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality) + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegEncodingColor.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegEncodingColor.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingColor colorType) where TPixel : unmanaged, IPixel { @@ -188,96 +171,6 @@ public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvid TestJpegEncoderCore(provider, colorType, 100, comparer); } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.L8, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio422)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio411)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio410)] - public void EncodeBaseline_WorksWithAllColorTypes(TestImageProvider provider, JpegEncodingColor colorType) - where TPixel : unmanaged, IPixel - { - // all reference output images are saved with quality=100 - const int quality = 100; - - using Image image = provider.GetImage(); - - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - - var encoder = new JpegEncoder - { - Quality = quality, - ColorType = colorType - }; - string info = $"{colorType}-Q{quality}"; - - ImageComparer comparer = GetComparer(quality, colorType); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } - - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.L8, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.Ycck)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio422)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio411)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio410)] - public void EncodeBaseline_WorksInNonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType) - where TPixel : unmanaged, IPixel - { - // all reference output images are saved with quality=100 - const int quality = 100; - - using Image image = provider.GetImage(); - - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - - var encoder = new JpegEncoder - { - ColorType = colorType, - Interleaved = false - }; - string info = $"{colorType}-Q{quality}"; - - ImageComparer comparer = GetComparer(quality, colorType); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } - } - } - [Theory] [InlineData(JpegEncodingColor.YCbCrRatio420)] [InlineData(JpegEncodingColor.YCbCrRatio444)] @@ -331,18 +224,19 @@ private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorTy return ImageComparer.Tolerant(tolerance); } - private static void TestJpegEncoderCore( - TestImageProvider provider, - JpegEncodingColor colorType = JpegEncodingColor.YCbCrRatio420, - int quality = 100, - ImageComparer comparer = null) + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType)); + + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance)); + + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, ImageComparer comparer) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); - var encoder = new JpegEncoder { Quality = quality, @@ -350,8 +244,6 @@ private static void TestJpegEncoderCore( }; string info = $"{colorType}-Q{quality}"; - comparer ??= GetComparer(quality, colorType); - // Does DebugSave & load reference CompareToReferenceInput(): image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs deleted file mode 100644 index 08b7714ec0..0000000000 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System; -using System.IO; -using System.Linq; -using System.Numerics; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; -using Xunit.Abstractions; - -// in this file, comments are used for disabling stuff for local execution -#pragma warning disable SA1515 - -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks -{ - public class JpegProfilingBenchmarks : MeasureFixture - { - public JpegProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } - - public static readonly TheoryData DecodeJpegData = new TheoryData - { - { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, - { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, - { TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, 40 }, - // { TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, 10 }, - // { TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, 5 }, - { TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, 5 } - }; - - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [MemberData(nameof(DecodeJpegData))] - public void DecodeJpeg(string fileName, int executionCount) - { - var decoder = new JpegDecoder() - { - IgnoreMetadata = true - }; - this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); - } - - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) - { - // do not run this on CI even by accident - if (TestEnvironment.RunsOnCI) - { - return; - } - - if (!Vector.IsHardwareAccelerated) - { - throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); - } - - string path = TestFile.GetInputFileFullPath(fileName); - byte[] bytes = File.ReadAllBytes(path); - - this.Measure( - executionCount, - () => - { - var img = Image.Load(bytes, decoder); - img.Dispose(); - }, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $"Decode {fileName}"); -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line - } - - [Fact(Skip = ProfilingSetup.SkipProfilingTests)] - public void EncodeJpeg_SingleMidSize() - { - string path = TestFile.GetInputFileFullPath(TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr); - using var image = Image.Load(path); - image.Metadata.ExifProfile = null; - - using var ms = new MemoryStream(); - for (int i = 0; i < 30; i++) - { - image.SaveAsJpeg(ms); - ms.Seek(0, SeekOrigin.Begin); - } - } - - // Benchmark, enable manually! - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(1, 75, JpegEncodingColor.YCbCrRatio420)] - [InlineData(30, 75, JpegEncodingColor.YCbCrRatio420)] - [InlineData(30, 75, JpegEncodingColor.YCbCrRatio444)] - [InlineData(30, 100, JpegEncodingColor.YCbCrRatio444)] - public void EncodeJpeg(int executionCount, int quality, JpegEncodingColor colorType) - { - // do not run this on CI even by accident - if (TestEnvironment.RunsOnCI) - { - return; - } - - string[] testFiles = TestImages.Bmp.Benchmark - .Concat(new[] { TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk }).ToArray(); - - Image[] testImages = testFiles.Select( - tf => TestImageProvider.File(tf, pixelTypeOverride: PixelTypes.Rgba32).GetImage()).ToArray(); - - using (var ms = new MemoryStream()) - { - this.Measure( - executionCount, - () => - { - foreach (Image img in testImages) - { - var options = new JpegEncoder { Quality = quality, ColorType = colorType }; - img.Save(ms, options); - ms.Seek(0, SeekOrigin.Begin); - } - }, -#pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $@"Encode {testFiles.Length} images"); -#pragma warning restore SA1515 // Single-line comment should be preceded by blank line - } - - foreach (Image image in testImages) - { - image.Dispose(); - } - } - } -} From 15e19c2b4f556cc6f712c2af977044bf13a002b6 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 7 Aug 2022 20:23:13 +0300 Subject: [PATCH 39/39] Removed obsolete external images --- ...eline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg | 3 --- ..._WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg | 3 --- ...seline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg | 3 --- ...ksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg | 3 --- ...ksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg | 3 --- ...ksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg | 3 --- ...ksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg | 3 --- ...ksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg | 3 --- 8 files changed, 24 deletions(-) delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg delete mode 100644 tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg deleted file mode 100644 index 5d9f955e6b..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Cmyk-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:acf150308d03dcf9e538eca4f5b1a4895e217c8e7fe7a3bbb7f94a32bc54c794 -size 1600914 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg deleted file mode 100644 index 610b91655d..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Luminance-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe9bcfac7e958c5195000a5c935766690c87a161e2ad87b765c0ba5c4d7a6ce8 -size 425003 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg deleted file mode 100644 index e06a83144b..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_Rgb-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fc1c157e3170aa13d39a7c8429f70c32c200d84122295b346e5ca7b4808e172 -size 757505 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg deleted file mode 100644 index 9d9c930d50..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio410-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8be680ee7a03074c8747dbfffa9175d19120d43ada9406b99110370dd3cda228 -size 491941 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg deleted file mode 100644 index dde66b6f59..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio411-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b594036290443faedb7ec11ec57ab4727d3ddc68fd09b7ac5a0a121691b499a4 -size 542610 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg deleted file mode 100644 index c2389a98ef..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio420-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9df47aa652d31050034e301df65ec327aee9d2ee2ea051a3ef7f4f5915641681 -size 561591 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg deleted file mode 100644 index 6e6b9f4437..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio422-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:06e0b522c1e8bedf5d03f7a0bec00ef474a886def8bbd514c6f4eba6af97880d -size 652375 diff --git a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg b/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg deleted file mode 100644 index 767bd0c50b..0000000000 --- a/tests/Images/External/ReferenceOutput/JpegEncoderTests/EncodeBaseline_WorksWithAllColorTypes_Rgb24_Calliphora_YCbCrRatio444-Q100.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d9bc290239d1739febabeda84191c6fadb206638c7f1cc13240681a0c6596ecb -size 810044