From 96a3c1903f38b872e0b9a8d71290e9e859f02e1d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 6 May 2022 22:30:50 +0300 Subject: [PATCH 01/98] 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 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } + 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.PixelFormats } } + /// + /// 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { - /// internal class SpectralConverter : SpectralConverter where TPixel : unmanaged, IPixel @@ -77,7 +76,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } + 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } + 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - /// - /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private Buffer2D pixelBuffer; + private int alignedPixelWidth; + private Decoder.ColorConverters.JpegColorConverterBase colorConverter; public SpectralConverter(Configuration configuration) => @@ -47,7 +49,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); + var frame = new Components.Encoder.JpegFrame(Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.colorType.Value)); var 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/98] Phase 1: prepare new encoder options API --- .../JpegColorConverter.FromCmykVector.cs | 2 + .../JpegColorConverter.FromYccKVector.cs | 2 + .../Components/Encoder/HuffmanScanEncoder.cs | 7 +- .../Jpeg/Components/Encoder/JpegComponent.cs | 2 +- .../Jpeg/Components/Encoder/JpegFrame.cs | 26 +++---- src/ImageSharp/Formats/Jpeg/JpegColorType.cs | 9 ++- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 76 ++++++++++++++++++- .../Formats/Jpeg/JpegEncoderCore.cs | 20 ++--- 8 files changed, 108 insertions(+), 36 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs index 50228c09a1..00ec1f8689 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -46,7 +46,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs index e90ff9438c..796685278f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -70,7 +70,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters protected override void ConvertCoreInplaceToRgb(in ComponentValues values) => FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + protected override void ConvertCoreVectorizedInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); + protected override void ConvertCoreInplaceFromRgb(in ComponentValues values) => throw new System.NotImplementedException(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index 8d404a3fd2..bc765742e9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; } - public void EncodeScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + public void EncodeInterleavedScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP @@ -199,6 +199,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.FlushRemainingBytes(); } + public void EncodeSingleComponentScan(JpegFrame frame, Image image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + } + /// /// Encodes the image with no subsampling. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs index 1c071c3352..65425c05c1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { private readonly MemoryAllocator memoryAllocator; - public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, byte quantizationTableIndex) + public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) { this.memoryAllocator = memoryAllocator; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 31e59acf8a..18a543dc33 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// internal sealed class JpegFrame : IDisposable { - public JpegFrame(MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) + public JpegFrame(Jpeg.JpegFrameConfig frameConfig, MemoryAllocator allocator, Image image, Decoder.JpegColorSpace colorSpace) { this.ColorSpace = colorSpace; @@ -19,21 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.PixelHeight = image.Height; // int componentCount = 3; - this.Components = new JpegComponent[] + var componentConfigs = frameConfig.Components; + this.Components = new JpegComponent[componentConfigs.Length]; + for (int i = 0; i < this.Components.Length; i++) { - // RGB - new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - - // YCbCr - //new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 }, - //new JpegComponent(allocator, 1, 1, 1) { DcTableId = 2, AcTableId = 3 }, - //new JpegComponent(allocator, 1, 1, 1) { DcTableId = 2, AcTableId = 3 }, - - // Luminance - //new JpegComponent(allocator, 1, 1, 0) { DcTableId = 0, AcTableId = 1 } - }; + var componentConfig = componentConfigs[i]; + this.Components[i] = new JpegComponent(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) + { + DcTableId = componentConfig.dcTableSelector, + AcTableId = componentConfig.acTableSelector, + }; + } } public Decoder.JpegColorSpace ColorSpace { get; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs index c15038c23b..ff4fa013df 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegColorType.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegColorType.cs @@ -41,8 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. - /// - /// Note: Not supported by the encoder. /// YCbCrRatio410 = 4, @@ -58,9 +56,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. - /// - /// Note: Not supported by the encoder. /// Cmyk = 7, + + /// + /// YCCK colorspace. + /// + YccK = 8, } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index fc6e3189fc..7323a30baf 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public JpegColorType? ColorType { get; set; } + public JpegFrameConfig JpegFrameConfig { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -28,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); encoder.Encode(image, stream); } @@ -43,8 +46,77 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var encoder = new JpegEncoderCore(this); + var encoder = new JpegEncoderCore(this, this.JpegFrameConfig); return encoder.EncodeAsync(image, stream, cancellationToken); } } + + public class JpegFrameConfig + { + public JpegFrameConfig(JpegColorType colorType, int precision) + { + this.ColorType = colorType; + this.Precision = precision; + + int componentCount = GetComponentCountFromColorType(colorType); + this.Components = new JpegComponentConfig[componentCount]; + + static int GetComponentCountFromColorType(JpegColorType colorType) + { + switch (colorType) + { + case JpegColorType.Luminance: + return 1; + case JpegColorType.YCbCrRatio444: + case JpegColorType.YCbCrRatio422: + case JpegColorType.YCbCrRatio420: + case JpegColorType.YCbCrRatio411: + case JpegColorType.YCbCrRatio410: + case JpegColorType.Rgb: + return 3; + case JpegColorType.Cmyk: + case JpegColorType.YccK: + return 4; + default: + throw new ArgumentException($"Unknown jpeg color space: {colorType}"); + } + } + } + + public JpegColorType ColorType { get; } + + public int Precision { get; } + + public JpegComponentConfig[] Components { get; } + + public JpegFrameConfig PopulateComponent(int index, int id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) + { + this.Components[index] = new JpegComponentConfig + { + Id = id, + HorizontalSampleFactor = hsf, + VerticalSampleFactor = vsf, + QuantizatioTableIndex = quantIndex, + dcTableSelector = dcIndex, + acTableSelector = acIndex, + }; + + return this; + } + } + + public class JpegComponentConfig + { + public int Id { get; set; } + + public int HorizontalSampleFactor { get; set; } + + public int VerticalSampleFactor { get; set; } + + public int QuantizatioTableIndex { get; set; } + + public int dcTableSelector { get; set; } + + public int acTableSelector { get; set; } + } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 79106856d7..065d9c6019 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -44,6 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private JpegColorType? colorType; + private JpegFrameConfig frameConfig; + /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -53,14 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Initializes a new instance of the class. /// /// The options. - public JpegEncoderCore(IJpegEncoderOptions options) + public JpegEncoderCore(IJpegEncoderOptions options, JpegFrameConfig frameConfig) { this.quality = options.Quality; - if (IsSupportedColorType(options.ColorType)) - { - this.colorType = options.ColorType; - } + this.frameConfig = frameConfig; + this.colorType = frameConfig.ColorType; } /// @@ -87,12 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); - // If the color type was not specified by the user, preserve the color type of the input image. - if (!this.colorType.HasValue) - { - this.colorType = GetFallbackColorType(image); - } - // Compute number of components based on color type in options. int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; ReadOnlySpan componentIds = this.GetComponentIds(); @@ -131,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the scan header. this.WriteStartOfScan(componentCount, componentIds); - var frame = new Components.Encoder.JpegFrame(Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.colorType.Value)); + var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; - new HuffmanScanEncoder(3, stream).EncodeScan(frame, image, quantTables, Configuration.Default, cancellationToken); + new HuffmanScanEncoder(3, stream).EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken); // Write the End Of Image marker. this.WriteEndOfImageMarker(); From fdb8b97f6f0f87964a9d08a4ba19e5038f01385f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 8 May 2022 19:18:50 +0300 Subject: [PATCH 06/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder where TPixel : unmanaged, IPixel { // DEBUG INITIALIZATION SETUP - this.huffmanTables = HuffmanLut.TheHuffmanLut; - frame.Init(1, 1); frame.AllocateComponents(fullScan: false); @@ -161,8 +159,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg || 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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/98] 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.InteropServices; 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.Numerics; 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.InteropServices; 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; 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.Numerics; 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.InteropServices; 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.Numerics; 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 System.Runtime.Intrinsics.X86; 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.CompilerServices; 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; 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.Numerics; 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.Collections.Generic; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// 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; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private int alignedPixelWidth; - private Decoder.ColorConverters.JpegColorConverterBase colorConverter; + private JpegColorConverterBase colorConverter; public SpectralConverter(Configuration configuration) => this.configuration = configuration; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public int? Quality { get; set; } /// - public JpegColorType? ColorType { get; set; } + public JpegEncodingMode? ColorType { get; set; } public JpegFrameConfig JpegFrameConfig { get; set; } @@ -53,28 +53,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Gets or sets the colorspace to use. /// - private JpegColorType? colorType; + private JpegEncodingMode? colorType; private JpegFrameConfig frameConfig; @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // the quality in InitQuantizationTables. if (isGrayscale) { - colorType = JpegColorType.Luminance; + colorType = JpegEncodingMode.Luminance; } return colorType; @@ -190,11 +190,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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] - [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg /// /// 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.target = outputStream; + + this.dcHuffmanTables = new HuffmanLut[4]; + this.acHuffmanTables = new HuffmanLut[4]; } /// @@ -128,6 +135,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { } + 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; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public JpegFrameConfig JpegFrameConfig { get; set; } + public JpegScanConfig JpegScanConfig { get; set; } + /// /// Encodes the image to the specified stream from the . /// @@ -31,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg cancellationToken.ThrowIfCancellationRequested(); + this.scanEncoder = new HuffmanScanEncoder(3, stream); + this.outputStream = stream; ImageMetadata metadata = image.Metadata; JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); @@ -116,14 +124,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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/98] 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 @@ using System.Runtime.Intrinsics; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } + 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 @@ using System.Runtime.CompilerServices; // 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 @@ using System.Runtime.Intrinsics.X86; 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 @@ using SixLabors.ImageSharp.Memory; // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.target = outputStream; - - this.dcHuffmanTables = new HuffmanLut[4]; - this.acHuffmanTables = new HuffmanLut[4]; } /// @@ -135,10 +132,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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.Linq; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Gets or sets the colorspace to use. /// - private JpegEncodingMode? colorType; + private JpegEncodingColor? colorType; private JpegFrameConfig frameConfig; @@ -66,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.quality = options.Quality; this.frameConfig = frameConfig; - this.colorType = frameConfig.ColorType; + this.colorType = frameConfig.EncodingColor; this.scanConfig = scanConfig; } @@ -91,7 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // the quality in InitQuantizationTables. if (isGrayscale) { - colorType = JpegEncodingMode.Luminance; + colorType = JpegEncodingColor.Luminance; } return colorType; @@ -195,11 +195,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } - /// - /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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] - [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg /// /// 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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/98] 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.Numerics; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private int emitWriteIndex; - private Block8x8 tempBlock; - /// /// The output stream. All attempted writes after the first error become no-ops. /// @@ -246,321 +243,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg || 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// 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/98] 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 @@ using System.Runtime.Intrinsics; 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 @@ using System.Runtime.CompilerServices; // 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 @@ using System.Runtime.Intrinsics.X86; 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 @@ using SixLabors.ImageSharp.Memory; // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public int? Quality { get; set; } + /// + /// Sets jpeg color for encoding. + /// public JpegEncodingColor ColorType { set @@ -43,8 +46,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg internal JpegFrameConfig FrameConfig { get; set; } - public JpegScanConfig ScanConfig { get; set; } - /// /// Encodes the image to the specified stream from the . /// @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public JpegComponentConfig[] Components { get; } + public JpegHuffmanTableConfig[] HuffmanTables { get; } + + public JpegQuantizationTableConfig[] QuantizationTables { get; } + public int MaxHorizontalSamplingFactor { get; } public int MaxVerticalSamplingFactor { get; } @@ -201,8 +270,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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/98] 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 @@ namespace SixLabors.ImageSharp 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 @@ namespace SixLabors.ImageSharp 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 @@ namespace SixLabors.ImageSharp 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 @@ namespace SixLabors.ImageSharp 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private int pixelRowCounter; - private IMemoryOwner rgbBuffer; - private Buffer2D pixelBuffer; private int alignedPixelWidth; @@ -57,9 +55,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.PixelFormats 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 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 @@ namespace SixLabors.ImageSharp.PixelFormats 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } } - - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public int PixelWidth { get; private set; } - public int ComponentCount => this.Components.Length; - public JpegComponent[] Components { get; } public int McusPerLine { get; } @@ -70,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Read on. fileMarker = FindNextFileMarker(this.markerBuffer, stream); } + + this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; } /// @@ -1178,6 +1180,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); + this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive; remaining -= length; @@ -1386,7 +1389,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder private Buffer2D pixelBuffer; + private IMemoryOwner redLane; + + private IMemoryOwner greenLane; + + private IMemoryOwner blueLane; + private int alignedPixelWidth; private JpegColorConverterBase colorConverter; @@ -55,6 +61,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } 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/98] 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 @@ namespace SixLabors.ImageSharp } 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 @@ namespace SixLabors.ImageSharp [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 @@ namespace SixLabors.ImageSharp } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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(in ComponentValues values, Span rLane, Span gLane, Span bLane) @@ -35,7 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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); + float luma = (0.299f * r) + (0.587f * g) + (0.114f * b); + c0[i] = luma; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs rename to src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs index f92b283d42..ef51ff2b0f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs @@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayScaleVector : JpegColorConverterVector + internal sealed class GrayScaleVector : JpegColorConverterVector { - public FromGrayScaleVector(int precision) + public GrayScaleVector(int precision) : base(JpegColorSpace.Grayscale, precision) { } @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } protected override void ConvertCoreInplaceToRgb(in ComponentValues values) - => 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components internal static void ConvertCoreInplaceToRgb(ComponentValues values, float maxValue) { - FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue); - FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue); - FromGrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); + GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component0, maxValue); + GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component1, maxValue); + GrayscaleScalar.ConvertCoreInplaceToRgb(values.Component2, maxValue); } internal static void ConvertCoreInplaceFromRgb(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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// 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 @@ using SixLabors.ImageSharp.Memory; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } } + + 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } } + + 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } } - } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { for (int i = 0; i < this.Components.Length; i++) { - this.Components[i]?.Dispose(); + this.Components[i].Dispose(); } } @@ -70,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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; 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.outputStream.Write(tableConfig.Table.Count); this.outputStream.Write(tableConfig.Table.Values); - this.scanEncoder.BuildHuffmanTable(tableConfig); + scanEncoder.BuildHuffmanTable(tableConfig); } } @@ -629,6 +589,40 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { this.ColorType = other.ColorType; - this.luminanceQuality = other.luminanceQuality; - this.chrominanceQuality = other.chrominanceQuality; + this.LuminanceQuality = other.LuminanceQuality; + this.ChrominanceQuality = other.ChrominanceQuality; } /// @@ -47,11 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { 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 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { 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 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg /* 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromYCbCrVector(8), + new JpegColorConverterBase.YCbCrVector(8), 3, FeatureTestRunner.Deserialize(arg)); } @@ -124,13 +121,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromCmykVector(8), + new JpegColorConverterBase.CmykVector(8), 4, FeatureTestRunner.Deserialize(arg)); } @@ -154,13 +151,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromGrayScaleVector(8), + new JpegColorConverterBase.GrayScaleVector(8), 1, FeatureTestRunner.Deserialize(arg)); } @@ -184,13 +181,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg static void RunTest(string arg) => ValidateConversion( - new JpegColorConverterBase.FromRgbVector(8), + new JpegColorConverterBase.RgbVector(8), 3, FeatureTestRunner.Deserialize(arg)); } @@ -214,13 +211,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, 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.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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ref Unsafe.As>(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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components => 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg new JpegQuantizationTableConfig[] { defaultLuminanceQuantTable - }), + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + }, // Cmyk new JpegFrameConfig( @@ -861,7 +864,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -48,10 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -36,7 +37,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 rBase = @@ -42,7 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -70,7 +71,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// public override void ConvertToRgbInplace(in ComponentValues values) { ref Vector256 c0Base = @@ -78,10 +79,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components int simdCount = length - remainder; if (simdCount > 0) { - this.ConvertCoreVectorizedInplaceFromRgb( + this.ConvertFromRgbVectorized( values.Slice(0, simdCount), r.Slice(0, simdCount), g.Slice(0, simdCount), @@ -76,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // remainder pixels if (remainder > 0) { - this.ConvertCoreInplaceFromRgb( + this.ConvertFromRgbScalarRemainder( values.Slice(simdCount, remainder), r.Slice(simdCount, remainder), g.Slice(simdCount, remainder), @@ -84,13 +87,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - protected abstract void ConvertCoreVectorizedInplaceToRgb(in ComponentValues values); + /// + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } /// - 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/98] 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 @@ namespace SixLabors.ImageSharp 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } + /// + /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// 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 @@ Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores | 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/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 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) - float luma = (0.299f * r) + (0.587f * g) + (0.114f * b); + float luma = (0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]); c0[i] = luma; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 8048f3233d..86b15e888e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.Components diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 2bbce6cb1b..f01cab76cb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -2,8 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,6 +17,68 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public partial class JpegEncoderTests { + [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] [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } - [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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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/98] 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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) From 4099205e5997b9922e3b9c8fce1b3c833f7a4068 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 21:11:58 +1000 Subject: [PATCH 33/98] Stub out options types. --- src/ImageSharp/Formats/Bmp/BmpDecoder2.cs | 30 +++++ .../Formats/Bmp/BmpDecoderOptions.cs | 20 +++ src/ImageSharp/Formats/DecoderOptions.cs | 33 +++++ src/ImageSharp/Formats/IImageDecoder2.cs | 37 ++++++ src/ImageSharp/Formats/IImageInfoDetector2.cs | 24 ++++ .../Formats/ISpecializedDecoderOptions.cs | 16 +++ src/ImageSharp/Formats/ImageDecoder{T}.cs | 119 ++++++++++++++++++ 7 files changed, 279 insertions(+) create mode 100644 src/ImageSharp/Formats/Bmp/BmpDecoder2.cs create mode 100644 src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/DecoderOptions.cs create mode 100644 src/ImageSharp/Formats/IImageDecoder2.cs create mode 100644 src/ImageSharp/Formats/IImageInfoDetector2.cs create mode 100644 src/ImageSharp/Formats/ISpecializedDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/ImageDecoder{T}.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs new file mode 100644 index 0000000000..97439182d5 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Image decoder for generating an image out of a Windows bitmap stream. + /// + public class BmpDecoder2 : ImageDecoder + { + /// + public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + + /// + public override IImageInfo IdentifySpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => throw new NotImplementedException(); + } +} diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs new file mode 100644 index 0000000000..74509d68f3 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Image decoder options for decoding Windows bitmap streams. + /// + public sealed class BmpDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + + /// + /// Gets the value indicating how to deal with skipped pixels, + /// which can occur during decoding run length encoded bitmaps. + /// + public RleSkippedPixelHandling RleSkippedPixelHandling { get; } + } +} diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs new file mode 100644 index 0000000000..a690b06075 --- /dev/null +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Provides general configuration options for decoding image formats. + /// + public sealed class DecoderOptions + { + /// + /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. + /// + public Configuration Configuration { get; set; } = Configuration.Default; + + /// + /// Gets or sets the target size to decode the image into. + /// + public Size? TargetSize { get; set; } = null; + + /// + /// Gets or sets a value indicating whether to ignore encoded metadata when decoding. + /// + public bool SkipMetadata { get; set; } = false; + + /// + /// Gets or sets the number of image frames to decode. + /// Leave to decode all frames. + /// + // supported decoders may handle this internally but we will fallback to removeing additional frames after decode. + public int? MaxFrames { get; set; } = null; + } +} diff --git a/src/ImageSharp/Formats/IImageDecoder2.cs b/src/ImageSharp/Formats/IImageDecoder2.cs new file mode 100644 index 0000000000..76d1a754d4 --- /dev/null +++ b/src/ImageSharp/Formats/IImageDecoder2.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Encapsulates properties and methods required for decoding an image from a stream. + /// + public interface IImageDecoder2 + { + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + /// + /// Decodes the image from the specified stream to an . + /// + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/IImageInfoDetector2.cs b/src/ImageSharp/Formats/IImageInfoDetector2.cs new file mode 100644 index 0000000000..3d340f06e8 --- /dev/null +++ b/src/ImageSharp/Formats/IImageInfoDetector2.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Encapsulates methods used for detecting the raw image information without fully decoding it. + /// + public interface IImageInfoDetector2 + { + /// + /// Reads the raw image information from the specified stream. + /// + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The object. + /// Thrown if the encoded image contains errors. + IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs new file mode 100644 index 0000000000..b79938f08d --- /dev/null +++ b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Provides specialized configuration options for decoding image formats. + /// + public interface ISpecializedDecoderOptions + { + /// + /// Gets or sets the general decoder options. + /// + DecoderOptions GeneralOptions { get; set; } + } +} diff --git a/src/ImageSharp/Formats/ImageDecoder{T}.cs b/src/ImageSharp/Formats/ImageDecoder{T}.cs new file mode 100644 index 0000000000..96080b3bed --- /dev/null +++ b/src/ImageSharp/Formats/ImageDecoder{T}.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// The base class for all image decoders. + /// + /// The type of specialized decoder options. + public abstract class ImageDecoder : IImageInfoDetector2, IImageDecoder2 + where T : ISpecializedDecoderOptions, new() + { + /// + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + T specializedOptions = new() { GeneralOptions = options }; + return this.IdentifySpecialized(specializedOptions, stream, cancellationToken); + } + + /// + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + T specializedOptions = new() { GeneralOptions = options }; + Image image = this.DecodeSpecialized(specializedOptions, stream, cancellationToken); + + Resize(options, image); + + return image; + } + + /// + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + T specializedOptions = new() { GeneralOptions = options }; + Image image = this.DecodeSpecialized(specializedOptions, stream, cancellationToken); + + Resize(options, image); + + return image; + } + + /// + /// Reads the raw image information from the specified stream. + /// + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The object. + /// Thrown if the encoded image contains errors. + public abstract IImageInfo IdentifySpecialized(T options, Stream stream, CancellationToken cancellationToken); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + public abstract Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + public abstract Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken); + + /// + /// Performs a resize operation against the decoded image. If the target size is not set, or the image size + /// already matches the target size, the image is untouched. + /// + /// The decoder options. + /// The decoded image. + protected static void Resize(DecoderOptions options, Image image) + { + if (ShouldResize(options, image)) + { + ResizeOptions resizeOptions = new() + { + Size = options.TargetSize.Value, + Sampler = KnownResamplers.Box, + Mode = ResizeMode.Max + }; + + image.Mutate(x => x.Resize(resizeOptions)); + } + } + + /// + /// Determines whether the decoded image should be resized. + /// + /// The decoder options. + /// The decoded image. + /// if the image should be resized, otherwise; . + private static bool ShouldResize(DecoderOptions options, Image image) + { + if (options.TargetSize is null) + { + return false; + } + + Size targetSize = options.TargetSize.Value; + Size currentSize = image.Size(); + return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; + } + } +} From dfe04e36e3349e2cef9e6548d889c732e9d817ce Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 22:12:13 +1000 Subject: [PATCH 34/98] Refactor Bmp and Gif --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 40 ++++------ src/ImageSharp/Formats/Bmp/BmpDecoder2.cs | 30 -------- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 41 +++++------ .../Formats/Bmp/BmpDecoderOptions.cs | 2 +- .../Formats/Bmp/IBmpDecoderOptions.cs | 16 ---- src/ImageSharp/Formats/DecoderOptions.cs | 6 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 35 ++++----- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 73 ++++++++++--------- .../Formats/Gif/GifDecoderOptions.cs | 14 ++++ ...ernals.cs => IImageDecoderInternals{T}.cs} | 10 ++- .../Formats/ImageDecoderUtilities.cs | 17 +++-- 11 files changed, 119 insertions(+), 165 deletions(-) delete mode 100644 src/ImageSharp/Formats/Bmp/BmpDecoder2.cs delete mode 100644 src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Gif/GifDecoderOptions.cs rename src/ImageSharp/Formats/{IImageDecoderInternals.cs => IImageDecoderInternals{T}.cs} (87%) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index e764489388..326ba0b740 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -10,43 +10,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Image decoder for generating an image out of a Windows bitmap stream. /// - /// - /// Does not support the following formats at the moment: - /// - /// JPG - /// PNG - /// Some OS/2 specific subtypes like: Bitmap Array, Color Icon, Color Pointer, Icon, Pointer. - /// - /// Formats will be supported in a later releases. We advise always - /// to use only 24 Bit Windows bitmaps. - /// - public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDetector + public class BmpDecoder : ImageDecoder { - /// - /// Gets or sets a value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps. - /// - public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black; - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new BmpDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + BmpDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); + + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + /// + public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); + return new BmpDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs deleted file mode 100644 index 97439182d5..0000000000 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder2.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; -using System.Threading; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Bmp -{ - /// - /// Image decoder for generating an image out of a Windows bitmap stream. - /// - public class BmpDecoder2 : ImageDecoder - { - /// - public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - /// - public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => throw new NotImplementedException(); - } -} diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 26687ff16f..f00d4cd03b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// A useful decoding source example can be found at /// - internal sealed class BmpDecoderCore : IImageDecoderInternals + internal sealed class BmpDecoderCore : IImageDecoderInternals { /// /// The default mask for the red part of the color for 16 bit rgb bitmaps. @@ -90,33 +90,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp private BmpInfoHeader infoHeader; /// - /// Used for allocating memory during processing operations. + /// The global configuration. /// - private readonly MemoryAllocator memoryAllocator; + private readonly Configuration configuration; /// - /// The bitmap decoder options. + /// Used for allocating memory during processing operations. /// - private readonly IBmpDecoderOptions options; + private readonly MemoryAllocator memoryAllocator; /// /// Initializes a new instance of the class. /// - /// The configuration. /// The options. - public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) + public BmpDecoderCore(BmpDecoderOptions options) { - this.Configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + this.Options = options; } /// - public Configuration Configuration { get; } + public BmpDecoderOptions Options { get; } - /// - /// Gets the dimensions of the image. - /// + /// public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); /// @@ -128,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); + image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -325,7 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp byte colorIdx = bufferRow[x]; if (undefinedPixelsSpan[rowStartIdx + x]) { - switch (this.options.RleSkippedPixelHandling) + switch (this.Options.RleSkippedPixelHandling) { case RleSkippedPixelHandling.FirstColorOfPalette: color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); @@ -397,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int idx = rowStartIdx + (x * 3); if (undefinedPixelsSpan[yMulWidth + x]) { - switch (this.options.RleSkippedPixelHandling) + switch (this.Options.RleSkippedPixelHandling) { case RleSkippedPixelHandling.FirstColorOfPalette: color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); @@ -943,7 +940,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( - this.Configuration, + this.configuration, rowSpan, pixelSpan, width); @@ -971,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, pixelSpan, width); @@ -1006,7 +1003,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(rowSpan); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, bgraRowSpan, width); @@ -1042,7 +1039,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, pixelSpan, width); @@ -1056,7 +1053,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(rowSpan); PixelOperations.Instance.FromBgra32Bytes( - this.Configuration, + this.configuration, rowSpan, bgraRowSpan, width); diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index 74509d68f3..535f819d27 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Image decoder options for decoding Windows bitmap streams. + /// Configuration options for decoding Windows Bitmap images. /// public sealed class BmpDecoderOptions : ISpecializedDecoderOptions { diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs deleted file mode 100644 index ff88d15a31..0000000000 --- a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Bmp -{ - /// - /// Image decoder options for decoding Windows bitmap streams. - /// - internal interface IBmpDecoderOptions - { - /// - /// Gets the value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps. - /// - RleSkippedPixelHandling RleSkippedPixelHandling { get; } - } -} diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index a690b06075..6b35e2614d 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -24,10 +24,8 @@ namespace SixLabors.ImageSharp.Formats public bool SkipMetadata { get; set; } = false; /// - /// Gets or sets the number of image frames to decode. - /// Leave to decode all frames. + /// Gets or sets the maximum number of image frames to decode, inclusive. /// - // supported decoders may handle this internally but we will fallback to removeing additional frames after decode. - public int? MaxFrames { get; set; } = null; + public int MaxFrames { get; set; } = int.MaxValue; } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 6d6cfc0792..5550c419e0 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif @@ -11,37 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Decoder for generating an image out of a gif encoded stream. /// - public sealed class GifDecoder : IImageDecoder, IGifDecoderOptions, IImageInfoDetector + public sealed class GifDecoder : ImageDecoder { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } = false; - - /// - /// Gets or sets the decoding mode for multi-frame images - /// - public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + GifDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); + + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + /// + public override Image DecodeSpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new GifDecoderCore(configuration, this); - return decoder.Identify(configuration, stream, cancellationToken); + return new GifDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 2932cafe24..c63a3e08ec 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Performs the gif decoding operation. /// - internal sealed class GifDecoderCore : IImageDecoderInternals + internal sealed class GifDecoderCore : IImageDecoderInternals { /// /// The temp buffer used to reduce allocations. @@ -56,6 +56,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// private GifImageDescriptor imageDescriptor; + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The maximum number of frames to decode. + /// + private readonly int maxFrames; + + /// + /// Whether to skip metadata during decode. + /// + private readonly bool skipMetadata; + /// /// The abstract metadata. /// @@ -69,39 +89,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// - /// The configuration. /// The decoder options. - public GifDecoderCore(Configuration configuration, IGifDecoderOptions options) + public GifDecoderCore(GifDecoderOptions options) { - this.IgnoreMetadata = options.IgnoreMetadata; - this.DecodingMode = options.DecodingMode; - this.Configuration = configuration ?? Configuration.Default; + this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + this.maxFrames = options.GeneralOptions.MaxFrames; } /// - public Configuration Configuration { get; } - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; internal set; } + public GifDecoderOptions Options { get; } - /// - /// Gets the decoding mode for multi-frame images. - /// - public FrameDecodingMode DecodingMode { get; } - - /// - /// Gets the dimensions of the image. - /// + /// public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height); - private MemoryAllocator MemoryAllocator => this.Configuration.MemoryAllocator; - /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { + int frameCount = 0; Image image = null; ImageFrame previousFrame = null; try @@ -114,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (nextFlag == GifConstants.ImageLabel) { - if (previousFrame != null && this.DecodingMode == FrameDecodingMode.First) + if (previousFrame != null && frameCount++ <= this.maxFrames) { break; } @@ -277,9 +284,9 @@ namespace SixLabors.ImageSharp.Formats.Gif this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); - if (isXmp && !this.IgnoreMetadata) + if (isXmp && !this.skipMetadata) { - var extension = GifXmpApplicationExtension.Read(this.stream, this.MemoryAllocator); + var extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator); if (extension.Data.Length > 0) { this.metadata.XmpProfile = new XmpProfile(extension.Data); @@ -346,13 +353,13 @@ namespace SixLabors.ImageSharp.Formats.Gif GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); } - if (this.IgnoreMetadata) + if (this.skipMetadata) { this.stream.Seek(length, SeekOrigin.Current); continue; } - using IMemoryOwner commentsBuffer = this.MemoryAllocator.Allocate(length); + using IMemoryOwner commentsBuffer = this.memoryAllocator.Allocate(length); Span commentsSpan = commentsBuffer.GetSpan(); this.stream.Read(commentsSpan); @@ -385,11 +392,11 @@ namespace SixLabors.ImageSharp.Formats.Gif if (this.imageDescriptor.LocalColorTableFlag) { int length = this.imageDescriptor.LocalColorTableSize * 3; - localColorTable = this.Configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + localColorTable = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); this.stream.Read(localColorTable.GetSpan()); } - indices = this.Configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); + indices = this.configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); this.ReadFrameIndices(indices); Span rawColorTable = default; @@ -423,7 +430,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void ReadFrameIndices(Buffer2D indices) { int minCodeSize = this.stream.ReadByte(); - using var lzwDecoder = new LzwDecoder(this.Configuration.MemoryAllocator, this.stream); + using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream); lzwDecoder.DecodePixels(minCodeSize, indices); } @@ -451,12 +458,12 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (!transFlag) { - image = new Image(this.Configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); + image = new Image(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); } else { // This initializes the image to become fully transparent because the alpha channel is zero. - image = new Image(this.Configuration, imageWidth, imageHeight, this.metadata); + image = new Image(this.configuration, imageWidth, imageHeight, this.metadata); } this.SetFrameMetadata(image.Frames.RootFrame.Metadata); @@ -675,7 +682,7 @@ namespace SixLabors.ImageSharp.Formats.Gif if (globalColorTableLength > 0) { - this.globalColorTable = this.MemoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); + this.globalColorTable = this.memoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); // Read the global color table data from the stream stream.Read(this.globalColorTable.GetSpan()); diff --git a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs new file mode 100644 index 0000000000..429c1fee1c --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Configuration options for decoding Gif images. + /// + public sealed class GifDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals{T}.cs similarity index 87% rename from src/ImageSharp/Formats/IImageDecoderInternals.cs rename to src/ImageSharp/Formats/IImageDecoderInternals{T}.cs index e190f7adda..ffc839d34d 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals.cs +++ b/src/ImageSharp/Formats/IImageDecoderInternals{T}.cs @@ -9,14 +9,16 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats { /// - /// Abstraction for shared internals for ***DecoderCore implementations to be used with . + /// Abstraction for shared internals for XXXDecoderCore implementations to be used with . /// - internal interface IImageDecoderInternals + /// The type of specialized decoder options. + internal interface IImageDecoderInternals + where T : ISpecializedDecoderOptions { /// - /// Gets the associated configuration. + /// Gets the specialized decoder options. /// - Configuration Configuration { get; } + T Options { get; } /// /// Gets the dimensions of the image being decoded. diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 71ecda8938..fc78f14b31 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -12,11 +12,12 @@ namespace SixLabors.ImageSharp.Formats { internal static class ImageDecoderUtilities { - public static IImageInfo Identify( - this IImageDecoderInternals decoder, + public static IImageInfo Identify( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, CancellationToken cancellationToken) + where T : ISpecializedDecoderOptions { using var bufferedReadStream = new BufferedReadStream(configuration, stream); @@ -30,20 +31,22 @@ namespace SixLabors.ImageSharp.Formats } } - public static Image Decode( - this IImageDecoderInternals decoder, + public static Image Decode( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, CancellationToken cancellationToken) + where T : ISpecializedDecoderOptions where TPixel : unmanaged, IPixel - => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); - public static Image Decode( - this IImageDecoderInternals decoder, + public static Image Decode( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, Func largeImageExceptionFactory, CancellationToken cancellationToken) + where T : ISpecializedDecoderOptions where TPixel : unmanaged, IPixel { using var bufferedReadStream = new BufferedReadStream(configuration, stream); From d4539222509b646d624df00e88ac3de00681a229 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 22:32:33 +1000 Subject: [PATCH 35/98] Port Pbm decoder --- .../Formats/Gif/IGifDecoderOptions.cs | 23 ---- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 24 ++-- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 105 ++++++++++-------- .../Formats/Pbm/PbmDecoderOptions.cs | 14 +++ 4 files changed, 83 insertions(+), 83 deletions(-) delete mode 100644 src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs deleted file mode 100644 index 56bb6d6519..0000000000 --- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Gif -{ - /// - /// Decoder for generating an image out of a gif encoded stream. - /// - internal interface IGifDecoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - - /// - /// Gets the decoding mode for multi-frame images. - /// - FrameDecodingMode DecodingMode { get; } - } -} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 97a9cb7d75..90752a6374 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -26,29 +26,29 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// The specification of these images is found at . /// - public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector + public sealed class PbmDecoder : ImageDecoder { - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + public override Image DecodeSpecialized(PbmDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - Guard.NotNull(stream, nameof(stream)); + PbmDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); - var decoder = new PbmDecoderCore(configuration); - return decoder.Decode(configuration, stream, cancellationToken); + return image; } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + public override Image DecodeSpecialized(PbmDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(PbmDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new PbmDecoderCore(configuration); - return decoder.Identify(configuration, stream, cancellationToken); + return new PbmDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 749fc0292b..568c1ab4c9 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -14,48 +14,55 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// Performs the PBM decoding operation. /// - internal sealed class PbmDecoderCore : IImageDecoderInternals + internal sealed class PbmDecoderCore : IImageDecoderInternals { private int maxPixelValue; /// - /// Initializes a new instance of the class. + /// The general configuration. /// - /// The configuration. - public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default; + private readonly Configuration configuration; - /// - public Configuration Configuration { get; } + /// + /// The colortype to use + /// + private PbmColorType colorType; /// - /// Gets the colortype to use + /// The size of the pixel array /// - public PbmColorType ColorType { get; private set; } + private Size pixelSize; /// - /// Gets the size of the pixel array + /// The component data type /// - public Size PixelSize { get; private set; } + private PbmComponentType componentType; /// - /// Gets the component data type + /// The Encoding of pixels /// - public PbmComponentType ComponentType { get; private set; } + private PbmEncoding encoding; /// - /// Gets the Encoding of pixels + /// The decoded by this decoder instance. /// - public PbmEncoding Encoding { get; private set; } + private ImageMetadata metadata; /// - /// Gets the decoded by this decoder instance. + /// Initializes a new instance of the class. /// - public ImageMetadata Metadata { get; private set; } + /// The decoder options. + public PbmDecoderCore(PbmDecoderOptions options) + { + this.Options = options; + this.configuration = options.GeneralOptions.Configuration; + } /// - Size IImageDecoderInternals.Dimensions => this.PixelSize; + public PbmDecoderOptions Options { get; } - private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; + /// + public Size Dimensions => this.pixelSize; /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -63,12 +70,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm { this.ProcessHeader(stream); - var image = new Image(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata); + var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); this.ProcessPixels(stream, pixels); - if (this.NeedsUpscaling) + if (this.NeedsUpscaling()) { this.ProcessUpscaling(image); } @@ -82,8 +89,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm this.ProcessHeader(stream); // BlackAndWhite pixels are encoded into a byte. - int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8; - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); + int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8; + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata); } /// @@ -104,33 +111,33 @@ namespace SixLabors.ImageSharp.Formats.Pbm { case '1': // Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). - this.ColorType = PbmColorType.BlackAndWhite; - this.Encoding = PbmEncoding.Plain; + this.colorType = PbmColorType.BlackAndWhite; + this.encoding = PbmEncoding.Plain; break; case '2': // Plain PGM format: 1 component per pixel, in decimal text. - this.ColorType = PbmColorType.Grayscale; - this.Encoding = PbmEncoding.Plain; + this.colorType = PbmColorType.Grayscale; + this.encoding = PbmEncoding.Plain; break; case '3': // Plain PPM format: 3 components per pixel, in decimal text. - this.ColorType = PbmColorType.Rgb; - this.Encoding = PbmEncoding.Plain; + this.colorType = PbmColorType.Rgb; + this.encoding = PbmEncoding.Plain; break; case '4': // Binary PBM format: 1 component per pixel, 8 pixels per byte. - this.ColorType = PbmColorType.BlackAndWhite; - this.Encoding = PbmEncoding.Binary; + this.colorType = PbmColorType.BlackAndWhite; + this.encoding = PbmEncoding.Binary; break; case '5': // Binary PGM format: 1 components per pixel, in binary integers. - this.ColorType = PbmColorType.Grayscale; - this.Encoding = PbmEncoding.Binary; + this.colorType = PbmColorType.Grayscale; + this.encoding = PbmEncoding.Binary; break; case '6': // Binary PPM format: 3 components per pixel, in binary integers. - this.ColorType = PbmColorType.Rgb; - this.Encoding = PbmEncoding.Binary; + this.colorType = PbmColorType.Rgb; + this.encoding = PbmEncoding.Binary; break; case '7': // PAM image: sequence of images. @@ -144,52 +151,54 @@ namespace SixLabors.ImageSharp.Formats.Pbm stream.SkipWhitespaceAndComments(); int height = stream.ReadDecimal(); stream.SkipWhitespaceAndComments(); - if (this.ColorType != PbmColorType.BlackAndWhite) + if (this.colorType != PbmColorType.BlackAndWhite) { this.maxPixelValue = stream.ReadDecimal(); if (this.maxPixelValue > 255) { - this.ComponentType = PbmComponentType.Short; + this.componentType = PbmComponentType.Short; } else { - this.ComponentType = PbmComponentType.Byte; + this.componentType = PbmComponentType.Byte; } stream.SkipWhitespaceAndComments(); } else { - this.ComponentType = PbmComponentType.Bit; + this.componentType = PbmComponentType.Bit; } - this.PixelSize = new Size(width, height); - this.Metadata = new ImageMetadata(); - PbmMetadata meta = this.Metadata.GetPbmMetadata(); - meta.Encoding = this.Encoding; - meta.ColorType = this.ColorType; - meta.ComponentType = this.ComponentType; + this.pixelSize = new Size(width, height); + this.metadata = new ImageMetadata(); + PbmMetadata meta = this.metadata.GetPbmMetadata(); + meta.Encoding = this.encoding; + meta.ColorType = this.colorType; + meta.ComponentType = this.componentType; } private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { - if (this.Encoding == PbmEncoding.Binary) + if (this.encoding == PbmEncoding.Binary) { - BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); + BinaryDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); } else { - PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); + PlainDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); } } private void ProcessUpscaling(Image image) where TPixel : unmanaged, IPixel { - int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255; + int maxAllocationValue = this.componentType == PbmComponentType.Short ? 65535 : 255; float factor = maxAllocationValue / this.maxPixelValue; image.Mutate(x => x.Brightness(factor)); } + + private bool NeedsUpscaling() => this.colorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; } } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs new file mode 100644 index 0000000000..c0b8856504 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Configuration options for decoding Pbm images. + /// + public sealed class PbmDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} From c9beb02c0e8ef29ad5bdd99bd366283be61888da Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 23:06:29 +1000 Subject: [PATCH 36/98] Port Tga and Png --- .../Formats/Png/IPngDecoderOptions.cs | 16 ----- src/ImageSharp/Formats/Png/PngDecoder.cs | 59 ++++++++++--------- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 57 +++++++++--------- .../Formats/Png/PngDecoderOptions.cs | 14 +++++ .../Formats/Tga/ITgaDecoderOptions.cs | 12 ---- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 23 ++++---- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 43 +++++++------- .../Formats/Tga/TgaDecoderOptions.cs | 14 +++++ 8 files changed, 119 insertions(+), 119 deletions(-) delete mode 100644 src/ImageSharp/Formats/Png/IPngDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Png/PngDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs diff --git a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs b/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs deleted file mode 100644 index 4b09c5b1ca..0000000000 --- a/src/ImageSharp/Formats/Png/IPngDecoderOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Png -{ - /// - /// The options for decoding png images - /// - internal interface IPngDecoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - } -} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 0b233848ad..a48b281c5a 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -10,24 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Decoder for generating an image out of a png encoded stream. /// - public sealed class PngDecoder : IImageDecoder, IPngDecoderOptions, IImageInfoDetector + public sealed class PngDecoder : ImageDecoder { /// - public bool IgnoreMetadata { get; set; } - - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - PngDecoderCore decoder = new(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + PngDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); + + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + /// + public override Image DecodeSpecialized(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - PngDecoderCore decoder = new(configuration, true); - IImageInfo info = decoder.Identify(configuration, stream, cancellationToken); + PngDecoderCore decoder = new(options, true); + IImageInfo info = decoder.Identify(options.GeneralOptions.Configuration, stream, cancellationToken); stream.Position = 0; PngMetadata meta = info.Metadata.GetPngMetadata(); @@ -39,49 +39,50 @@ namespace SixLabors.ImageSharp.Formats.Png if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(configuration, stream, cancellationToken) - : this.Decode(configuration, stream, cancellationToken); + ? this.DecodeSpecialized(options, stream, cancellationToken) + : this.DecodeSpecialized(options, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(configuration, stream, cancellationToken) - : this.Decode(configuration, stream, cancellationToken); + ? this.DecodeSpecialized(options, stream, cancellationToken) + : this.DecodeSpecialized(options, stream, cancellationToken); case PngColorType.Rgb: if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(configuration, stream, cancellationToken) - : this.Decode(configuration, stream, cancellationToken); + ? this.DecodeSpecialized(options, stream, cancellationToken) + : this.DecodeSpecialized(options, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(configuration, stream, cancellationToken) - : this.Decode(configuration, stream, cancellationToken); + ? this.DecodeSpecialized(options, stream, cancellationToken) + : this.DecodeSpecialized(options, stream, cancellationToken); case PngColorType.Palette: - return this.Decode(configuration, stream, cancellationToken); + return this.DecodeSpecialized(options, stream, cancellationToken); case PngColorType.GrayscaleWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream, cancellationToken) - : this.Decode(configuration, stream, cancellationToken); + ? this.DecodeSpecialized(options, stream, cancellationToken) + : this.DecodeSpecialized(options, stream, cancellationToken); case PngColorType.RgbWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream, cancellationToken) - : this.Decode(configuration, stream, cancellationToken); + ? this.DecodeSpecialized(options, stream, cancellationToken) + : this.DecodeSpecialized(options, stream, cancellationToken); default: - return this.Decode(configuration, stream, cancellationToken); + return this.DecodeSpecialized(options, stream, cancellationToken); } } /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - PngDecoderCore decoder = new(configuration, this); - return decoder.Identify(configuration, stream, cancellationToken); + Guard.NotNull(stream, nameof(stream)); + + return new PngDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index f46b5058a1..ae01a2f568 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Runtime.CompilerServices; @@ -28,17 +27,22 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Performs the png decoding operation. /// - internal sealed class PngDecoderCore : IImageDecoderInternals + internal sealed class PngDecoderCore : IImageDecoderInternals { /// /// Reusable buffer. /// private readonly byte[] buffer = new byte[4]; + /// + /// The general decoder options. + /// + private readonly Configuration configuration; + /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - private readonly bool ignoreMetadata; + private readonly bool skipMetadata; /// /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only. @@ -118,29 +122,26 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Initializes a new instance of the class. /// - /// The configuration. /// The decoder options. - public PngDecoderCore(Configuration configuration, IPngDecoderOptions options) + public PngDecoderCore(PngDecoderOptions options) { - this.Configuration = configuration ?? Configuration.Default; - this.memoryAllocator = this.Configuration.MemoryAllocator; - this.ignoreMetadata = options.IgnoreMetadata; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + this.skipMetadata = options.GeneralOptions.SkipMetadata; } - internal PngDecoderCore(Configuration configuration, bool colorMetadataOnly) + internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) { - this.Configuration = configuration ?? Configuration.Default; - this.memoryAllocator = this.Configuration.MemoryAllocator; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; this.colorMetadataOnly = colorMetadataOnly; - this.ignoreMetadata = true; + this.skipMetadata = true; } /// - public Configuration Configuration { get; } + public PngDecoderOptions Options { get; } - /// - /// Gets the dimensions of the image. - /// + /// public Size Dimensions => new(this.header.Width, this.header.Height); /// @@ -199,7 +200,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: - if (!this.ignoreMetadata) + if (!this.skipMetadata) { byte[] exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); @@ -336,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; } - if (!this.ignoreMetadata) + if (!this.skipMetadata) { byte[] exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); @@ -469,7 +470,7 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : unmanaged, IPixel { image = Image.CreateUninitialized( - this.Configuration, + this.configuration, this.header.Width, this.header.Height, metadata); @@ -485,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.previousScanline?.Dispose(); this.scanline?.Dispose(); this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.Configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); } /// @@ -798,7 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.Rgb: PngScanlineProcessor.ProcessRgbScanline( - this.Configuration, + this.configuration, this.header, scanlineSpan, rowSpan, @@ -812,7 +813,7 @@ namespace SixLabors.ImageSharp.Formats.Png case PngColorType.RgbWithAlpha: PngScanlineProcessor.ProcessRgbaScanline( - this.Configuration, + this.configuration, this.header, scanlineSpan, rowSpan, @@ -1001,7 +1002,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing the data. private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) { - if (this.ignoreMetadata) + if (this.skipMetadata) { return; } @@ -1036,7 +1037,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing the data. private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) { - if (this.ignoreMetadata) + if (this.skipMetadata) { return; } @@ -1222,10 +1223,10 @@ namespace SixLabors.ImageSharp.Formats.Png { fixed (byte* compressedDataBase = compressedData) { - using (IMemoryOwner destBuffer = this.memoryAllocator.Allocate(this.Configuration.StreamProcessingBufferSize)) + using (IMemoryOwner destBuffer = this.memoryAllocator.Allocate(this.configuration.StreamProcessingBufferSize)) using (var memoryStreamOutput = new MemoryStream(compressedData.Length)) using (var memoryStreamInput = new UnmanagedMemoryStream(compressedDataBase, compressedData.Length)) - using (var bufferedStream = new BufferedReadStream(this.Configuration, memoryStreamInput)) + using (var bufferedStream = new BufferedReadStream(this.configuration, memoryStreamInput)) using (var inflateStream = new ZlibInflateStream(bufferedStream)) { Span destUncompressedData = destBuffer.GetSpan(); @@ -1324,7 +1325,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing the data. private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan data) { - if (this.ignoreMetadata) + if (this.skipMetadata) { return; } @@ -1563,7 +1564,7 @@ namespace SixLabors.ImageSharp.Formats.Png private IMemoryOwner ReadChunkData(int length) { // We rent the buffer here to return it afterwards in Decode() - IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); this.currentStream.Read(buffer.GetSpan(), 0, length); diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs new file mode 100644 index 0000000000..f8b3364bb5 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Configuration options for decoding Png images. + /// + public class PngDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} diff --git a/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs deleted file mode 100644 index 240b8b9b3a..0000000000 --- a/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Tga -{ - /// - /// The options for decoding tga images. Currently empty, but this may change in the future. - /// - internal interface ITgaDecoderOptions - { - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index bb0a0d5489..60616f9cd3 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -10,28 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Image decoder for Truevision TGA images. /// - public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector + public sealed class TgaDecoder : ImageDecoder { /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(TgaDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - Guard.NotNull(stream, nameof(stream)); + TgaDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); - var decoder = new TgaDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + /// + public override Image DecodeSpecialized(TgaDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(TgaDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); + return new TgaDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index d101ccd94a..421c0224a7 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -15,13 +15,18 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Performs the tga decoding operation. /// - internal sealed class TgaDecoderCore : IImageDecoderInternals + internal sealed class TgaDecoderCore : IImageDecoderInternals { /// /// A scratch buffer to reduce allocations. /// private readonly byte[] scratchBuffer = new byte[4]; + /// + /// General configuration options. + /// + private readonly Configuration configuration; + /// /// The metadata. /// @@ -47,11 +52,6 @@ namespace SixLabors.ImageSharp.Formats.Tga /// private BufferedReadStream currentStream; - /// - /// The bitmap decoder options. - /// - private readonly ITgaDecoderOptions options; - /// /// Indicates whether there is a alpha channel present. /// @@ -60,22 +60,19 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Initializes a new instance of the class. /// - /// The configuration. /// The options. - public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options) + public TgaDecoderCore(TgaDecoderOptions options) { - this.Configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.options = options; + this.Options = options; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; } /// - public Configuration Configuration { get; } + public TgaDecoderOptions Options { get; } - /// - /// Gets the dimensions of the image. - /// - public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); + /// + public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -87,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Tga this.currentStream.Skip(this.fileHeader.IdLength); // Parse the color map, if present. - if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1) + if (this.fileHeader.ColorMapType is not 0 and not 1) { TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); } @@ -97,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tga throw new UnknownImageFormatException("Width or height cannot be 0"); } - var image = Image.CreateUninitialized(this.Configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); + var image = Image.CreateUninitialized(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (this.fileHeader.ColorMapType == 1) @@ -451,11 +448,11 @@ namespace SixLabors.ImageSharp.Formats.Tga if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { - PixelOperations.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width); + PixelOperations.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); } else { - PixelOperations.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width); + PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); } } } @@ -655,7 +652,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width); + PixelOperations.Instance.FromL8Bytes(this.configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -682,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width); + PixelOperations.Instance.FromBgr24Bytes(this.configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -701,7 +698,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width); + PixelOperations.Instance.FromBgra32Bytes(this.configuration, row, pixelSpan, width); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs new file mode 100644 index 0000000000..abce31c666 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + /// + /// Configuration options for decoding Png images. + /// + public class TgaDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} From 3917b123d1c1d63b5366262a6abde676282a3623 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 23:23:37 +1000 Subject: [PATCH 37/98] Port Tiff, fix constructors --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 7 +-- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 8 +-- .../Formats/Tga/TgaDecoderOptions.cs | 2 +- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 33 ++++-------- .../Formats/Tiff/TiffDecoderCore.cs | 50 ++++++++++--------- .../Formats/Tiff/TiffDecoderOptions.cs | 14 ++++++ 7 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index f00d4cd03b..a9f779bfa6 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -105,9 +105,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The options. public BmpDecoderCore(BmpDecoderOptions options) { + this.Options = options; this.configuration = options.GeneralOptions.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; - this.Options = options; } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index c63a3e08ec..c084f57cea 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private readonly MemoryAllocator memoryAllocator; /// - /// The maximum number of frames to decode. + /// The maximum number of frames to decode. Inclusive. /// private readonly int maxFrames; @@ -92,10 +92,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The decoder options. public GifDecoderCore(GifDecoderOptions options) { - this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.Options = options; this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; + this.skipMetadata = options.GeneralOptions.SkipMetadata; this.maxFrames = options.GeneralOptions.MaxFrames; + this.memoryAllocator = this.configuration.MemoryAllocator; } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index ae01a2f568..383a3ffeb3 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -125,17 +125,19 @@ namespace SixLabors.ImageSharp.Formats.Png /// The decoder options. public PngDecoderCore(PngDecoderOptions options) { + this.Options = options; this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.memoryAllocator = this.configuration.MemoryAllocator; } internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) { - this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; + this.Options = options; this.colorMetadataOnly = colorMetadataOnly; this.skipMetadata = true; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; } /// diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs index abce31c666..f5c0fb128e 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { /// - /// Configuration options for decoding Png images. + /// Configuration options for decoding Tga images. /// public class TgaDecoderOptions : ISpecializedDecoderOptions { diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index c4a9f3b225..e503c1af11 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -3,7 +3,6 @@ using System.IO; using System.Threading; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -11,39 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Image decoder for generating an image out of a TIFF stream. /// - public class TiffDecoder : IImageDecoder, ITiffDecoderOptions, IImageInfoDetector + public class TiffDecoder : ImageDecoder { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } - - /// - /// Gets or sets the decoding mode for multi-frame images. - /// - public FrameDecodingMode DecodingMode { get; set; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(TiffDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - Guard.NotNull(stream, nameof(stream)); + TiffDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); - var decoder = new TiffDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + return image; } /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + public override Image DecodeSpecialized(TiffDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(TiffDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - var decoder = new TiffDecoderCore(configuration, this); - return decoder.Identify(configuration, stream, cancellationToken); + return new TiffDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index c595246a5b..0bb01fcdce 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -20,8 +20,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Performs the tiff decoding operation. /// - internal class TiffDecoderCore : IImageDecoderInternals + internal class TiffDecoderCore : IImageDecoderInternals { + /// + /// General configuration options. + /// + private readonly Configuration configuration; + /// /// Used for allocating memory during processing operations. /// @@ -30,12 +35,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. /// - private readonly bool ignoreMetadata; + private readonly bool skipMetadata; /// - /// Gets the decoding mode for multi-frame images + /// The maximum number of frames to decode. Inclusive. /// - private readonly FrameDecodingMode decodingMode; + private readonly int maxFrames; /// /// The stream to decode from. @@ -55,16 +60,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Initializes a new instance of the class. /// - /// The configuration. /// The decoder options. - public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options) + public TiffDecoderCore(TiffDecoderOptions options) { - options ??= new TiffDecoder(); - - this.Configuration = configuration ?? Configuration.Default; - this.ignoreMetadata = options.IgnoreMetadata; - this.decodingMode = options.DecodingMode; - this.memoryAllocator = this.Configuration.MemoryAllocator; + this.Options = options; + this.configuration = options.GeneralOptions.Configuration; + this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.maxFrames = options.GeneralOptions.MaxFrames; + this.memoryAllocator = this.configuration.MemoryAllocator; } /// @@ -148,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff public TiffPredictor Predictor { get; set; } /// - public Configuration Configuration { get; } + public TiffDecoderOptions Options { get; } /// public Size Dimensions { get; private set; } @@ -161,25 +164,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff try { this.inputStream = stream; - var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); + var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); IEnumerable directories = reader.Read(); this.byteOrder = reader.ByteOrder; this.isBigTiff = reader.IsBigTiff; + int frameCount = 0; foreach (ExifProfile ifd in directories) { cancellationToken.ThrowIfCancellationRequested(); ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); frames.Add(frame); - if (this.decodingMode is FrameDecodingMode.First) + if (frameCount++ <= this.maxFrames) { break; } } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); // TODO: Tiff frames can have different sizes. ImageFrame root = frames[0]; @@ -192,7 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } - return new Image(this.Configuration, metadata, frames); + return new Image(this.configuration, metadata, frames); } catch { @@ -209,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.inputStream = stream; - var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); + var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); IEnumerable directories = reader.Read(); ExifProfile rootFrameExifProfile = directories.First(); @@ -233,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff where TPixel : unmanaged, IPixel { var imageFrameMetaData = new ImageFrameMetadata(); - if (!this.ignoreMetadata) + if (!this.skipMetadata) { imageFrameMetaData.ExifProfile = tags; } @@ -245,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff int width = GetImageWidth(tags); int height = GetImageHeight(tags); - var frame = new ImageFrame(this.Configuration, width, height, imageFrameMetaData); + var frame = new ImageFrame(this.configuration, width, height, imageFrameMetaData); int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; @@ -369,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Configuration, + this.configuration, this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, @@ -449,7 +453,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff Buffer2D pixels = frame.PixelBuffer; using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Configuration, + this.configuration, this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, @@ -463,7 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.byteOrder); TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( - this.Configuration, + this.configuration, this.memoryAllocator, this.ColorType, this.BitsPerSample, diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs new file mode 100644 index 0000000000..ac01952510 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// Configuration options for decoding Tiff images. + /// + public class TiffDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} From 48a81062c3437f87f7966da03bf238f1ba8f5de9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 23:48:50 +1000 Subject: [PATCH 38/98] Port WebP and cleanup options --- src/ImageSharp/Formats/DecoderOptions.cs | 6 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 6 +- .../Formats/Png/PngDecoderOptions.cs | 2 +- .../Formats/Tga/TgaDecoderOptions.cs | 2 +- .../Formats/Tiff/ITiffDecoderOptions.cs | 23 ---- .../Formats/Tiff/TiffDecoderCore.cs | 8 +- .../Formats/Tiff/TiffDecoderOptions.cs | 2 +- .../Formats/Webp/IWebpDecoderOptions.cs | 23 ---- .../Formats/Webp/WebpAnimationDecoder.cs | 39 ++++--- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 44 ++----- .../Formats/Webp/WebpDecoderCore.cs | 110 +++++++++--------- .../Formats/Webp/WebpDecoderOptions.cs | 14 +++ 12 files changed, 116 insertions(+), 163 deletions(-) delete mode 100644 src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 6b35e2614d..6ed66db92c 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Formats { /// @@ -8,6 +10,8 @@ namespace SixLabors.ImageSharp.Formats /// public sealed class DecoderOptions { + private uint maxFrames = uint.MaxValue; + /// /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. /// @@ -26,6 +30,6 @@ namespace SixLabors.ImageSharp.Formats /// /// Gets or sets the maximum number of image frames to decode, inclusive. /// - public int MaxFrames { get; set; } = int.MaxValue; + public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Min(Math.Max(value, 1), uint.MaxValue); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index c084f57cea..938a8f3603 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The maximum number of frames to decode. Inclusive. /// - private readonly int maxFrames; + private readonly uint maxFrames; /// /// Whether to skip metadata during decode. @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Gif public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - int frameCount = 0; + uint frameCount = 0; Image image = null; ImageFrame previousFrame = null; try @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (nextFlag == GifConstants.ImageLabel) { - if (previousFrame != null && frameCount++ <= this.maxFrames) + if (previousFrame != null && frameCount++ > this.maxFrames) { break; } diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs index f8b3364bb5..9619bbe7f7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Configuration options for decoding Png images. /// - public class PngDecoderOptions : ISpecializedDecoderOptions + public sealed class PngDecoderOptions : ISpecializedDecoderOptions { /// public DecoderOptions GeneralOptions { get; set; } = new(); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs index f5c0fb128e..7483618c47 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Configuration options for decoding Tga images. /// - public class TgaDecoderOptions : ISpecializedDecoderOptions + public sealed class TgaDecoderOptions : ISpecializedDecoderOptions { /// public DecoderOptions GeneralOptions { get; set; } = new(); diff --git a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs deleted file mode 100644 index d6d1bb8a4c..0000000000 --- a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - /// - /// Encapsulates the options for the . - /// - internal interface ITiffDecoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - - /// - /// Gets the decoding mode for multi-frame images. - /// - FrameDecodingMode DecodingMode { get; } - } -} diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 0bb01fcdce..1f644fa82f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -33,14 +33,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff private readonly MemoryAllocator memoryAllocator; /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// A value indicating whether the metadata should be ignored when the image is being decoded. /// private readonly bool skipMetadata; /// /// The maximum number of frames to decode. Inclusive. /// - private readonly int maxFrames; + private readonly uint maxFrames; /// /// The stream to decode from. @@ -170,14 +170,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.byteOrder = reader.ByteOrder; this.isBigTiff = reader.IsBigTiff; - int frameCount = 0; + uint frameCount = 0; foreach (ExifProfile ifd in directories) { cancellationToken.ThrowIfCancellationRequested(); ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); frames.Add(frame); - if (frameCount++ <= this.maxFrames) + if (frameCount++ > this.maxFrames) { break; } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs index ac01952510..ec3b437971 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Configuration options for decoding Tiff images. /// - public class TiffDecoderOptions : ISpecializedDecoderOptions + public sealed class TiffDecoderOptions : ISpecializedDecoderOptions { /// public DecoderOptions GeneralOptions { get; set; } = new(); diff --git a/src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs deleted file mode 100644 index cf607ef69f..0000000000 --- a/src/ImageSharp/Formats/Webp/IWebpDecoderOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Metadata; - -namespace SixLabors.ImageSharp.Formats.Webp -{ - /// - /// Image decoder options for generating an image out of a webp stream. - /// - internal interface IWebpDecoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - - /// - /// Gets the decoding mode for multi-frame images. - /// - FrameDecodingMode DecodingMode { get; } - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 09653fd4cd..8e69673118 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -33,6 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private readonly Configuration configuration; + /// + /// The maximum number of frames to decode. Inclusive. + /// + private readonly uint maxFrames; + /// /// The area to restore. /// @@ -48,29 +53,24 @@ namespace SixLabors.ImageSharp.Formats.Webp /// private WebpMetadata webpMetadata; + /// + /// The alpha data, if an ALPH chunk is present. + /// + private IMemoryOwner alphaData; + /// /// Initializes a new instance of the class. /// /// The memory allocator. /// The global configuration. - /// The frame decoding mode. - public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, FrameDecodingMode decodingMode) + /// The maximum number of frames to decode. Inclusive. + public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; - this.DecodingMode = decodingMode; + this.maxFrames = maxFrames; } - /// - /// Gets or sets the alpha data, if an ALPH chunk is present. - /// - public IMemoryOwner AlphaData { get; set; } - - /// - /// Gets the decoding mode for multi-frame images. - /// - public FrameDecodingMode DecodingMode { get; } - /// /// Decodes the animated webp image from the specified stream. /// @@ -90,6 +90,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.webpMetadata = this.metadata.GetWebpMetadata(); this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; + uint frameCount = 0; int remainingBytes = (int)completeDataSize; while (remainingBytes > 0) { @@ -110,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp break; } - if (stream.Position == stream.Length || this.DecodingMode is FrameDecodingMode.First) + if (stream.Position == stream.Length || frameCount++ > this.maxFrames) { break; } @@ -224,14 +225,14 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The stream to read from. private byte ReadAlphaData(BufferedReadStream stream) { - this.AlphaData?.Dispose(); + this.alphaData?.Dispose(); uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); int alphaDataSize = (int)(alphaChunkSize - 1); - this.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); + this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); byte alphaChunkHeader = (byte)stream.ReadByte(); - Span alphaData = this.AlphaData.GetSpan(); + Span alphaData = this.alphaData.GetSpan(); stream.Read(alphaData, 0, alphaDataSize); return alphaChunkHeader; @@ -260,7 +261,7 @@ namespace SixLabors.ImageSharp.Formats.Webp else { var lossyDecoder = new WebpLossyDecoder(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); - lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.AlphaData); + lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); } return pixelBufferDecoded; @@ -381,6 +382,6 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public void Dispose() => this.AlphaData?.Dispose(); + public void Dispose() => this.alphaData?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index 2f6b593eef..64544c5e69 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -3,8 +3,6 @@ using System.IO; using System.Threading; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp @@ -12,49 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Image decoder for generating an image out of a webp stream. /// - public sealed class WebpDecoder : IImageDecoder, IWebpDecoderOptions, IImageInfoDetector + public sealed class WebpDecoder : ImageDecoder { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } - - /// - /// Gets or sets the decoding mode for multi-frame images. - /// Defaults to All. - /// - public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All; - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override Image DecodeSpecialized(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - Guard.NotNull(stream, nameof(stream)); + WebpDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - using var decoder = new WebpDecoderCore(configuration, this); + Resize(options.GeneralOptions, image); - try - { - return decoder.Decode(configuration, stream, cancellationToken); - } - catch (InvalidMemoryOperationException ex) - { - Size dims = decoder.Dimensions; - - throw new InvalidImageContentException($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); - } + return image; } - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.Decode(configuration, stream, cancellationToken); + /// + public override Image DecodeSpecialized(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - return new WebpDecoderCore(configuration, this).Identify(configuration, stream, cancellationToken); + return new WebpDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 979ac55825..70ca6900b9 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Threading; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; @@ -21,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Performs the webp decoding operation. /// - internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable + internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { /// /// Reusable buffer. @@ -29,65 +28,68 @@ namespace SixLabors.ImageSharp.Formats.Webp private readonly byte[] buffer = new byte[4]; /// - /// Used for allocating memory during the decoding operations. + /// General configuration options. /// - private readonly MemoryAllocator memoryAllocator; + private readonly Configuration configuration; /// - /// The stream to decode from. + /// A value indicating whether the metadata should be ignored when the image is being decoded. /// - private BufferedReadStream currentStream; + private readonly bool skipMetadata; /// - /// The webp specific metadata. + /// The maximum number of frames to decode. Inclusive. /// - private WebpMetadata webpMetadata; + private readonly uint maxFrames; /// - /// Information about the webp image. + /// Gets the decoded by this decoder instance. /// - private WebpImageInfo webImageInfo; + private ImageMetadata metadata; /// - /// Initializes a new instance of the class. + /// Gets or sets the alpha data, if an ALPH chunk is present. /// - /// The configuration. - /// The options. - public WebpDecoderCore(Configuration configuration, IWebpDecoderOptions options) - { - this.Configuration = configuration; - this.DecodingMode = options.DecodingMode; - this.memoryAllocator = configuration.MemoryAllocator; - this.IgnoreMetadata = options.IgnoreMetadata; - } - - /// - public Configuration Configuration { get; } + private IMemoryOwner alphaData; /// - /// Gets the decoding mode for multi-frame images. + /// Used for allocating memory during the decoding operations. /// - public FrameDecodingMode DecodingMode { get; } + private readonly MemoryAllocator memoryAllocator; /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// The stream to decode from. /// - public bool IgnoreMetadata { get; } + private BufferedReadStream currentStream; /// - /// Gets the decoded by this decoder instance. + /// The webp specific metadata. /// - public ImageMetadata Metadata { get; private set; } + private WebpMetadata webpMetadata; /// - /// Gets the dimensions of the image. + /// Information about the webp image. /// - public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + private WebpImageInfo webImageInfo; /// - /// Gets or sets the alpha data, if an ALPH chunk is present. + /// Initializes a new instance of the class. /// - public IMemoryOwner AlphaData { get; set; } + /// The decoder options. + public WebpDecoderCore(WebpDecoderOptions options) + { + this.Options = options; + this.configuration = options.GeneralOptions.Configuration; + this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.maxFrames = options.GeneralOptions.MaxFrames; + this.memoryAllocator = this.configuration.MemoryAllocator; + } + + /// + public WebpDecoderOptions Options { get; } + + /// + public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -96,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Webp Image image = null; try { - this.Metadata = new ImageMetadata(); + this.metadata = new ImageMetadata(); this.currentStream = stream; uint fileSize = this.ReadImageHeader(); @@ -105,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { if (this.webImageInfo.Features is { Animation: true }) { - using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.Configuration, this.DecodingMode); + using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames); return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); } @@ -114,17 +116,17 @@ namespace SixLabors.ImageSharp.Formats.Webp WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); } - image = new Image(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); + image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (this.webImageInfo.IsLossless) { - var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); + var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); - lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData); + var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); } // There can be optional chunks after the image data, like EXIF and XMP. @@ -151,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Webp this.ReadImageHeader(); using (this.webImageInfo = this.ReadVp8Info(true)) { - return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); + return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); } } @@ -182,8 +184,8 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Information about the webp image. private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) { - this.Metadata = new ImageMetadata(); - this.webpMetadata = this.Metadata.GetFormatMetadata(WebpFormat.Instance); + this.metadata = new ImageMetadata(); + this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance); WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); @@ -277,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The webp features. private void ParseOptionalChunks(WebpFeatures features) { - if (this.IgnoreMetadata || (features.ExifProfile == false && features.XmpMetaData == false)) + if (this.skipMetadata || (features.ExifProfile == false && features.XmpMetaData == false)) { return; } @@ -287,11 +289,11 @@ namespace SixLabors.ImageSharp.Formats.Webp { // Read chunk header. WebpChunkType chunkType = this.ReadChunkType(); - if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null) + if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null) { this.ReadExifProfile(); } - else if (chunkType == WebpChunkType.Xmp && this.Metadata.XmpProfile == null) + else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null) { this.ReadXmpProfile(); } @@ -310,7 +312,7 @@ namespace SixLabors.ImageSharp.Formats.Webp private void ReadExifProfile() { uint exifChunkSize = this.ReadChunkSize(); - if (this.IgnoreMetadata) + if (this.skipMetadata) { this.currentStream.Skip((int)exifChunkSize); } @@ -325,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } var profile = new ExifProfile(exifData); - this.Metadata.ExifProfile = profile; + this.metadata.ExifProfile = profile; } } @@ -335,7 +337,7 @@ namespace SixLabors.ImageSharp.Formats.Webp private void ReadXmpProfile() { uint xmpChunkSize = this.ReadChunkSize(); - if (this.IgnoreMetadata) + if (this.skipMetadata) { this.currentStream.Skip((int)xmpChunkSize); } @@ -350,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } var profile = new XmpProfile(xmpData); - this.Metadata.XmpProfile = profile; + this.metadata.XmpProfile = profile; } } @@ -360,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Webp private void ReadIccProfile() { uint iccpChunkSize = this.ReadChunkSize(); - if (this.IgnoreMetadata) + if (this.skipMetadata) { this.currentStream.Skip((int)iccpChunkSize); } @@ -376,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Webp var profile = new IccProfile(iccpData); if (profile.CheckIsValid()) { - this.Metadata.IccProfile = profile; + this.metadata.IccProfile = profile; } } } @@ -419,8 +421,8 @@ namespace SixLabors.ImageSharp.Formats.Webp features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); int alphaDataSize = (int)(alphaChunkSize - 1); - this.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); - Span alphaData = this.AlphaData.GetSpan(); + this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); + Span alphaData = this.alphaData.GetSpan(); int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize); if (bytesRead != alphaDataSize) { @@ -462,6 +464,6 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public void Dispose() => this.AlphaData?.Dispose(); + public void Dispose() => this.alphaData?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs new file mode 100644 index 0000000000..e574b0c72f --- /dev/null +++ b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Webp +{ + /// + /// Configuration options for decoding Webp images. + /// + public sealed class WebpDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} From 22421dbfcb9fbf7165dd8f461ff96f2fb97b74ab Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 29 Jun 2022 23:54:07 +1000 Subject: [PATCH 39/98] Limit to collection max --- src/ImageSharp/Formats/DecoderOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 6ed66db92c..95b02a0ae2 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats /// public sealed class DecoderOptions { - private uint maxFrames = uint.MaxValue; + private uint maxFrames = int.MaxValue; /// /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. @@ -30,6 +30,6 @@ namespace SixLabors.ImageSharp.Formats /// /// Gets or sets the maximum number of image frames to decode, inclusive. /// - public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Min(Math.Max(value, 1), uint.MaxValue); } + public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Min(Math.Max(value, 1), int.MaxValue); } } } From c9b5e77430045cc5471dac58677f46309850bfdb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Mon, 4 Jul 2022 09:05:09 +0300 Subject: [PATCH 40/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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), 7, 5, 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)] From 4b28eb6bdc6fa9088c968c5b1ca3a0ba9857f7c5 Mon Sep 17 00:00:00 2001 From: Yu Date: Wed, 13 Jul 2022 16:29:29 +0800 Subject: [PATCH 41/98] Fix rle compression mistake in tga encoder. --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 43 +++++++++----------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 1a1260a58e..c19495da72 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -7,7 +7,6 @@ using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -73,7 +72,7 @@ namespace SixLabors.ImageSharp.Formats.Tga this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); - this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel; + this.bitsPerPixel ??= tgaMetadata.BitsPerPixel; TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) @@ -160,6 +159,8 @@ namespace SixLabors.ImageSharp.Formats.Tga case TgaBitsPerPixel.Pixel32: this.Write32Bit(stream, pixels); break; + default: + break; } } @@ -213,6 +214,8 @@ namespace SixLabors.ImageSharp.Formats.Tga stream.WriteByte(color.R); stream.WriteByte(color.A); break; + default: + break; } encodedPixels += equalPixelCount + 1; @@ -225,35 +228,29 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The pixel type. /// The pixels of the image. /// X coordinate to start searching for the same pixels. - /// Y coordinate to start searching for the same pixels. + /// Y coordinate to searching for the same pixels in only one scan line. /// The number of equal pixels. - private byte FindEqualPixels(Buffer2D pixels, int xStart, int yStart) + private byte FindEqualPixels(Buffer2D pixels, int xStart, int yPos) where TPixel : unmanaged, IPixel { byte equalPixelCount = 0; - bool firstRow = true; - TPixel startPixel = pixels[xStart, yStart]; - for (int y = yStart; y < pixels.Height; y++) + TPixel startPixel = pixels[xStart, yPos]; + for (int x = xStart + 1; x < pixels.Width; x++) { - for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++) + TPixel nextPixel = pixels[x, yPos]; + if (startPixel.Equals(nextPixel)) + { + equalPixelCount++; + } + else { - TPixel nextPixel = pixels[x, y]; - if (startPixel.Equals(nextPixel)) - { - equalPixelCount++; - } - else - { - return equalPixelCount; - } - - if (equalPixelCount >= 127) - { - return equalPixelCount; - } + return equalPixelCount; } - firstRow = false; + if (equalPixelCount >= 127) + { + return equalPixelCount; + } } return equalPixelCount; From d53ad0efa04fcdc96cba11611ce0acff2d73cbd1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 Jul 2022 19:39:42 +1000 Subject: [PATCH 42/98] Refactor jpeg decoder and general load --- src/ImageSharp/Advanced/AotCompilerTools.cs | 24 +- .../Formats/Bmp/BmpDecoderOptions.cs | 2 +- src/ImageSharp/Formats/DecoderOptions.cs | 9 +- .../Formats/Gif/GifDecoderOptions.cs | 2 +- src/ImageSharp/Formats/IImageDecoder.cs | 12 +- src/ImageSharp/Formats/IImageDecoder2.cs | 37 -- src/ImageSharp/Formats/IImageInfoDetector.cs | 7 +- src/ImageSharp/Formats/IImageInfoDetector2.cs | 24 - .../Formats/ISpecializedDecoderOptions.cs | 2 +- src/ImageSharp/Formats/ImageDecoder{T}.cs | 4 +- .../Formats/Jpeg/IJpegDecoderOptions.cs | 16 - src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 78 +--- .../Formats/Jpeg/JpegDecoderCore.cs | 90 ++-- .../Formats/Jpeg/JpegDecoderOptions.cs | 14 + .../Formats/Pbm/PbmDecoderOptions.cs | 2 +- .../Formats/Png/PngDecoderOptions.cs | 2 +- .../Formats/Tga/TgaDecoderOptions.cs | 2 +- .../Decompressors/JpegTiffCompression.cs | 20 +- .../Compression/TiffDecompressorsFactory.cs | 4 +- .../Formats/Tiff/TiffDecoderCore.cs | 4 +- .../Formats/Tiff/TiffDecoderOptions.cs | 2 +- .../Formats/Webp/WebpDecoderOptions.cs | 2 +- src/ImageSharp/Image.Decode.cs | 38 +- src/ImageSharp/Image.FromBytes.cs | 430 ++---------------- src/ImageSharp/Image.FromFile.cs | 295 +++--------- src/ImageSharp/Image.FromStream.cs | 333 ++++---------- src/ImageSharp/Image.LoadPixelData.cs | 68 +-- .../LoadResizeSaveStressRunner.cs | 35 +- 28 files changed, 350 insertions(+), 1208 deletions(-) delete mode 100644 src/ImageSharp/Formats/IImageDecoder2.cs delete mode 100644 src/ImageSharp/Formats/IImageInfoDetector2.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 7db2b7f963..5e8b899a4d 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.Advanced img.CloneAs(default); img.CloneAs(default); - ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); + ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); } @@ -210,21 +210,21 @@ namespace SixLabors.ImageSharp.Advanced } /// - /// This method pre-seeds the all in the AoT compiler. + /// This method pre-seeds the all in the AoT compiler. /// /// The pixel format. [Preserve] private static void AotCompileImageDecoderInternals() where TPixel : unmanaged, IPixel { - default(WebpDecoderCore).Decode(default, default, default); - default(BmpDecoderCore).Decode(default, default, default); - default(GifDecoderCore).Decode(default, default, default); - default(JpegDecoderCore).Decode(default, default, default); - default(PbmDecoderCore).Decode(default, default, default); - default(PngDecoderCore).Decode(default, default, default); - default(TgaDecoderCore).Decode(default, default, default); - default(TiffDecoderCore).Decode(default, default, default); + default(WebpDecoderCore).Decode(default, default); + default(BmpDecoderCore).Decode(default, default); + default(GifDecoderCore).Decode(default, default); + default(JpegDecoderCore).Decode(default, default); + default(PbmDecoderCore).Decode(default, default); + default(PngDecoderCore).Decode(default, default); + default(TgaDecoderCore).Decode(default, default); + default(TiffDecoderCore).Decode(default, default); } /// @@ -286,9 +286,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileImageDecoder() where TPixel : unmanaged, IPixel where TDecoder : class, IImageDecoder - { - default(TDecoder).Decode(default, default, default); - } + => default(TDecoder).Decode(default, default, default); /// /// This method pre-seeds the all in the AoT compiler. diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index 535f819d27..b01d179802 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.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.Bmp { diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 95b02a0ae2..5e03efb6e9 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.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; @@ -10,8 +10,15 @@ namespace SixLabors.ImageSharp.Formats /// public sealed class DecoderOptions { + private static readonly Lazy LazyOptions = new(() => new()); + private uint maxFrames = int.MaxValue; + /// + /// Gets the shared default general decoder options instance. + /// + public static DecoderOptions Default { get; } = LazyOptions.Value; + /// /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs index 429c1fee1c..3c5f7bddc1 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderOptions.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.Gif { diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 82c7804d57..cdec1e9289 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -16,22 +16,22 @@ namespace SixLabors.ImageSharp.Formats /// Decodes the image from the specified stream to an of a specific pixel type. /// /// The pixel format. - /// The configuration for the image. + /// The general decoder options. /// The containing image data. /// The token to monitor for cancellation requests. /// The . - // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) + /// Thrown if the encoded image contains errors. + Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// /// Decodes the image from the specified stream to an . /// - /// The configuration for the image. + /// The general decoder options. /// The containing image data. /// The token to monitor for cancellation requests. /// The . - // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) - Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken); + /// Thrown if the encoded image contains errors. + Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageDecoder2.cs b/src/ImageSharp/Formats/IImageDecoder2.cs deleted file mode 100644 index 76d1a754d4..0000000000 --- a/src/ImageSharp/Formats/IImageDecoder2.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats -{ - /// - /// Encapsulates properties and methods required for decoding an image from a stream. - /// - public interface IImageDecoder2 - { - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an . - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 2d30af5ceb..443e8b9809 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -14,10 +14,11 @@ namespace SixLabors.ImageSharp.Formats /// /// Reads the raw image information from the specified stream. /// - /// The configuration for the image. + /// The general decoder options. /// The containing image data. /// The token to monitor for cancellation requests. - /// The object - IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken); + /// The object. + /// Thrown if the encoded image contains errors. + IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageInfoDetector2.cs b/src/ImageSharp/Formats/IImageInfoDetector2.cs deleted file mode 100644 index 3d340f06e8..0000000000 --- a/src/ImageSharp/Formats/IImageInfoDetector2.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Threading; - -namespace SixLabors.ImageSharp.Formats -{ - /// - /// Encapsulates methods used for detecting the raw image information without fully decoding it. - /// - public interface IImageInfoDetector2 - { - /// - /// Reads the raw image information from the specified stream. - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object. - /// Thrown if the encoded image contains errors. - IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs index b79938f08d..eacb901838 100644 --- a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs +++ b/src/ImageSharp/Formats/ISpecializedDecoderOptions.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 { diff --git a/src/ImageSharp/Formats/ImageDecoder{T}.cs b/src/ImageSharp/Formats/ImageDecoder{T}.cs index 96080b3bed..3a75595790 100644 --- a/src/ImageSharp/Formats/ImageDecoder{T}.cs +++ b/src/ImageSharp/Formats/ImageDecoder{T}.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.IO; using System.Threading; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats /// The base class for all image decoders. /// /// The type of specialized decoder options. - public abstract class ImageDecoder : IImageInfoDetector2, IImageDecoder2 + public abstract class ImageDecoder : IImageInfoDetector, IImageDecoder where T : ISpecializedDecoderOptions, new() { /// diff --git a/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs deleted file mode 100644 index 76874b7ffd..0000000000 --- a/src/ImageSharp/Formats/Jpeg/IJpegDecoderOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg -{ - /// - /// Image decoder for generating an image out of a jpg stream. - /// - internal interface IJpegDecoderOptions - { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 7f23d1ac85..1675fd6d74 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -3,77 +3,45 @@ using System.IO; using System.Threading; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg { /// - /// Image decoder for generating an image out of a jpg stream. + /// Decoder for generating an image out of a jpeg encoded stream. /// - public sealed class JpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector + public sealed class JpegDecoder : ImageDecoder { /// - public bool IgnoreMetadata { get; set; } - - /// - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + /// Unlike , when + /// is passed, the codec may not be able to scale efficiently to + /// the exact scale factor requested, so returns a size that approximates that scale. + /// Upscaling is not supported, so the original size will be returned. + /// + public override Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - Guard.NotNull(stream, nameof(stream)); - - using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Decode(configuration, stream, cancellationToken); + using JpegDecoderCore decoder = new(options); + return decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); } - /// - 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. - /// - /// The pixel format. - /// Configuration. - /// Stream. - /// Target size. - /// Cancellation token. - internal Image DecodeInto(Configuration configuration, Stream stream, Size targetSize, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(stream, nameof(stream)); - - using var decoder = new JpegDecoderCore(configuration, this); - using var bufferedReadStream = new BufferedReadStream(configuration, stream); - try - { - return decoder.DecodeInto(bufferedReadStream, targetSize, cancellationToken); - } - catch (InvalidMemoryOperationException ex) - { - throw new InvalidImageContentException(((IImageDecoderInternals)decoder).Dimensions, ex); - } - } + /// + /// + /// Unlike , when + /// is passed, the codec may not be able to scale efficiently to + /// the exact scale factor requested, so returns a size that approximates that scale. + /// Upscaling is not supported, so the original size will be returned. + /// + public override Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); /// - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - using var decoder = new JpegDecoderCore(configuration, this); - return decoder.Identify(configuration, stream, cancellationToken); + using JpegDecoderCore decoder = new(options); + return decoder.Identify(options.GeneralOptions.Configuration, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index d5997e4128..4be75f2a15 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Originally ported from /// with additional fixes for both performance and common encoding errors. /// - internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals + internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals { /// /// The only supported precision @@ -116,31 +116,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private int? resetInterval; /// - /// Initializes a new instance of the class. + /// The global configuration. /// - /// The configuration. - /// The options. - public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) - { - this.Configuration = configuration ?? Configuration.Default; - this.IgnoreMetadata = options.IgnoreMetadata; - } + private readonly Configuration configuration; - /// - public Configuration Configuration { get; } + /// + /// Whether to skip metadata during decode. + /// + private readonly bool skipMetadata; /// - /// Gets the frame + /// Initializes a new instance of the class. /// - public JpegFrame Frame { get; private set; } + /// The decoder options. + public JpegDecoderCore(JpegDecoderOptions options) + { + this.Options = options; + this.configuration = options.GeneralOptions.Configuration; + this.skipMetadata = options.GeneralOptions.SkipMetadata; + } + + /// + public JpegDecoderOptions Options { get; } /// - Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize; + public Size Dimensions => this.Frame.PixelSize; /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. + /// Gets the frame /// - public bool IgnoreMetadata { get; } + public JpegFrame Frame { get; private set; } /// /// Gets the decoded by this decoder instance. @@ -201,48 +206,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel - => this.Decode(stream, targetSize: null, cancellationToken); - - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.ParseStream(stream, spectralConverter: null, cancellationToken); + using var spectralConverter = new SpectralConverter(this.configuration, this.Options.GeneralOptions.TargetSize); + this.ParseStream(stream, spectralConverter, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); this.InitXmpProfile(); this.InitDerivedMetadataProperties(); - Size pixelSize = this.Frame.PixelSize; - return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); + return new Image( + this.configuration, + spectralConverter.GetPixelBuffer(cancellationToken), + this.Metadata); } - /// - /// Decodes and downscales the image from the specified stream if possible. - /// - /// The pixel format. - /// Stream. - /// Target size. - /// Cancellation token. - internal Image DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => this.Decode(stream, targetSize, cancellationToken); - - private Image Decode(BufferedReadStream stream, Size? targetSize, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - using var spectralConverter = new SpectralConverter(this.Configuration, targetSize); - this.ParseStream(stream, spectralConverter, cancellationToken); + this.ParseStream(stream, spectralConverter: null, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); this.InitXmpProfile(); this.InitDerivedMetadataProperties(); - return new Image( - this.Configuration, - spectralConverter.GetPixelBuffer(cancellationToken), - this.Metadata); + Size pixelSize = this.Frame.PixelSize; + return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); } /// @@ -262,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } using var ms = new MemoryStream(tableBytes); - using var stream = new BufferedReadStream(this.Configuration, ms); + using var stream = new BufferedReadStream(this.configuration, ms); // Check for the Start Of Image marker. int bytesRead = stream.Read(this.markerBuffer, 0, 2); @@ -809,7 +799,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { const int ExifMarkerLength = 6; const int XmpMarkerLength = 29; - if (remaining < ExifMarkerLength || this.IgnoreMetadata) + if (remaining < ExifMarkerLength || this.skipMetadata) { // Skip the application header length. stream.Skip(remaining); @@ -847,7 +837,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) { const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; - if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata) + if (remaining < remainingXmpMarkerBytes || this.skipMetadata) { // Skip the application header length. stream.Skip(remaining); @@ -889,7 +879,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { // Length is 14 though we only need to check 12. const int Icclength = 14; - if (remaining < Icclength || this.IgnoreMetadata) + if (remaining < Icclength || this.skipMetadata) { stream.Skip(remaining); return; @@ -930,7 +920,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessApp13Marker(BufferedReadStream stream, int remaining) { - if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata) + if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata) { stream.Skip(remaining); return; @@ -1310,8 +1300,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } IJpegComponent component = decodingComponentType is ComponentType.Huffman ? - new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) : - new ArithmeticDecodingComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); + new JpegComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) : + new ArithmeticDecodingComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); this.Frame.Components[i] = (JpegComponent)component; this.Frame.ComponentIds[i] = componentId; @@ -1348,7 +1338,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } int length = remaining; - using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize)) + using (IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(totalBufferSize)) { Span bufferSpan = buffer.GetSpan(); Span huffmanLengthsSpan = bufferSpan.Slice(0, codeLengthsByteSize); diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs new file mode 100644 index 0000000000..80e680cd0e --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Configuration options for decoding Jpeg images. + /// + public sealed class JpegDecoderOptions : ISpecializedDecoderOptions + { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs index c0b8856504..1253d72b39 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.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.Pbm { diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs index 9619bbe7f7..722d803254 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderOptions.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.Png { diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs index 7483618c47..89ec90430d 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.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.Tga { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index d0543917a0..50698d64dc 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// internal sealed class JpegTiffCompression : TiffBaseDecompressor { - private readonly Configuration configuration; + private readonly JpegDecoderOptions options; private readonly byte[] jpegTables; @@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// Initializes a new instance of the class. /// - /// The configuration. + /// The specialized jpeg decoder options. /// The memoryAllocator to use for buffer allocations. /// The image width. /// The bits per pixel. /// The JPEG tables containing the quantization and/or Huffman tables. /// The photometric interpretation. public JpegTiffCompression( - Configuration configuration, + JpegDecoderOptions options, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors TiffPhotometricInterpretation photometricInterpretation) : base(memoryAllocator, width, bitsPerPixel) { - this.configuration = configuration; + this.options = options; this.jpegTables = jpegTables; this.photometricInterpretation = photometricInterpretation; } @@ -52,14 +52,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { if (this.jpegTables != null) { - using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder()); - + using var jpegDecoder = new JpegDecoderCore(this.options); + Configuration configuration = this.options.GeneralOptions.Configuration; switch (this.photometricInterpretation) { case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.WhiteIsZero: { - using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration); + using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(configuration); var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None); @@ -73,8 +73,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors case TiffPhotometricInterpretation.YCbCr: case TiffPhotometricInterpretation.Rgb: { - using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? - new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration); + using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr + ? new RgbJpegSpectralConverter(this.options.GeneralOptions.Configuration) + : new SpectralConverter(this.options.GeneralOptions.Configuration); + var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None); diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 5d6793660a..086db4b0a1 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression internal static class TiffDecompressorsFactory { public static TiffBaseDecompressor Create( - Configuration configuration, + DecoderOptions options, TiffDecoderCompressionType method, MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression case TiffDecoderCompressionType.Jpeg: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation); + return new JpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation); default: throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 06a950e73c..cec1997367 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -373,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.configuration, + this.Options.GeneralOptions, this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, @@ -453,7 +453,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff Buffer2D pixels = frame.PixelBuffer; using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.configuration, + this.Options.GeneralOptions, this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs index ec3b437971..b22cf001e1 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.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.Tiff { diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs index e574b0c72f..8e8218472f 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.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.Webp { diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 3fcd44d3a8..efa337d6d0 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -44,14 +44,14 @@ namespace SixLabors.ImageSharp /// /// By reading the header on the provided stream this calculates the images format. /// + /// The general configuration. /// The image stream to read the header from. - /// The configuration. /// The mime type or null if none found. - private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) + private static IImageFormat InternalDetectFormat(Configuration configuration, Stream stream) { // We take a minimum of the stream length vs the max header size and always check below // to ensure that only formats that headers fit within the given buffer length are tested. - int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length); + int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length); if (headerSize <= 0) { return null; @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp // and does that data match the format specification? // Individual formats should still check since they are public. IImageFormat format = null; - foreach (IImageFormatDetector formatDetector in config.ImageFormatsManager.FormatDetectors) + foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) { if (formatDetector.HeaderSize <= headerSize) { @@ -98,73 +98,73 @@ namespace SixLabors.ImageSharp /// /// By reading the header on the provided stream this calculates the images format. /// + /// The general decoder options. /// The image stream to read the header from. - /// The configuration. /// The IImageFormat. /// The image format or null if none found. - private static IImageDecoder DiscoverDecoder(Stream stream, Configuration config, out IImageFormat format) + private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format) { - format = InternalDetectFormat(stream, config); + format = InternalDetectFormat(options.Configuration, stream); return format != null - ? config.ImageFormatsManager.FindDecoder(format) + ? options.Configuration.ImageFormatsManager.FindDecoder(format) : null; } /// /// Decodes the image stream to the current image. /// + /// The general decoder options. /// The stream. - /// the configuration. /// The token to monitor for cancellation requests. /// The pixel format. /// /// A new . /// - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) + private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { - IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); if (decoder is null) { return (null, null); } - Image img = decoder.Decode(config, stream, cancellationToken); + Image img = decoder.Decode(options, stream, cancellationToken); return (img, format); } - private static (Image Image, IImageFormat Format) Decode(Stream stream, Configuration config, CancellationToken cancellationToken = default) + private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) { - IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); if (decoder is null) { return (null, null); } - Image img = decoder.Decode(config, stream, cancellationToken); + Image img = decoder.Decode(options, stream, cancellationToken); return (img, format); } /// /// Reads the raw image information from the specified stream. /// + /// The general decoder options. /// The stream. - /// the configuration. /// The token to monitor for cancellation requests. /// /// The or null if a suitable info detector is not found. /// - private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(Stream stream, Configuration config, CancellationToken cancellationToken = default) + private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) { - IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); + IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); if (decoder is not IImageInfoDetector detector) { return (null, null); } - IImageInfo info = detector?.Identify(config, stream, cancellationToken); + IImageInfo info = detector?.Identify(options, stream, cancellationToken); return (info, format); } } diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 342143bb5a..0d7fb27f40 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -9,232 +9,30 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { /// - /// Adds static methods allowing the creation of new image from a byte array. + /// Adds static methods allowing the creation of new image from a byte span. /// public abstract partial class Image { - /// - /// By reading the header on the provided byte array this calculates the images format. - /// - /// The byte array containing encoded image data to read the header from. - /// The data is null. - /// The format or null if none found. - public static IImageFormat DetectFormat(byte[] data) - => DetectFormat(Configuration.Default, data); - - /// - /// By reading the header on the provided byte array this calculates the images format. - /// - /// The configuration. - /// The byte array containing encoded image data to read the header from. - /// The configuration is null. - /// The data is null. - /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration configuration, byte[] data) - { - Guard.NotNull(data, nameof(data)); - - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return DetectFormat(configuration, stream); - } - } - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The byte array containing encoded image data to read the header from. - /// The data is null. - /// The data is not readable. - /// - /// The or null if suitable info detector not found. - /// - public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The byte array containing encoded image data to read the header from. - /// The format type of the decoded image. - /// The data is null. - /// The data is not readable. - /// - /// The or null if suitable info detector not found. - /// - public static IImageInfo Identify(byte[] data, out IImageFormat format) => Identify(Configuration.Default, data, out format); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The configuration. - /// The byte array containing encoded image data to read the header from. - /// The format type of the decoded image. - /// The configuration is null. - /// The data is null. - /// The data is not readable. - /// - /// The or null if suitable info detector is not found. - /// - public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format) - { - Guard.NotNull(data, nameof(data)); - - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Identify(configuration, stream, out format); - } - } - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing image data. - /// The configuration is null. - /// The data is null. - /// A new . - public static Image Load(byte[] data) - => Load(Configuration.Default, data); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing encoded image data. - /// The pixel format. - /// The data is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(byte[] data) - where TPixel : unmanaged, IPixel - => Load(Configuration.Default, data); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing image data. - /// The mime type of the decoded image. - /// The pixel format. - /// The data is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(byte[] data, out IImageFormat format) - where TPixel : unmanaged, IPixel - => Load(Configuration.Default, data, out format); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The configuration options. - /// The byte array containing encoded image data. - /// The pixel format. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(Configuration configuration, byte[] data) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(data, nameof(data)); - - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Load(configuration, stream); - } - } - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The configuration options. - /// The byte array containing encoded image data. - /// The of the decoded image. - /// The pixel format. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(data, nameof(data)); - - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Load(configuration, stream, out format); - } - } - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing encoded image data. - /// The decoder. - /// The pixel format. - /// The data is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(byte[] data, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(data, nameof(data)); - - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Load(stream, decoder); - } - } - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The Configuration. - /// The byte array containing encoded image data. - /// The decoder. - /// The pixel format. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(data, nameof(data)); - - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Load(configuration, stream, decoder); - } - } - /// /// By reading the header on the provided byte span this calculates the images format. /// /// The byte span containing encoded image data to read the header from. /// The format or null if none found. - public static IImageFormat DetectFormat(ReadOnlySpan data) => DetectFormat(Configuration.Default, data); + public static IImageFormat DetectFormat(ReadOnlySpan data) + => DetectFormat(DecoderOptions.Default, data); /// /// By reading the header on the provided byte span this calculates the images format. /// - /// The configuration. + /// The general decoder options. /// The byte span containing encoded image data to read the header from. - /// The configuration is null. + /// The options are null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan data) + public static IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan data) { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options.Configuration)); + Configuration configuration = options.Configuration; int maxHeaderSize = configuration.MaxHeaderSize; if (maxHeaderSize <= 0) { @@ -265,7 +63,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(ReadOnlySpan data) where TPixel : unmanaged, IPixel - => Load(Configuration.Default, data); + => Load(DecoderOptions.Default, data); /// /// Load a new instance of from the given encoded byte span. @@ -279,177 +77,51 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(ReadOnlySpan data, out IImageFormat format) where TPixel : unmanaged, IPixel - => Load(Configuration.Default, data, out format); + => Load(DecoderOptions.Default, data, out format); /// /// Load a new instance of from the given encoded byte span. /// + /// The general decoder options. /// The byte span containing encoded image data. - /// The decoder. /// The pixel format. + /// The options are null. /// Image format not recognised. /// Image contains invalid content. /// Image format is not supported. /// A new . - public static Image Load(ReadOnlySpan data, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - => Load(Configuration.Default, data, decoder); - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The configuration options. - /// The byte span containing encoded image data. - /// The pixel format. - /// The configuration is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// A new . - public static unsafe Image Load(Configuration configuration, ReadOnlySpan data) - where TPixel : unmanaged, IPixel - { - fixed (byte* ptr = &data.GetPinnableReference()) - { - using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) - { - return Load(configuration, stream); - } - } - } - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The Configuration. - /// The byte span containing image data. - /// The decoder. - /// The pixel format. - /// The configuration is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// A new . - public static unsafe Image Load( - Configuration configuration, - ReadOnlySpan data, - IImageDecoder decoder) + public static unsafe Image Load(DecoderOptions options, ReadOnlySpan data) where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { - using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) - { - return Load(configuration, stream, decoder); - } + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Load(options, stream); } } /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The general decoder options. /// The byte span containing image data. /// The of the decoded image. /// The pixel format. - /// The configuration is null. + /// The options are null. /// Image format not recognised. /// Image contains invalid content. /// Image format is not supported. /// A new . public static unsafe Image Load( - Configuration configuration, + DecoderOptions options, ReadOnlySpan data, out IImageFormat format) where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { - using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) - { - return Load(configuration, stream, out format); - } - } - } - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing image data. - /// The detected format. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(byte[] data, out IImageFormat format) - => Load(Configuration.Default, data, out format); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte array containing encoded image data. - /// The decoder. - /// The data is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(byte[] data, IImageDecoder decoder) - => Load(Configuration.Default, data, decoder); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The configuration for the decoder. - /// The byte array containing encoded image data. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(Configuration configuration, byte[] data) - => Load(configuration, data, out _); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The configuration for the decoder. - /// The byte array containing image data. - /// The decoder. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) - { - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Load(configuration, stream, decoder); - } - } - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The configuration for the decoder. - /// The byte array containing image data. - /// The mime type of the decoded image. - /// The configuration is null. - /// The data is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) - { - using (var stream = new MemoryStream(data, 0, data.Length, false, true)) - { - return Load(configuration, stream, out format); + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Load(options, stream, out format); } } @@ -462,21 +134,7 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// The . public static Image Load(ReadOnlySpan data) - => Load(Configuration.Default, data); - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The byte span containing image data. - /// The decoder. - /// The data is null. - /// The decoder is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(ReadOnlySpan data, IImageDecoder decoder) - => Load(Configuration.Default, data, decoder); + => Load(DecoderOptions.Default, data); /// /// Load a new instance of from the given encoded byte array. @@ -489,65 +147,37 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// The . public static Image Load(ReadOnlySpan data, out IImageFormat format) - => Load(Configuration.Default, data, out format); + => Load(DecoderOptions.Default, data, out format); /// /// Decodes a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The general decoder options. /// The byte span containing image data. /// The . - public static Image Load(Configuration configuration, ReadOnlySpan data) - => Load(configuration, data, out _); + public static Image Load(DecoderOptions options, ReadOnlySpan data) + => Load(options, data, out _); /// /// Load a new instance of from the given encoded byte span. /// - /// The Configuration. - /// The byte span containing image data. - /// The decoder. - /// The configuration is null. - /// The decoder is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The . - public static unsafe Image Load( - Configuration configuration, - ReadOnlySpan data, - IImageDecoder decoder) - { - fixed (byte* ptr = &data.GetPinnableReference()) - { - using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) - { - return Load(configuration, stream, decoder); - } - } - } - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The configuration options. + /// The general decoder options. /// The byte span containing image data. /// The of the decoded image.> - /// The configuration is null. + /// The options are null. /// Image format not recognised. /// Image contains invalid content. /// Image format is not supported. /// The . public static unsafe Image Load( - Configuration configuration, + DecoderOptions options, ReadOnlySpan data, out IImageFormat format) { fixed (byte* ptr = &data.GetPinnableReference()) { - using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) - { - return Load(configuration, stream, out format); - } + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Load(options, stream, out format); } } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 79c036d24d..6594ed2183 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -21,23 +21,21 @@ namespace SixLabors.ImageSharp /// The image file to open and to read the header from. /// The mime type or null if none found. public static IImageFormat DetectFormat(string filePath) - => DetectFormat(Configuration.Default, filePath); + => DetectFormat(DecoderOptions.Default, filePath); /// /// By reading the header on the provided file this calculates the images mime type. /// - /// The configuration. + /// The general decoder options. /// The image file to open and to read the header from. /// The configuration is null. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration configuration, string filePath) + public static IImageFormat DetectFormat(DecoderOptions options, string filePath) { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); - using (Stream file = configuration.FileSystem.OpenRead(filePath)) - { - return DetectFormat(configuration, file); - } + using Stream file = options.Configuration.FileSystem.OpenRead(filePath); + return DetectFormat(options, file); } /// @@ -59,25 +57,23 @@ namespace SixLabors.ImageSharp /// The or null if suitable info detector not found. /// public static IImageInfo Identify(string filePath, out IImageFormat format) - => Identify(Configuration.Default, filePath, out format); + => Identify(DecoderOptions.Default, filePath, out format); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image file to open and to read the header from. /// The format type of the decoded image. /// The configuration is null. /// /// The or null if suitable info detector is not found. /// - public static IImageInfo Identify(Configuration configuration, string filePath, out IImageFormat format) + public static IImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format) { - Guard.NotNull(configuration, nameof(configuration)); - using (Stream file = configuration.FileSystem.OpenRead(filePath)) - { - return Identify(configuration, file, out format); - } + Guard.NotNull(options, nameof(options)); + using Stream file = options.Configuration.FileSystem.OpenRead(filePath); + return Identify(options, file, out format); } /// @@ -91,12 +87,12 @@ namespace SixLabors.ImageSharp /// property set to null if suitable info detector is not found. /// public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken = default) - => IdentifyAsync(Configuration.Default, filePath, cancellationToken); + => IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image file to open and to read the header from. /// The token to monitor for cancellation requests. /// The configuration is null. @@ -105,12 +101,12 @@ namespace SixLabors.ImageSharp /// property set to null if suitable info detector is not found. /// public static async Task IdentifyAsync( - Configuration configuration, + DecoderOptions options, string filePath, CancellationToken cancellationToken = default) { - (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, filePath, cancellationToken) - .ConfigureAwait(false); + (IImageInfo ImageInfo, IImageFormat Format) res = + await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false); return res.ImageInfo; } @@ -127,12 +123,12 @@ namespace SixLabors.ImageSharp public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( string filePath, CancellationToken cancellationToken = default) - => IdentifyWithFormatAsync(Configuration.Default, filePath, cancellationToken); + => IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image file to open and to read the header from. /// The token to monitor for cancellation requests. /// The configuration is null. @@ -141,13 +137,13 @@ namespace SixLabors.ImageSharp /// property set to null if suitable info detector is not found. /// public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - Configuration configuration, + DecoderOptions options, string filePath, CancellationToken cancellationToken = default) { - Guard.NotNull(configuration, nameof(configuration)); - using Stream stream = configuration.FileSystem.OpenRead(filePath); - return await IdentifyWithFormatAsync(configuration, stream, cancellationToken) + Guard.NotNull(options, nameof(options)); + using Stream stream = options.Configuration.FileSystem.OpenRead(filePath); + return await IdentifyWithFormatAsync(options, stream, cancellationToken) .ConfigureAwait(false); } @@ -160,7 +156,7 @@ namespace SixLabors.ImageSharp /// /// The . public static Image Load(string path) - => Load(Configuration.Default, path); + => Load(DecoderOptions.Default, path); /// /// Create a new instance of the class from the given file. @@ -172,12 +168,12 @@ namespace SixLabors.ImageSharp /// /// A new . public static Image Load(string path, out IImageFormat format) - => Load(Configuration.Default, path, out format); + => Load(DecoderOptions.Default, path, out format); /// /// Create a new instance of the class from the given file. /// - /// The configuration for the decoder. + /// The general decoder options. /// The file path to the image. /// The configuration is null. /// The path is null. @@ -185,13 +181,13 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// Image contains invalid content. /// The . - public static Image Load(Configuration configuration, string path) - => Load(configuration, path, out _); + public static Image Load(DecoderOptions options, string path) + => Load(options, path, out _); /// /// Create a new instance of the class from the given file. /// - /// The configuration for the decoder. + /// The general decoder options. /// The file path to the image. /// The token to monitor for cancellation requests. /// The configuration is null. @@ -201,40 +197,16 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// A representing the asynchronous operation. public static async Task LoadAsync( - Configuration configuration, + DecoderOptions options, string path, CancellationToken cancellationToken = default) { - using Stream stream = configuration.FileSystem.OpenRead(path); - (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) .ConfigureAwait(false); return img; } - /// - /// Create a new instance of the class from the given file. - /// - /// The Configuration. - /// The file path to the image. - /// The decoder. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The . - public static Image Load(Configuration configuration, string path, IImageDecoder decoder) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(path, nameof(path)); - - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - return Load(configuration, stream, decoder); - } - } - /// /// Create a new instance of the class from the given file. /// @@ -248,99 +220,7 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// A representing the asynchronous operation. public static Task LoadAsync(string path, CancellationToken cancellationToken = default) - => LoadAsync(Configuration.Default, path, cancellationToken); - - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default) - => LoadAsync(Configuration.Default, path, decoder, cancellationToken); - - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, path, decoder, cancellationToken); - - /// - /// Create a new instance of the class from the given file. - /// - /// The Configuration. - /// The file path to the image. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static async Task LoadAsync( - Configuration configuration, - string path, - IImageDecoder decoder, - CancellationToken cancellationToken = default) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(path, nameof(path)); - - using Stream stream = configuration.FileSystem.OpenRead(path); - return await LoadAsync(configuration, stream, decoder, cancellationToken) - .ConfigureAwait(false); - } - - /// - /// Create a new instance of the class from the given file. - /// - /// The Configuration. - /// The file path to the image. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static async Task> LoadAsync( - Configuration configuration, - string path, - IImageDecoder decoder, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(path, nameof(path)); - - using Stream stream = configuration.FileSystem.OpenRead(path); - return await LoadAsync(configuration, stream, decoder, cancellationToken) - .ConfigureAwait(false); - } + => LoadAsync(DecoderOptions.Default, path, cancellationToken); /// /// Create a new instance of the class from the given file. @@ -356,12 +236,12 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation. public static Task> LoadAsync(string path, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, path, cancellationToken); + => LoadAsync(DecoderOptions.Default, path, cancellationToken); /// /// Create a new instance of the class from the given file. /// - /// The configuration for the decoder. + /// The general decoder options. /// The file path to the image. /// The token to monitor for cancellation requests. /// The configuration is null. @@ -372,31 +252,19 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A representing the asynchronous operation. public static async Task> LoadAsync( - Configuration configuration, + DecoderOptions options, string path, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { - using Stream stream = configuration.FileSystem.OpenRead(path); - (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + Guard.NotNull(options, nameof(options)); + + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + (Image img, _) = + await LoadWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); return img; } - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The decoder. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The . - public static Image Load(string path, IImageDecoder decoder) - => Load(Configuration.Default, path, decoder); - /// /// Create a new instance of the class from the given file. /// @@ -409,7 +277,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(string path) where TPixel : unmanaged, IPixel - => Load(Configuration.Default, path); + => Load(DecoderOptions.Default, path); /// /// Create a new instance of the class from the given file. @@ -424,12 +292,12 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(string path, out IImageFormat format) where TPixel : unmanaged, IPixel - => Load(Configuration.Default, path, out format); + => Load(DecoderOptions.Default, path, out format); /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The general decoder options. /// The file path to the image. /// The configuration is null. /// The path is null. @@ -438,22 +306,20 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration configuration, string path) + public static Image Load(DecoderOptions options, string path) where TPixel : unmanaged, IPixel { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); Guard.NotNull(path, nameof(path)); - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - return Load(configuration, stream); - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + return Load(options, stream); } /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The general decoder options. /// The file path to the image. /// The mime type of the decoded image. /// The configuration is null. @@ -463,23 +329,21 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Configuration configuration, string path, out IImageFormat format) + public static Image Load(DecoderOptions options, string path, out IImageFormat format) where TPixel : unmanaged, IPixel { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); Guard.NotNull(path, nameof(path)); - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - return Load(configuration, stream, out format); - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + return Load(options, stream, out format); } /// /// Create a new instance of the class from the given file. /// The pixel type is selected by the decoder. /// - /// The configuration options. + /// The general decoder options. /// The file path to the image. /// The mime type of the decoded image. /// The configuration is null. @@ -488,56 +352,13 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// Image contains invalid content. /// A new . - public static Image Load(Configuration configuration, string path, out IImageFormat format) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(path, nameof(path)); - - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - return Load(configuration, stream, out format); - } - } - - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The decoder. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(string path, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - => Load(Configuration.Default, path, decoder); - - /// - /// Create a new instance of the class from the given file. - /// - /// The Configuration. - /// The file path to the image. - /// The decoder. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(Configuration configuration, string path, IImageDecoder decoder) - where TPixel : unmanaged, IPixel + public static Image Load(DecoderOptions options, string path, out IImageFormat format) { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); Guard.NotNull(path, nameof(path)); - using (Stream stream = configuration.FileSystem.OpenRead(path)) - { - return Load(configuration, stream, decoder); - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + return Load(options, stream, out format); } } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 803d8a7544..86baa1c1a9 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -26,19 +26,19 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// The format type or null if none found. public static IImageFormat DetectFormat(Stream stream) - => DetectFormat(Configuration.Default, stream); + => DetectFormat(DecoderOptions.Default, stream); /// /// By reading the header on the provided stream this calculates the images format type. /// - /// The configuration. + /// The general decoder options. /// The image stream to read the header from. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// The format type or null if none found. - public static IImageFormat DetectFormat(Configuration configuration, Stream stream) - => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration)); + public static IImageFormat DetectFormat(DecoderOptions options, Stream stream) + => WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); /// /// By reading the header on the provided stream this calculates the images format type. @@ -49,23 +49,23 @@ namespace SixLabors.ImageSharp /// The stream is not readable. /// A representing the asynchronous operation or null if none is found. public static Task DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) - => DetectFormatAsync(Configuration.Default, stream, cancellationToken); + => DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken); /// /// By reading the header on the provided stream this calculates the images format type. /// - /// The configuration. + /// The general decoder options. /// The image stream to read the header from. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// A representing the asynchronous operation. - public static Task DetectFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) + public static Task DetectFormatAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) => WithSeekableStreamAsync( - configuration, + options, stream, - (s, _) => InternalDetectFormat(s, configuration), + (s, _) => InternalDetectFormat(options.Configuration, s), cancellationToken); /// @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp /// a suitable detector is not found. /// public static Task IdentifyAsync(Stream stream, CancellationToken cancellationToken = default) - => IdentifyAsync(Configuration.Default, stream, cancellationToken); + => IdentifyAsync(DecoderOptions.Default, stream, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -108,30 +108,30 @@ namespace SixLabors.ImageSharp /// The or null if a suitable info detector is not found. /// public static IImageInfo Identify(Stream stream, out IImageFormat format) - => Identify(Configuration.Default, stream, out format); + => Identify(DecoderOptions.Default, stream, out format); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image stream to read the information from. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. /// /// The or null if a suitable info detector is not found. /// - public static IImageInfo Identify(Configuration configuration, Stream stream) - => Identify(configuration, stream, out _); + public static IImageInfo Identify(DecoderOptions options, Stream stream) + => Identify(options, stream, out _); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image stream to read the information from. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. @@ -140,30 +140,30 @@ namespace SixLabors.ImageSharp /// a suitable detector is not found. /// public static async Task IdentifyAsync( - Configuration configuration, + DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) { - (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); return res.ImageInfo; } /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image stream to read the information from. /// The format type of the decoded image. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. /// /// The or null if a suitable info detector is not found. /// - public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format) + public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format) { - (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); + (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentity(options, s)); format = data.Format; return data.ImageInfo; @@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp /// /// The image stream to read the information from. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. @@ -185,15 +185,15 @@ namespace SixLabors.ImageSharp public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( Stream stream, CancellationToken cancellationToken = default) - => IdentifyWithFormatAsync(Configuration.Default, stream, cancellationToken); + => IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The general decoder options. /// The image stream to read the information from. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. @@ -202,13 +202,13 @@ namespace SixLabors.ImageSharp /// property set to null if suitable info detector is not found. /// public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - Configuration configuration, + DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) => WithSeekableStreamAsync( - configuration, + options, stream, - (s, ct) => InternalIdentity(s, configuration ?? Configuration.Default, ct), + (s, ct) => InternalIdentity(options, s, ct), cancellationToken); /// @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The . public static Image Load(Stream stream, out IImageFormat format) - => Load(Configuration.Default, stream, out format); + => Load(DecoderOptions.Default, stream, out format); /// /// Decode a new instance of the class from the given stream. @@ -237,7 +237,7 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// A representing the asynchronous operation. public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) - => LoadWithFormatAsync(Configuration.Default, stream, cancellationToken); + => LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp /// Image format not recognised. /// Image contains invalid content. /// The . - public static Image Load(Stream stream) => Load(Configuration.Default, stream); + public static Image Load(Stream stream) => Load(DecoderOptions.Default, stream); /// /// Decode a new instance of the class from the given stream. @@ -263,116 +263,37 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// A representing the asynchronous operation. public static Task LoadAsync(Stream stream, CancellationToken cancellationToken = default) - => LoadAsync(Configuration.Default, stream, cancellationToken); + => LoadAsync(DecoderOptions.Default, stream, cancellationToken); /// /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. /// + /// The general decoder options. /// The stream containing image information. - /// The decoder. + /// The options are null. /// The stream is null. - /// The decoder is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The . - public static Image Load(Stream stream, IImageDecoder decoder) - => Load(Configuration.Default, stream, decoder); - - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The stream containing image information. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The decoder is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default) - => LoadAsync(Configuration.Default, stream, decoder, cancellationToken); - - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The configuration for the decoder. - /// The stream containing image information. - /// The decoder. - /// The configuration is null. - /// The stream is null. - /// The decoder is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A new . - public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) - { - Guard.NotNull(decoder, nameof(decoder)); - return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default)); - } + public static Image Load(DecoderOptions options, Stream stream) + => Load(options, stream, out _); /// /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. /// - /// The configuration for the decoder. + /// The general decoder options. /// The stream containing image information. - /// The decoder. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. - /// The decoder is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync( - Configuration configuration, - Stream stream, - IImageDecoder decoder, - CancellationToken cancellationToken = default) + public static async Task LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) { - Guard.NotNull(decoder, nameof(decoder)); - return WithSeekableStreamAsync( - configuration, - stream, - (s, ct) => decoder.Decode(configuration, s, ct), - cancellationToken); - } - - /// - /// Decode a new instance of the class from the given stream. - /// - /// The configuration for the decoder. - /// The stream containing image information. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A new . - public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); - - /// - /// Decode a new instance of the class from the given stream. - /// - /// The configuration for the decoder. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static async Task LoadAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) - { - (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(configuration, stream, cancellationToken) + (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(options, stream, cancellationToken) .ConfigureAwait(false); return fmt.Image; } @@ -389,7 +310,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Stream stream) where TPixel : unmanaged, IPixel - => Load(Configuration.Default, stream); + => Load(DecoderOptions.Default, stream); /// /// Create a new instance of the class from the given stream. @@ -404,7 +325,7 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation. public static Task> LoadAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, stream, cancellationToken); + => LoadAsync(DecoderOptions.Default, stream, cancellationToken); /// /// Create a new instance of the class from the given stream. @@ -419,7 +340,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(Stream stream, out IImageFormat format) where TPixel : unmanaged, IPixel - => Load(Configuration.Default, stream, out format); + => Load(DecoderOptions.Default, stream, out format); /// /// Create a new instance of the class from the given stream. @@ -434,119 +355,41 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation. public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadWithFormatAsync(Configuration.Default, stream, cancellationToken); + => LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); /// /// Create a new instance of the class from the given stream. /// + /// The general decoder options. /// The stream containing image information. - /// The decoder. + /// The options are null. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. /// A new . - public static Image Load(Stream stream, IImageDecoder decoder) + public static Image Load(DecoderOptions options, Stream stream) where TPixel : unmanaged, IPixel - => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s, default)); + => Load(options, stream, out IImageFormat _); /// /// Create a new instance of the class from the given stream. /// - /// The stream containing image information. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => WithSeekableStreamAsync( - Configuration.Default, - stream, - (s, ct) => decoder.Decode(Configuration.Default, s, ct), - cancellationToken); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The Configuration. - /// The stream containing image information. - /// The decoder. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) - where TPixel : unmanaged, IPixel - => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s, default)); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The Configuration. - /// The stream containing image information. - /// The decoder. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync( - Configuration configuration, - Stream stream, - IImageDecoder decoder, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => WithSeekableStreamAsync( - configuration, - stream, - (s, ct) => decoder.Decode(configuration, s, ct), - cancellationToken); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The configuration options. - /// The stream containing image information. - /// The configuration is null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(Configuration configuration, Stream stream) - where TPixel : unmanaged, IPixel - => Load(configuration, stream, out IImageFormat _); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The configuration options. + /// The general decoder options. /// The stream containing image information. /// The format type of the decoded image. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) + public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) where TPixel : unmanaged, IPixel { - (Image Image, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); + (Image Image, IImageFormat Format) data = WithSeekableStream(options, stream, s => Decode(options, s)); format = data.Format; @@ -558,7 +401,7 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) { sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } @@ -569,24 +412,24 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The general decoder options. /// The stream containing image information. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( - Configuration configuration, + DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) { (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( - configuration, + options, stream, - (s, ct) => Decode(s, configuration, ct), + (s, ct) => Decode(options, s, ct), cancellationToken) .ConfigureAwait(false); @@ -598,7 +441,7 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) { sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } @@ -609,10 +452,10 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The general decoder options. /// The stream containing image information. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. @@ -620,16 +463,16 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A representing the asynchronous operation. public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( - Configuration configuration, + DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( - configuration, + options, stream, - (s, ct) => Decode(s, configuration, ct), + (s, ct) => Decode(options, s, ct), cancellationToken) .ConfigureAwait(false); @@ -641,7 +484,7 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) { sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } @@ -652,10 +495,10 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The general decoder options. /// The stream containing image information. /// The token to monitor for cancellation requests. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. @@ -663,12 +506,12 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A representing the asynchronous operation. public static async Task> LoadAsync( - Configuration configuration, + DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel { - (Image img, _) = await LoadWithFormatAsync(configuration, stream, cancellationToken) + (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) .ConfigureAwait(false); return img; } @@ -677,30 +520,30 @@ namespace SixLabors.ImageSharp /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// - /// The configuration options. + /// The general decoder options. /// The stream containing image information. /// The format type of the decoded image. - /// The configuration is null. + /// The options are null. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A new . - public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) + public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) { - (Image Img, IImageFormat Format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); + (Image img, IImageFormat fmt) = WithSeekableStream(options, stream, s => Decode(options, s)); - format = data.Format; + format = fmt; - if (data.Img != null) + if (img != null) { - return data.Img; + return img; } var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) { sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } @@ -712,16 +555,16 @@ namespace SixLabors.ImageSharp /// Performs the given action against the stream ensuring that it is seekable. /// /// The type of object returned from the action. - /// The configuration. + /// The general decoder options. /// The input stream. /// The action to perform. /// The . private static T WithSeekableStream( - Configuration configuration, + DecoderOptions options, Stream stream, Func action) { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); if (!stream.CanRead) @@ -729,6 +572,7 @@ namespace SixLabors.ImageSharp throw new NotSupportedException("Cannot read from the stream."); } + Configuration configuration = options.Configuration; if (stream.CanSeek) { if (configuration.ReadOrigin == ReadOrigin.Begin) @@ -751,18 +595,18 @@ namespace SixLabors.ImageSharp /// Performs the given action asynchronously against the stream ensuring that it is seekable. /// /// The type of object returned from the action. - /// The configuration. + /// The general decoder options. /// The input stream. /// The action to perform. /// The cancellation token. /// The . private static async Task WithSeekableStreamAsync( - Configuration configuration, + DecoderOptions options, Stream stream, Func action, CancellationToken cancellationToken) { - Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); if (!stream.CanRead) @@ -770,6 +614,7 @@ namespace SixLabors.ImageSharp throw new NotSupportedException("Cannot read from the stream."); } + Configuration configuration = options.Configuration; if (stream.CanSeek) { if (configuration.ReadOrigin == ReadOrigin.Begin) diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index 93563c794d..a59f5a34ce 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -16,20 +16,7 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the raw data. /// - /// The byte array containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(TPixel[] data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(Configuration.Default, data, width, height); - - /// - /// Create a new instance of the class from the raw data. - /// - /// The byte array containing image data. + /// The readonly span of bytes containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. @@ -40,22 +27,9 @@ namespace SixLabors.ImageSharp => LoadPixelData(Configuration.Default, data, width, height); /// - /// Create a new instance of the class from the given byte array in format. + /// Create a new instance of the class from the given readonly span of bytes in format. /// - /// The byte array containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(byte[] data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(Configuration.Default, data, width, height); - - /// - /// Create a new instance of the class from the given byte array in format. - /// - /// The byte array containing image data. + /// The readonly span of bytes containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. @@ -66,25 +40,10 @@ namespace SixLabors.ImageSharp => LoadPixelData(Configuration.Default, data, width, height); /// - /// Create a new instance of the class from the given byte array in format. - /// - /// The configuration for the decoder. - /// The byte array containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The configuration is null. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(Configuration configuration, byte[] data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(configuration, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height); - - /// - /// Create a new instance of the class from the given byte array in format. + /// Create a new instance of the class from the given readonly span of bytes in format. /// /// The configuration for the decoder. - /// The byte array containing image data. + /// The readonly span of bytes containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. @@ -99,22 +58,7 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the raw data. /// /// The configuration for the decoder. - /// The Span containing the image Pixel data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The configuration is null. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(Configuration configuration, TPixel[] data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(configuration, new ReadOnlySpan(data), width, height); - - /// - /// Create a new instance of the class from the raw data. - /// - /// The configuration for the decoder. - /// The Span containing the image Pixel data. + /// The readonly span containing the image pixel data. /// The width of the final image. /// The height of the final image. /// The configuration is null. diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index c26de91590..bee55600f3 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -13,6 +13,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; @@ -157,7 +158,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave this.TotalProcessedMegapixels += pixels / 1_000_000.0; } - private string OutputPath(string inputPath, [CallerMemberName]string postfix = null) => + private string OutputPath(string inputPath, [CallerMemberName] string postfix = null) => Path.Combine( this.outputDirectory, Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); @@ -208,18 +209,15 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave using FileStream outputStream = File.Open(this.OutputPath(input), FileMode.Create); // Resize it to fit a 150x150 square - var targetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize); + DecoderOptions options = new() + { + 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.Decode(options, inputStream, default); this.LogImageProcessed(image.Width, image.Height); - image.Mutate(i => i.Resize(new ResizeOptions - { - Size = targetSize, - Mode = ResizeMode.Max, - Sampler = KnownResamplers.Box - })); - // Reduce the size of the file image.Metadata.ExifProfile = null; image.Metadata.XmpProfile = null; @@ -235,18 +233,19 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave using FileStream output = File.Open(this.OutputPath(input), FileMode.Create); // Resize it to fit a 150x150 square. - using ImageSharpImage image = await ImageSharpImage.LoadAsync(input); - this.LogImageProcessed(image.Width, image.Height); - - // Resize checks whether image size and target sizes are equal - image.Mutate(i => i.Resize(new ResizeOptions + DecoderOptions options = new() { - Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize), - Mode = ResizeMode.Max - })); + TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) + }; + + using ImageSharpImage image = await ImageSharpImage.LoadAsync(options, input); + this.LogImageProcessed(image.Width, image.Height); // Reduce the size of the file image.Metadata.ExifProfile = null; + image.Metadata.XmpProfile = null; + image.Metadata.IccProfile = null; + image.Metadata.IptcProfile = null; // Save the results await image.SaveAsync(output, this.imageSharpJpegEncoder); From 1c2d1f42e2c46e2d3d1f025e93db9ea91872c0f1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 01:14:04 +1000 Subject: [PATCH 43/98] Update tests and fix issues --- .../Formats/Bmp/BmpDecoderOptions.cs | 4 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/ImageDecoder{T}.cs | 2 +- .../Formats/Tiff/TiffDecoderCore.cs | 2 +- .../Formats/Webp/WebpAnimationDecoder.cs | 2 +- src/ImageSharp/Image.FromBytes.cs | 45 +++ .../Codecs/Jpeg/DecodeJpeg.cs | 3 +- .../Codecs/Jpeg/DecodeJpegParseStreamOnly.cs | 4 +- .../Codecs/Jpeg/DecodeJpeg_Aggregate.cs | 21 +- .../Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs | 23 +- .../Codecs/Jpeg/IdentifyJpeg.cs | 3 +- .../Codecs/Tga/DecodeTga.cs | 5 +- .../Codecs/Webp/DecodeWebp.cs | 4 +- .../Formats/Bmp/BmpDecoderTests.cs | 344 +++++++----------- .../Formats/Bmp/BmpEncoderTests.cs | 194 +++++----- .../Formats/GeneralFormatTests.cs | 136 +++---- .../Formats/Gif/GifDecoderTests.cs | 81 ++--- .../Formats/Gif/GifMetadataTests.cs | 125 +++---- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 128 +++---- .../Formats/Jpg/JpegDecoderTests.cs | 98 ++--- .../Formats/Jpg/SpectralJpegTests.cs | 6 +- .../Jpg/SpectralToPixelConversionTests.cs | 31 +- .../Formats/Jpg/Utils/JpegFixture.cs | 11 +- .../Formats/Png/PngDecoderTests.Chunks.cs | 10 +- .../Formats/Png/PngEncoderTests.cs | 226 +++++------- .../Formats/Png/PngMetadataTests.cs | 282 +++++++------- .../Formats/Png/PngSmokeTests.cs | 45 +-- .../Formats/Tga/TgaFileHeaderTests.cs | 2 +- .../Formats/Tiff/TiffDecoderTests.cs | 8 +- .../Formats/Tiff/TiffMetadataTests.cs | 202 +++++----- .../Formats/WebP/PredictorEncoderTests.cs | 4 +- .../Formats/WebP/WebpDecoderTests.cs | 241 +++++------- .../Formats/WebP/WebpMetaDataTests.cs | 61 ++-- .../Image/ImageTests.Decode_Cancellation.cs | 91 +++-- .../Image/ImageTests.DetectFormat.cs | 60 ++- .../Image/ImageTests.Identify.cs | 105 ++++-- .../Image/ImageTests.ImageLoadTestBase.cs | 29 +- ...d_FileSystemPath_PassLocalConfiguration.cs | 67 ++-- ..._FileSystemPath_UseDefaultConfiguration.cs | 66 +--- ...s.Load_FromBytes_PassLocalConfiguration.cs | 90 ++--- ...s.Load_FromBytes_UseGlobalConfiguration.cs | 83 +---- ....Load_FromStream_PassLocalConfiguration.cs | 63 ++-- ...ts.Load_FromStream_ThrowsRightException.cs | 17 +- ...Load_FromStream_UseDefaultConfiguration.cs | 91 +---- .../Image/LargeImageIntegrationTests.cs | 10 +- .../Profiles/Exif/ExifProfileTests.cs | 84 ++--- .../Profiles/IPTC/IptcProfileTests.cs | 30 +- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 10 +- .../JpegProfilingBenchmarks.cs | 19 +- .../LoadResizeSaveProfilingBenchmarks.cs | 32 +- tests/ImageSharp.Tests/TestFile.cs | 56 ++- tests/ImageSharp.Tests/TestFormat.cs | 36 +- .../ImageProviders/FileProvider.cs | 77 +++- .../ImageProviders/TestImageProvider.cs | 53 +-- .../ReferenceCodecs/MagickReferenceDecoder.cs | 77 ++-- .../SystemDrawingReferenceDecoder.cs | 67 ++-- .../TestUtilities/TestEnvironment.Formats.cs | 2 +- .../TestUtilities/TestImageExtensions.cs | 66 ++-- .../Tests/MagickReferenceCodecTests.cs | 51 +-- .../Tests/SystemDrawingReferenceCodecTests.cs | 86 ++--- .../Tests/TestImageProviderTests.cs | 109 +++--- 61 files changed, 1826 insertions(+), 2156 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index b01d179802..1f5ce08d1c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp public DecoderOptions GeneralOptions { get; set; } = new(); /// - /// Gets the value indicating how to deal with skipped pixels, + /// Gets or sets the value indicating how to deal with skipped pixels, /// which can occur during decoding run length encoded bitmaps. /// - public RleSkippedPixelHandling RleSkippedPixelHandling { get; } + public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index f9098538b2..ff4b97b3c8 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (nextFlag == GifConstants.ImageLabel) { - if (previousFrame != null && frameCount++ > this.maxFrames) + if (previousFrame != null && ++frameCount == this.maxFrames) { break; } diff --git a/src/ImageSharp/Formats/ImageDecoder{T}.cs b/src/ImageSharp/Formats/ImageDecoder{T}.cs index 3a75595790..6f43b053f9 100644 --- a/src/ImageSharp/Formats/ImageDecoder{T}.cs +++ b/src/ImageSharp/Formats/ImageDecoder{T}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats /// /// The type of specialized decoder options. public abstract class ImageDecoder : IImageInfoDetector, IImageDecoder - where T : ISpecializedDecoderOptions, new() + where T : class, ISpecializedDecoderOptions, new() { /// public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index cec1997367..f2e5fc7dec 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); frames.Add(frame); - if (frameCount++ > this.maxFrames) + if (++frameCount == this.maxFrames) { break; } diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index e2083efe74..57c01cac74 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp break; } - if (stream.Position == stream.Length || frameCount++ > this.maxFrames) + if (stream.Position == stream.Length || ++frameCount == this.maxFrames) { break; } diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 0d7fb27f40..9635ec76ef 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -52,6 +52,51 @@ namespace SixLabors.ImageSharp return default; } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte span containing encoded image data to read the header from. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(ReadOnlySpan data) => Identify(data, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(ReadOnlySpan data, out IImageFormat format) + => Identify(DecoderOptions.Default, data, out format); + + /// + /// Reads the raw image information from the specified span of bytes without fully decoding it. + /// + /// The general decoder options. + /// The byte span containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan data, out IImageFormat format) + { + fixed (byte* ptr = &data.GetPinnableReference()) + { + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Identify(options, stream, out format); + } + } + /// /// Load a new instance of from the given encoded byte span. /// diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 79daa7df48..1c8977f5e3 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -3,6 +3,7 @@ using System.IO; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private void GenericBechmark() { this.preloadedImageStream.Position = 0; - using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream, default); + using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream, default); } [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 34aa111448..546051772e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -38,8 +38,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using var memoryStream = new MemoryStream(this.jpegBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); + var options = new JpegDecoderOptions(); + options.GeneralOptions.SkipMetadata = true; - using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true }); + using var decoder = new JpegDecoderCore(options); var spectralConverter = new NoopSpectralConverter(); decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index 5b3fa68b8e..d6d699c0c8 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -18,22 +17,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase { - protected override IEnumerable InputImageSubfoldersOrFiles => - new[] - { - TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, - TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, - }; + protected override IEnumerable InputImageSubfoldersOrFiles + => new[] + { + TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, + TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + }; [Params(InputImageCategory.AllImages)] public override InputImageCategory InputCategory { get; set; } [Benchmark] public void ImageSharp() - => this.ForEachStream(ms => Image.Load(ms, new JpegDecoder())); + => this.ForEachStream(ms => Image.Load(ms)); [Benchmark(Baseline = true)] public void SystemDrawing() diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 2c0c109978..f704bef280 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -3,8 +3,7 @@ using System.IO; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; using SDSize = System.Drawing.Size; @@ -47,25 +46,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public SDSize SystemDrawing() { - using (var memoryStream = new MemoryStream(this.jpegBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark] public Size ImageSharp() { - using (var memoryStream = new MemoryStream(this.jpegBytes)) - { - using (var image = Image.Load(memoryStream, new JpegDecoder { IgnoreMetadata = true })) - { - return new Size(image.Width, image.Height); - } - } + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream); + return new Size(image.Width, image.Height); } /* diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index b9f1e72fcd..d1e1f0e77e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -3,6 +3,7 @@ using System.IO; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; @@ -32,7 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using var memoryStream = new MemoryStream(this.jpegBytes); var decoder = new JpegDecoder(); - return decoder.Identify(Configuration.Default, memoryStream, default); + return decoder.Identify(DecoderOptions.Default, memoryStream, default); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs index 4a96a2b533..9e60fecacc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs @@ -7,7 +7,6 @@ using System.Threading; using BenchmarkDotNet.Attributes; using ImageMagick; using Pfim; -using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - private readonly PfimConfig pfimConfig = new PfimConfig(allocator: new PfimAllocator()); + private readonly PfimConfig pfimConfig = new(allocator: new PfimAllocator()); private byte[] data; @@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "ImageSharp Tga")] public int TgaImageSharp() { - using var image = Image.Load(this.data, new TgaDecoder()); + using var image = Image.Load(this.data); return image.Width; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs index 55d2a83452..f49310b9c3 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossy() { using var memoryStream = new MemoryStream(this.webpLossyBytes); - using var image = Image.Load(this.configuration, memoryStream); + using var image = Image.Load(memoryStream); return image.Height; } @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int WebpLossless() { using var memoryStream = new MemoryStream(this.webpLosslessBytes); - using var image = Image.Load(this.configuration, memoryStream); + using var image = Image.Load(memoryStream); return image.Height; } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 239e209765..8a5892e027 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using Microsoft.DotNet.RemoteExecutor; - +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -75,11 +75,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco") - .Dispose(); + RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose(); } [Theory] @@ -98,11 +94,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -111,11 +105,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -124,11 +116,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); } [Theory] @@ -136,11 +126,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -148,11 +136,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -160,11 +146,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -172,11 +156,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -184,11 +166,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -199,11 +179,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp where TPixel : unmanaged, IPixel { RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling })) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; + + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -212,11 +192,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp where TPixel : unmanaged, IPixel { RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = skippedPixelHandling })) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; + + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -227,13 +207,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + if (TestEnvironment.IsWindows) { - image.DebugSave(provider); - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); - } + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); } } @@ -243,11 +222,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -263,11 +241,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); } - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -285,13 +262,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); } - using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) - { - image.DebugSave(provider); + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); - // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. - // image.CompareToOriginal(provider); - } + // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. + // image.CompareToOriginal(provider); } [Theory] @@ -299,13 +275,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. - // image.CompareToOriginal(provider); - } + // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. + // image.CompareToOriginal(provider); } [Theory] @@ -313,11 +287,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -325,17 +297,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - - // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel - // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, - // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. - // The total difference without the alpha channel is still: 0.0204% - // Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation. - image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder()); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + + // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel + // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, + // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. + // The total difference without the alpha channel is still: 0.0204% + // Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation. + image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder()); } [Theory] @@ -344,13 +314,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // Do not validate. Reference files will fail validation. - image.CompareToOriginal(provider, new MagickReferenceDecoder(false)); - } + // Do not validate. Reference files will fail validation. + image.CompareToOriginal(provider, new MagickReferenceDecoder(false)); } [Theory] @@ -358,11 +326,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -370,11 +336,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -383,13 +347,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + if (TestEnvironment.IsWindows) { - image.DebugSave(provider); - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } + image.CompareToOriginal(provider); } } @@ -397,39 +359,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider) where TPixel : unmanaged, IPixel - { - Assert.Throws(() => + => Assert.Throws(() => { using (provider.GetImage(BmpDecoder)) { } }); - } [Theory] [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] [WithFile(Rgb24png, PixelTypes.Rgba32)] public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) where TPixel : unmanaged, IPixel - { - Assert.Throws(() => + => Assert.Throws(() => { using (provider.GetImage(BmpDecoder)) { } }); - } [Theory] [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -437,11 +393,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -449,11 +403,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -462,11 +414,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -474,11 +424,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -486,11 +434,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -498,11 +444,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } [Theory] @@ -521,12 +465,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); } [Theory] @@ -541,13 +483,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); } [Theory] @@ -555,17 +495,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new BmpDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream, default)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new BmpDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -573,13 +509,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // image.CompareToOriginal(provider); - } + // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // image.CompareToOriginal(provider); } [Theory] @@ -587,15 +521,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it, - // but i think incorrectly. I have loaded the image with GIMP and exported as PNG. - // The results are the same as the image sharp implementation. - // image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + // TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it, + // but i think incorrectly. I have loaded the image with GIMP and exported as PNG. + // The results are the same as the image sharp implementation. + // image.CompareToOriginal(provider, new MagickReferenceDecoder()); } [Theory] @@ -611,13 +543,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(BmpDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // image.CompareToOriginal(provider); - } + // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // image.CompareToOriginal(provider); } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index dd59fb2795..5dd712c1ff 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -53,22 +53,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var options = new BmpEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, options); - - 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); - } - } - } + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, options); + + 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] @@ -78,21 +72,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var options = new BmpEncoder(); var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, options); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - BmpMetadata meta = output.Metadata.GetBmpMetadata(); - - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } - } - } + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, options); + + memStream.Position = 0; + using var output = Image.Load(memStream); + BmpMetadata meta = output.Metadata.GetBmpMetadata(); + + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); } [Theory] @@ -237,28 +225,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp return; } - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + var encoder = new BmpEncoder { - var encoder = new BmpEncoder - { - BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new WuQuantizer() - }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - - // Use the default decoder to test our encoded image. This verifies the content. - // We do not verify the reference image though as some are invalid. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) - { - referenceImage.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - extension: "bmp", - appendPixelTypeToFileName: false, - decoder: new MagickReferenceDecoder(false)); - } - } + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new WuQuantizer() + }; + + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + + // Use the default decoder to test our encoded image. This verifies the content. + // We do not verify the reference image though as some are invalid. + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using FileStream stream = File.OpenRead(actualOutputFile); + using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); + referenceImage.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + extension: "bmp", + appendPixelTypeToFileName: false, + decoder: new MagickReferenceDecoder(false)); } [Theory] @@ -271,28 +257,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp return; } - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + var encoder = new BmpEncoder { - var encoder = new BmpEncoder - { - BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new OctreeQuantizer() - }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - - // Use the default decoder to test our encoded image. This verifies the content. - // We do not verify the reference image though as some are invalid. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) - { - referenceImage.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - extension: "bmp", - appendPixelTypeToFileName: false, - decoder: new MagickReferenceDecoder(false)); - } - } + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new OctreeQuantizer() + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + + // Use the default decoder to test our encoded image. This verifies the content. + // We do not verify the reference image though as some are invalid. + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using FileStream stream = File.OpenRead(actualOutputFile); + using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); + referenceImage.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + extension: "bmp", + appendPixelTypeToFileName: false, + decoder: new MagickReferenceDecoder(false)); } [Theory] @@ -306,26 +289,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_PreservesColorProfile(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image input = provider.GetImage(new BmpDecoder())) - { - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using (var memStream = new MemoryStream()) - { - input.Save(memStream, new BmpEncoder()); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } - } - } + using Image input = provider.GetImage(new BmpDecoder(), new()); + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); + + using var memStream = new MemoryStream(); + input.Save(memStream, new BmpEncoder()); + + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); + + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); } [Theory] @@ -346,24 +323,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp ImageComparer customComparer = null) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + + // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel != BmpBitsPerPixel.Pixel32) { - // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. - if (bitsPerPixel != BmpBitsPerPixel.Pixel32) - { - image.Mutate(c => c.MakeOpaque()); - } - - var encoder = new BmpEncoder - { - BitsPerPixel = bitsPerPixel, - SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Octree - }; - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); + image.Mutate(c => c.MakeOpaque()); } + + var encoder = new BmpEncoder + { + BitsPerPixel = bitsPerPixel, + SupportTransparency = supportTransparency, + Quantizer = quantizer ?? KnownQuantizers.Octree + }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index e936eef657..d19f4862be 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -45,12 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Formats public void ResolutionShouldChange(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.Metadata.VerticalResolution = 150; - image.Metadata.HorizontalResolution = 150; - image.DebugSave(provider); - } + using Image image = provider.GetImage(); + image.Metadata.VerticalResolution = 150; + image.Metadata.HorizontalResolution = 150; + image.DebugSave(provider); } [Fact] @@ -60,11 +58,9 @@ namespace SixLabors.ImageSharp.Tests.Formats foreach (TestFile file in Files) { - using (Image image = file.CreateRgba32Image()) - { - string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); - File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); - } + using Image image = file.CreateRgba32Image(); + string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); + File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); } } @@ -75,10 +71,8 @@ namespace SixLabors.ImageSharp.Tests.Formats foreach (TestFile file in Files) { - using (Image image = file.CreateRgba32Image()) - { - image.Save(Path.Combine(path, file.FileName)); - } + using Image image = file.CreateRgba32Image(); + image.Save(Path.Combine(path, file.FileName)); } } @@ -120,42 +114,40 @@ namespace SixLabors.ImageSharp.Tests.Formats foreach (TestFile file in Files) { - using (Image image = file.CreateRgba32Image()) + using Image image = file.CreateRgba32Image(); + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) + { + image.SaveAsBmp(output); + } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) + { + image.SaveAsJpeg(output); + } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) + { + image.SaveAsPbm(output); + } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) + { + image.SaveAsPng(output); + } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) + { + image.SaveAsGif(output); + } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) { - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) - { - image.SaveAsBmp(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) - { - image.SaveAsJpeg(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) - { - image.SaveAsPbm(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) - { - image.SaveAsPng(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) - { - image.SaveAsGif(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) - { - image.SaveAsTga(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) - { - image.SaveAsTiff(output); - } + image.SaveAsTga(output); + } + + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) + { + image.SaveAsTiff(output); } } } @@ -176,10 +168,8 @@ namespace SixLabors.ImageSharp.Tests.Formats serialized = memoryStream.ToArray(); } - using (var image2 = Image.Load(serialized)) - { - image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}"); - } + using var image2 = Image.Load(serialized); + image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}"); } } @@ -213,39 +203,33 @@ namespace SixLabors.ImageSharp.Tests.Formats public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { - using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) - { - using (var memoryStream = new MemoryStream()) - { - IImageFormat format = GetFormat(extension); - image.Save(memoryStream, format); - memoryStream.Position = 0; + using var image = Image.LoadPixelData(new Rgba32[width * height], width, height); + using var memoryStream = new MemoryStream(); + IImageFormat format = GetFormat(extension); + image.Save(memoryStream, format); + memoryStream.Position = 0; - IImageInfo imageInfo = Image.Identify(memoryStream); + IImageInfo imageInfo = Image.Identify(memoryStream); - Assert.Equal(imageInfo.Width, width); - Assert.Equal(imageInfo.Height, height); - memoryStream.Position = 0; + Assert.Equal(imageInfo.Width, width); + Assert.Equal(imageInfo.Height, height); + memoryStream.Position = 0; - imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); + imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); - Assert.Equal(format, detectedFormat); - } - } + Assert.Equal(format, detectedFormat); } [Fact] public void IdentifyReturnsNullWithInvalidStream() { - var invalid = new byte[10]; + byte[] invalid = new byte[10]; - using (var memoryStream = new MemoryStream(invalid)) - { - IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); + using var memoryStream = new MemoryStream(invalid); + IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); - Assert.Null(imageInfo); - Assert.Null(format); - } + Assert.Null(imageInfo); + Assert.Null(format); } private static IImageFormat GetFormat(string format) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 8fbac4a9e9..7c0447a98e 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -4,7 +4,7 @@ using System; using System.IO; using Microsoft.DotNet.RemoteExecutor; - +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -35,11 +35,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Decode_VerifyAllFrames(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } [Fact] @@ -51,13 +49,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif fixed (byte* data = testFile.Bytes.AsSpan(0, length)) { - using (var stream = new UnmanagedMemoryStream(data, length)) - { - using (Image image = GifDecoder.Decode(Configuration.Default, stream, default)) - { - Assert.Equal((200, 200), (image.Width, image.Height)); - } - } + using var stream = new UnmanagedMemoryStream(data, length); + using Image image = GifDecoder.Decode(DecoderOptions.Default, stream, default); + Assert.Equal((200, 200), (image.Width, image.Height)); } } @@ -66,11 +60,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + using Image image = provider.GetImage(); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } [Theory] @@ -80,12 +72,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - Assert.Equal(expectedFrameCount, image.Frames.Count); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + using Image image = provider.GetImage(); + Assert.Equal(expectedFrameCount, image.Frames.Count); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } [Theory] @@ -93,10 +83,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void CanDecodeJustOneFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First })) - { - Assert.Equal(1, image.Frames.Count); - } + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(new GifDecoder(), options); + Assert.Equal(1, image.Frames.Count); } [Theory] @@ -104,10 +93,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void CanDecodeAllFrames(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All })) - { - Assert.True(image.Frames.Count > 1); - } + using Image image = provider.GetImage(new GifDecoder()); + Assert.True(image.Frames.Count > 1); } [Theory] @@ -118,10 +105,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void DetectPixelSize(string imagePath, int expectedPixelSize) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); - } + using var stream = new MemoryStream(testFile.Bytes, false); + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } [Theory] @@ -146,11 +131,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Decode_WithMaxDimensions_Works(TestImageProvider provider, int expectedWidth, int expectedHeight) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(GifDecoder)) - { - Assert.Equal(expectedWidth, image.Width); - Assert.Equal(expectedHeight, image.Height); - } + using Image image = provider.GetImage(GifDecoder); + Assert.Equal(expectedWidth, image.Width); + Assert.Equal(expectedHeight, image.Height); } [Fact] @@ -190,12 +173,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Issue405_BadApplicationExtensionBlockLength(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.DebugSave(provider); + using Image image = provider.GetImage(); + image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } // https://github.com/SixLabors/ImageSharp/issues/1668 @@ -204,12 +185,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Issue1668_InvalidColorIndex(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - image.DebugSave(provider); + using Image image = provider.GetImage(); + image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 6b6f4fbc21..23de14b638 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -58,51 +59,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { - var options = new GifDecoder - { - IgnoreMetadata = false - }; - var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = testFile.CreateRgba32Image(options)) - { - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal("ImageSharp", metadata.Comments[0]); - } + using Image image = testFile.CreateRgba32Image(new GifDecoder()); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal("ImageSharp", metadata.Comments[0]); } [Fact] public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() { - var options = new GifDecoder + DecoderOptions options = new() { - IgnoreMetadata = true + SkipMetadata = true }; var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image image = testFile.CreateRgba32Image(options)) - { - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(0, metadata.Comments.Count); - } + using Image image = testFile.CreateRgba32Image(new GifDecoder(), options); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(0, metadata.Comments.Count); } [Fact] public void Decode_CanDecodeLargeTextComment() { - var options = new GifDecoder(); var testFile = TestFile.Create(TestImages.Gif.LargeComment); - using (Image image = testFile.CreateRgba32Image(options)) - { - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } + using Image image = testFile.CreateRgba32Image(new GifDecoder()); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(2, metadata.Comments.Count); + Assert.Equal(new string('c', 349), metadata.Comments[0]); + Assert.Equal("ImageSharp", metadata.Comments[1]); } [Fact] @@ -111,20 +100,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var decoder = new GifDecoder(); var testFile = TestFile.Create(TestImages.Gif.LargeComment); - using (Image input = testFile.CreateRgba32Image(decoder)) - using (var memoryStream = new MemoryStream()) - { - input.Save(memoryStream, new GifEncoder()); - memoryStream.Position = 0; - - using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) - { - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } - } + using Image input = testFile.CreateRgba32Image(decoder); + using var memoryStream = new MemoryStream(); + input.Save(memoryStream, new GifEncoder()); + memoryStream.Position = 0; + + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream, default); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(2, metadata.Comments.Count); + Assert.Equal(new string('c', 349), metadata.Comments[0]); + Assert.Equal("ImageSharp", metadata.Comments[1]); } [Theory] @@ -132,15 +117,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream, default); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -148,17 +131,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream, default)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -166,13 +145,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream, default); - GifMetadata meta = image.Metadata.GetGifMetadata(); - Assert.Equal(repeatCount, meta.RepeatCount); - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); } [Theory] @@ -180,15 +157,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new GifDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream, default)) - { - GifMetadata meta = image.Metadata.GetGifMetadata(); - Assert.Equal(repeatCount, meta.RepeatCount); - } - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 74cda1f3a5..9046ca14db 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -80,17 +80,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream, default)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -98,15 +94,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream, default); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -114,13 +108,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Identify_VerifyQuality(string imagePath, int quality) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream, default); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); } [Theory] @@ -128,14 +120,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Decode_VerifyQuality(string imagePath, int quality) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - using (Image image = JpegDecoder.Decode(Configuration.Default, stream, default)) - { - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } - } + using var stream = new MemoryStream(testFile.Bytes, false); + using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream, default); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); } [Theory] @@ -150,12 +138,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream, default); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream, default); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); } [Theory] @@ -167,28 +153,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegColorType expectedColorType) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(JpegDecoder)) - { - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + using Image image = provider.GetImage(JpegDecoder); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); } private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) + using var stream = new MemoryStream(testFile.Bytes, false); + if (useIdentify) { - if (useIdentify) - { - IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default); - test(imageInfo); - } - else - { - using var img = decoder.Decode(Configuration.Default, stream, default); - test(img); - } + IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(DecoderOptions.Default, stream, default); + test(imageInfo); + } + else + { + using Image img = decoder.Decode(DecoderOptions.Default, stream, default); + test(img); } } @@ -248,23 +230,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(true)] public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) { - var decoder = new JpegDecoder { IgnoreMetadata = ignoreMetadata }; + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; // Snake.jpg has both Exif and ICC profiles defined: var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); - using (Image image = testFile.CreateRgba32Image(decoder)) + using Image image = testFile.CreateRgba32Image(JpegDecoder, options); + if (ignoreMetadata) { - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.ExifProfile); - Assert.NotNull(image.Metadata.IccProfile); - } + Assert.Null(image.Metadata.ExifProfile); + Assert.Null(image.Metadata.IccProfile); + } + else + { + Assert.NotNull(image.Metadata.ExifProfile); + Assert.NotNull(image.Metadata.IccProfile); } } @@ -314,7 +294,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Exception ex = Record.Exception(() => { using Image image = provider.GetImage(JpegDecoder); - var clone = image.Metadata.ExifProfile.DeepClone(); + ExifProfile clone = image.Metadata.ExifProfile.DeepClone(); }); Assert.Null(ex); } @@ -357,11 +337,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void EncodedStringTags_Read() { - using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings))) - { - ExifProfile exif = image.Metadata.ExifProfile; - VerifyEncodedStrings(exif); - } + using var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings)); + ExifProfile exif = image.Metadata.ExifProfile; + VerifyEncodedStrings(exif); } private static void VerifyEncodedStrings(ExifProfile exif) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 87179a0be0..1706f2e217 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -73,10 +74,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void ParseStream_BasicPropertiesAreCorrect() { + JpegDecoderOptions options = new(); byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; using var ms = new MemoryStream(bytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(options); using Image image = decoder.Decode(bufferedStream, cancellationToken: default); // I don't know why these numbers are different. All I know is that the decoder works @@ -148,17 +150,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var cts = new CancellationTokenSource(); string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); using var pausedStream = new PausedStream(file); - pausedStream.OnWaiting(s => + pausedStream.OnWaiting(_ => { cts.Cancel(); pausedStream.Release(); }); - var config = Configuration.CreateDefaultInstance(); - config.FileSystem = new SingleStreamFileSystem(pausedStream); + var configuration = Configuration.CreateDefaultInstance(); + configuration.FileSystem = new SingleStreamFileSystem(pausedStream); + DecoderOptions options = new() + { + Configuration = configuration + }; + await Assert.ThrowsAsync(async () => { - using Image image = await Image.LoadAsync(config, "someFakeFile", cts.Token); + using Image image = await Image.LoadAsync(options, "someFakeFile", cts.Token); }); } @@ -169,28 +176,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); using var pausedStream = new PausedStream(file); - pausedStream.OnWaiting(s => + pausedStream.OnWaiting(_ => { cts.Cancel(); pausedStream.Release(); }); - var config = Configuration.CreateDefaultInstance(); - config.FileSystem = new SingleStreamFileSystem(pausedStream); + var configuration = Configuration.CreateDefaultInstance(); + configuration.FileSystem = new SingleStreamFileSystem(pausedStream); + DecoderOptions options = new() + { + Configuration = configuration + }; - await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token)); + await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(options, "someFakeFile", cts.Token)); } [Theory] [WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)] public void ThrowsNotSupported_WithUnsupportedJpegs(TestImageProvider provider) where TPixel : unmanaged, IPixel - { - Assert.Throws(() => + => Assert.Throws(() => { using Image image = provider.GetImage(JpegDecoder); }); - } // https://github.com/SixLabors/ImageSharp/pull/1732 [Theory] @@ -198,11 +207,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Issue1732_DecodesWithRgbColorSpace(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } // https://github.com/SixLabors/ImageSharp/issues/2057 @@ -211,11 +218,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Issue2057_DecodeWorks(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } // https://github.com/SixLabors/ImageSharp/issues/2133 @@ -224,11 +229,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Issue2133_DeduceColorSpace(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } // https://github.com/SixLabors/ImageSharp/issues/2133 @@ -237,44 +240,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void Issue2136_DecodeWorks(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(JpegDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider); - } - } - - // DEBUG ONLY! - // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" - // into "\tests\Images\ActualOutput\JpegDecoderTests\" - // [Theory] - // [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] - public void ValidateProgressivePdfJsOutput( - TestImageProvider provider, - string pdfJsOriginalResultImage) - where TPixel : unmanaged, IPixel - { - // tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm - string pdfJsOriginalResultPath = Path.Combine( - provider.Utility.GetTestOutputDir(), - pdfJsOriginalResultImage); - - byte[] sourceBytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; - - provider.Utility.TestName = nameof(DecodeProgressiveJpegOutputName); - - var comparer = ImageComparer.Tolerant(0, 0); - - using (Image expectedImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) - using (var pdfJsOriginalResult = Image.Load(pdfJsOriginalResultPath)) - using (var pdfJsPortResult = Image.Load(sourceBytes, JpegDecoder)) - { - ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); - ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); - - this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}"); - this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}"); - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 9824315fff..2ecfe331eb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -49,8 +49,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // Calculating data from ImageSharp byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + JpegDecoderOptions option = new(); - using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(option); using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); @@ -78,8 +79,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Calculating data from ImageSharp byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + JpegDecoderOptions options = new(); - using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(options); using var ms = new MemoryStream(sourceBytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index af870ed442..f0e2d3ce11 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -18,12 +18,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class SpectralToPixelConversionTests { public static readonly string[] BaselineTestJpegs = - { - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - }; + { + TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output; @@ -40,8 +40,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); // Decoding + JpegDecoderOptions options = new(); using var converter = new SpectralConverter(Configuration.Default); - using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + using var decoder = new JpegDecoderCore(options); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); decoder.ParseStream(bufferedStream, converter, cancellationToken: default); @@ -50,17 +51,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; // Comparison - using (var image = new Image(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata())) - using (Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) - { - ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); + using var image = new Image(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()); + using Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false); + ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); - this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); - this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); + this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); + this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); - // ReSharper disable once PossibleInvalidOperationException - Assert.True(report.TotalNormalizedDifference.Value < 0.005f); - } + // ReSharper disable once PossibleInvalidOperationException + Assert.True(report.TotalNormalizedDifference.Value < 0.005f); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index ef74549d0c..ffb54fb0a9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // ReSharper disable once InconsistentNaming public static float[] Create8x8FloatData() { - var result = new float[64]; + float[] result = new float[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // ReSharper disable once InconsistentNaming public static int[] Create8x8IntData() { - var result = new int[64]; + int[] result = new int[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // ReSharper disable once InconsistentNaming public static short[] Create8x8ShortData() { - var result = new short[64]; + short[] result = new short[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) { var rnd = new Random(seed); - var result = new int[64]; + int[] result = new int[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) @@ -222,7 +222,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils using var ms = new MemoryStream(bytes); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); + JpegDecoderOptions options = new(); + var decoder = new JpegDecoderCore(options); if (metaDataOnly) { decoder.Identify(bufferedStream, cancellationToken: default); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index f17d8584c6..16627dd34f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -4,6 +4,7 @@ using System.Buffers.Binary; using System.IO; using System.Text; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -75,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); ImageFormatException exception = - Assert.Throws(() => decoder.Decode(Configuration.Default, memStream, default)); + Assert.Throws(() => decoder.Decode(DecoderOptions.Default, memStream, default)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } @@ -83,18 +84,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static string GetChunkTypeName(uint value) { - var data = new byte[4]; + byte[] data = new byte[4]; BinaryPrimitives.WriteUInt32BigEndian(data, value); return Encoding.ASCII.GetString(data); } - private static void WriteHeaderChunk(MemoryStream memStream) - { + private static void WriteHeaderChunk(MemoryStream memStream) => + // Writes a 1x1 32bit png header chunk containing a single black pixel. memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); - } private static void WriteChunk(MemoryStream memStream, string chunkName) { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 5593128ae7..a35f8037c5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -111,15 +111,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) where TPixel : unmanaged, IPixel - { - TestPngEncoderCore( + => TestPngEncoderCore( provider, pngColorType, PngFilterMethod.Adaptive, PngBitDepth.Bit8, PngInterlaceMode.None, appendPngColorType: true); - } [Theory] [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] @@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png return; } - foreach (var filterMethod in PngFilterMethods) + foreach (object[] filterMethod in PngFilterMethods) { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -235,7 +233,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void WorksWithAllBitDepthsAndExcludeAllFilter(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) where TPixel : unmanaged, IPixel { - foreach (var filterMethod in PngFilterMethods) + foreach (object[] filterMethod in PngFilterMethods) { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -284,20 +282,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void InfersColorTypeAndBitDepth(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) where TPixel : unmanaged, IPixel { - using (Stream stream = new MemoryStream()) - { - PngEncoder.Encode(provider.GetImage(), stream); + using Stream stream = new MemoryStream(); + PngEncoder.Encode(provider.GetImage(), stream); - stream.Seek(0, SeekOrigin.Begin); + stream.Seek(0, SeekOrigin.Begin); - var decoder = new PngDecoder(); + var decoder = new PngDecoder(); - Image image = decoder.Decode(Configuration.Default, stream, default); + Image image = decoder.Decode(DecoderOptions.Default, stream, default); - PngMetadata metadata = image.Metadata.GetPngMetadata(); - Assert.Equal(pngColorType, metadata.ColorType); - Assert.Equal(pngBitDepth, metadata.BitDepth); - } + PngMetadata metadata = image.Metadata.GetPngMetadata(); + Assert.Equal(pngColorType, metadata.ColorType); + Assert.Equal(pngBitDepth, metadata.BitDepth); } [Theory] @@ -329,14 +325,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void WritesFileMarker(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - using (var ms = new MemoryStream()) - { - image.Save(ms, PngEncoder); + using Image image = provider.GetImage(); + using var ms = new MemoryStream(); + image.Save(ms, PngEncoder); - byte[] data = ms.ToArray().Take(8).ToArray(); - byte[] expected = - { + byte[] data = ms.ToArray().Take(8).ToArray(); + byte[] expected = + { 0x89, // Set the high bit. 0x50, // P 0x4E, // N @@ -347,8 +342,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 0x0A // LF }; - Assert.Equal(expected, data); - } + Assert.Equal(expected, data); } [Theory] @@ -356,22 +350,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png 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, PngEncoder); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); - 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); - } - } - } + 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] @@ -379,21 +367,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) { var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, PngEncoder); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - PngMetadata meta = output.Metadata.GetPngMetadata(); + memStream.Position = 0; + using var output = Image.Load(memStream); + PngMetadata meta = output.Metadata.GetPngMetadata(); - Assert.Equal(pngBitDepth, meta.BitDepth); - } - } - } + Assert.Equal(pngBitDepth, meta.BitDepth); } [Theory] @@ -437,9 +419,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png memStream.Position = 0; using var actual = Image.Load(memStream); Rgba32 expectedColor = Color.Blue; - if (colorType == PngColorType.Grayscale || colorType == PngColorType.GrayscaleWithAlpha) + if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha) { - var luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B); + byte luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B); expectedColor = new Rgba32(luminance, luminance, luminance); } @@ -467,51 +449,45 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) { var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using Image input = testFile.CreateRgba32Image(); + PngMetadata inMeta = input.Metadata.GetPngMetadata(); + Assert.True(inMeta.HasTransparency); + + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + memStream.Position = 0; + using var output = Image.Load(memStream); + PngMetadata outMeta = output.Metadata.GetPngMetadata(); + Assert.True(outMeta.HasTransparency); + + switch (pngColorType) { - PngMetadata inMeta = input.Metadata.GetPngMetadata(); - Assert.True(inMeta.HasTransparency); + case PngColorType.Grayscale: + if (pngBitDepth.Equals(PngBitDepth.Bit16)) + { + Assert.True(outMeta.TransparentL16.HasValue); + Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16); + } + else + { + Assert.True(outMeta.TransparentL8.HasValue); + Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8); + } - using (var memStream = new MemoryStream()) - { - input.Save(memStream, PngEncoder); - memStream.Position = 0; - using (var output = Image.Load(memStream)) + break; + case PngColorType.Rgb: + if (pngBitDepth.Equals(PngBitDepth.Bit16)) { - PngMetadata outMeta = output.Metadata.GetPngMetadata(); - Assert.True(outMeta.HasTransparency); - - switch (pngColorType) - { - case PngColorType.Grayscale: - if (pngBitDepth.Equals(PngBitDepth.Bit16)) - { - Assert.True(outMeta.TransparentL16.HasValue); - Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16); - } - else - { - Assert.True(outMeta.TransparentL8.HasValue); - Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8); - } - - break; - case PngColorType.Rgb: - if (pngBitDepth.Equals(PngBitDepth.Bit16)) - { - Assert.True(outMeta.TransparentRgb48.HasValue); - Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48); - } - else - { - Assert.True(outMeta.TransparentRgb24.HasValue); - Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24); - } - - break; - } + Assert.True(outMeta.TransparentRgb48.HasValue); + Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48); } - } + else + { + Assert.True(outMeta.TransparentRgb24.HasValue); + Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24); + } + + break; } } @@ -591,42 +567,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png PngChunkFilter optimizeMethod = PngChunkFilter.None) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + var encoder = new PngEncoder { - var encoder = new PngEncoder - { - ColorType = pngColorType, - FilterMethod = pngFilterMethod, - CompressionLevel = compressionLevel, - BitDepth = bitDepth, - Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), - InterlaceMethod = interlaceMode, - ChunkFilter = optimizeMethod, - }; + ColorType = pngColorType, + FilterMethod = pngFilterMethod, + CompressionLevel = compressionLevel, + BitDepth = bitDepth, + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), + InterlaceMethod = interlaceMode, + ChunkFilter = optimizeMethod, + }; - string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; - string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; - string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; - string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; - string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; - string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty; + string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; + string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; + string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; + string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; + string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; + string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty; - string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}{pngInterlaceModeInfo}"; + string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}{pngInterlaceModeInfo}"; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - // Compare to the Magick reference decoder. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + // Compare to the Magick reference decoder. + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - // We compare using both our decoder and the reference decoder as pixel transformation - // occurs within the encoder itself leaving the input image unaffected. - // This means we are benefiting from testing our decoder also. - using (var imageSharpImage = Image.Load(actualOutputFile, new PngDecoder())) - using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) - { - ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); - } - } + // We compare using both our decoder and the reference decoder as pixel transformation + // occurs within the encoder itself leaving the input image unaffected. + // This means we are benefiting from testing our decoder also. + using FileStream fileStream = File.OpenRead(actualOutputFile); + using Image imageSharpImage = new PngDecoder().Decode(DecoderOptions.Default, fileStream, default); + + fileStream.Position = 0; + + using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, fileStream, default); + ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 1c89e72ac0..fe74c0b8ca 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -56,11 +57,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_CanReadTextData(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } + using Image image = provider.GetImage(new PngDecoder()); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); } [Theory] @@ -69,18 +68,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png where TPixel : unmanaged, IPixel { var decoder = new PngDecoder(); - using (Image input = provider.GetImage(decoder)) - using (var memoryStream = new MemoryStream()) - { - input.Save(memoryStream, new PngEncoder()); - - memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - } + using Image input = provider.GetImage(decoder); + using var memoryStream = new MemoryStream(); + input.Save(memoryStream, new PngEncoder()); + + memoryStream.Position = 0; + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream, default); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); } [Theory] @@ -88,16 +83,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "empty"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "too large"); - } + using Image image = provider.GetImage(new PngDecoder()); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "empty"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "too large"); } [Theory] @@ -106,30 +99,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png where TPixel : unmanaged, IPixel { var decoder = new PngDecoder(); - using (Image input = provider.GetImage(decoder)) - using (var memoryStream = new MemoryStream()) + using Image input = provider.GetImage(decoder); + using var memoryStream = new MemoryStream(); + + // This will be a zTXt chunk. + var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); + + // This will be a iTXt chunk. + var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); + PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); + inputMetadata.TextData.Add(expectedText); + inputMetadata.TextData.Add(expectedTextNoneLatin); + input.Save(memoryStream, new PngEncoder { - // This will be a zTXt chunk. - var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); - - // This will be a iTXt chunk. - var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); - PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); - inputMetadata.TextData.Add(expectedText); - inputMetadata.TextData.Add(expectedTextNoneLatin); - input.Save(memoryStream, new PngEncoder - { - TextCompressionThreshold = 50 - }); - - memoryStream.Position = 0; - using (Image image = decoder.Decode(Configuration.Default, memoryStream, default)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Contains(meta.TextData, m => m.Equals(expectedText)); - Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); - } - } + TextCompressionThreshold = 50 + }); + + memoryStream.Position = 0; + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream, default); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.Contains(meta.TextData, m => m.Equals(expectedText)); + Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); } [Theory] @@ -137,17 +127,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_ReadsExifData(TestImageProvider provider) where TPixel : unmanaged, IPixel { - var decoder = new PngDecoder + DecoderOptions options = new() { - IgnoreMetadata = false + SkipMetadata = false }; - using (Image image = provider.GetImage(decoder)) - { - Assert.NotNull(image.Metadata.ExifProfile); - ExifProfile exif = image.Metadata.ExifProfile; - VerifyExifDataIsPresent(exif); - } + using Image image = provider.GetImage(new PngDecoder(), options); + Assert.NotNull(image.Metadata.ExifProfile); + ExifProfile exif = image.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); } [Theory] @@ -155,53 +143,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider) where TPixel : unmanaged, IPixel { - var decoder = new PngDecoder + DecoderOptions options = new() { - IgnoreMetadata = true + SkipMetadata = true }; - using (Image image = provider.GetImage(decoder)) - { - Assert.Null(image.Metadata.ExifProfile); - } + PngDecoder decoder = new(); + + using Image image = provider.GetImage(decoder, options); + Assert.Null(image.Metadata.ExifProfile); } [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() { - var options = new PngDecoder + DecoderOptions options = new() { - IgnoreMetadata = false + SkipMetadata = false }; var testFile = TestFile.Create(TestImages.Png.Blur); - using (Image image = testFile.CreateRgba32Image(options)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + using Image image = testFile.CreateRgba32Image(new PngDecoder(), options); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Equal(1, meta.TextData.Count); - Assert.Equal("Software", meta.TextData[0].Keyword); - Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); - Assert.Equal(0.4545d, meta.Gamma, precision: 4); - } + Assert.Equal(1, meta.TextData.Count); + Assert.Equal("Software", meta.TextData[0].Keyword); + Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); + Assert.Equal(0.4545d, meta.Gamma, precision: 4); } [Fact] public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - var options = new PngDecoder + DecoderOptions options = new() { - IgnoreMetadata = true + SkipMetadata = true }; var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using (Image image = testFile.CreateRgba32Image(options)) - { - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Equal(0, meta.TextData.Count); - } + using Image image = testFile.CreateRgba32Image(new PngDecoder(), options); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.Equal(0, meta.TextData.Count); } [Theory] @@ -209,17 +193,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new PngDecoder(); - using (Image image = decoder.Decode(Configuration.Default, stream, default)) - { - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new PngDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -227,26 +207,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Encode_PreservesColorProfile(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image input = provider.GetImage(new PngDecoder())) - { - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using (var memStream = new MemoryStream()) - { - input.Save(memStream, new PngEncoder()); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } - } - } + using Image input = provider.GetImage(new PngDecoder()); + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); + + using var memStream = new MemoryStream(); + input.Save(memStream, new PngEncoder()); + + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); + + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); } [Theory] @@ -254,15 +228,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - var decoder = new PngDecoder(); - IImageInfo image = decoder.Identify(Configuration.Default, stream, default); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new PngDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } [Theory] @@ -270,13 +242,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Identify_ReadsTextData(string imagePath) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); } [Theory] @@ -284,14 +254,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Identify_ReadsExifData(string imagePath) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.Metadata.ExifProfile); - ExifProfile exif = imageInfo.Metadata.ExifProfile; - VerifyExifDataIsPresent(exif); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.Metadata.ExifProfile); + ExifProfile exif = imageInfo.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); } private static void VerifyExifDataIsPresent(ExifProfile exif) @@ -323,28 +291,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Identify_ReadsLegacyExifData(string imagePath) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.Metadata.ExifProfile); - - PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)); - - ExifProfile exif = imageInfo.Metadata.ExifProfile; - Assert.Equal(0, exif.InvalidTags.Count); - Assert.Equal(3, exif.Values.Count); - - Assert.Equal( - "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", - exif.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal( - "Duplicated from basn3p02.png, then image metadata modified with exiv2", - exif.GetValue(ExifTag.ImageHistory).Value); - - Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.Metadata.ExifProfile); + + PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)); + + ExifProfile exif = imageInfo.Metadata.ExifProfile; + Assert.Equal(0, exif.InvalidTags.Count); + Assert.Equal(3, exif.Values.Count); + + Assert.Equal( + "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", + exif.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal( + "Duplicated from basn3p02.png, then image metadata modified with exiv2", + exif.GetValue(ExifTag.ImageHistory).Value); + + Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 60d4be4228..934b57b033 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -19,19 +20,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png where TPixel : unmanaged, IPixel { // does saving a file then reopening mean both files are identical??? - using (Image image = provider.GetImage()) - using (var ms = new MemoryStream()) - { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder()); - ms.Position = 0; - using (var img2 = Image.Load(ms, new PngDecoder())) - { - ImageComparer.Tolerant().VerifySimilarity(image, img2); + using Image image = provider.GetImage(); + using var ms = new MemoryStream(); - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - } - } + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms, default); + ImageComparer.Tolerant().VerifySimilarity(image, img2); + + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); } /* JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the @@ -104,20 +102,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png where TPixel : unmanaged, IPixel { // does saving a file then reopening mean both files are identical??? - using (Image image = provider.GetImage()) - using (var ms = new MemoryStream()) - { - // image.Save(provider.Utility.GetTestOutputFileName("png")); - image.Mutate(x => x.Resize(100, 100)); + using Image image = provider.GetImage(); + using var ms = new MemoryStream(); - // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); - image.Save(ms, new PngEncoder()); - ms.Position = 0; - using (var img2 = Image.Load(ms, new PngDecoder())) - { - ImageComparer.Tolerant().VerifySimilarity(image, img2); - } - } + // image.Save(provider.Utility.GetTestOutputFileName("png")); + image.Mutate(x => x.Resize(100, 100)); + + // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms, default); + ImageComparer.Tolerant().VerifySimilarity(image, img2); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs index eb1e42ad06..12e2926bd7 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga Assert.Throws(() => { - using (Image.Load(Configuration.Default, stream, out IImageFormat _)) + using (Image.Load(DecoderOptions.Default, stream, out IImageFormat _)) { } }); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 12e0b819d4..eb719bcb1a 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -4,6 +4,7 @@ // ReSharper disable InconsistentNaming using System; using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -635,10 +636,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void CanDecodeJustOneFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TiffDecoder() { DecodingMode = FrameDecodingMode.First })) - { - Assert.Equal(1, image.Frames.Count); - } + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(new TiffDecoder(), options); + Assert.Equal(1, image.Frames.Count); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 38fac249c9..c30a7b6c6f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata; @@ -50,24 +51,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(TiffDecoder)) - { - TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); - VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); + using Image image = provider.GetImage(TiffDecoder); + TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); + var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); + VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); - var clone = (TiffFrameMetadata)meta.DeepClone(); + var clone = (TiffFrameMetadata)meta.DeepClone(); - clone.BitsPerPixel = TiffBitsPerPixel.Bit8; - clone.Compression = TiffCompression.None; - clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; - clone.Predictor = TiffPredictor.Horizontal; + clone.BitsPerPixel = TiffBitsPerPixel.Bit8; + clone.Compression = TiffCompression.None; + clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; + clone.Predictor = TiffPredictor.Horizontal; - Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); - Assert.False(meta.Compression == clone.Compression); - Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); - Assert.False(meta.Predictor == clone.Predictor); - } + Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); + Assert.False(meta.Compression == clone.Compression); + Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); + Assert.False(meta.Predictor == clone.Predictor); } private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) @@ -119,23 +118,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void MetadataProfiles(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = ignoreMetadata })) + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(new TiffDecoder(), options); + TiffMetadata meta = image.Metadata.GetTiffMetadata(); + ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; + + Assert.NotNull(meta); + if (ignoreMetadata) + { + Assert.Null(rootFrameMetaData.XmpProfile); + Assert.Null(rootFrameMetaData.ExifProfile); + } + else { - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; - Assert.NotNull(meta); - if (ignoreMetadata) - { - Assert.Null(rootFrameMetaData.XmpProfile); - Assert.Null(rootFrameMetaData.ExifProfile); - } - else - { - Assert.NotNull(rootFrameMetaData.XmpProfile); - Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); - Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); - } + Assert.NotNull(rootFrameMetaData.XmpProfile); + Assert.NotNull(rootFrameMetaData.ExifProfile); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); + Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -158,63 +157,61 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void BaselineTags(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(TiffDecoder)) - { - ImageFrame rootFrame = image.Frames.RootFrame; - Assert.Equal(32, rootFrame.Width); - Assert.Equal(32, rootFrame.Height); - Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); - - ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; - TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); - Assert.NotNull(exifProfile); - - // The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData - // and removed from the profile on decode. - Assert.Equal(26, exifProfile.Values.Count); - Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); - Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value); - Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value); - Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); - Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); - Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); - Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); - Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); - var expectedResolution = new Rational(10000, 1000, simplify: false); - Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); - Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); - Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); - Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); - Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); - Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); - Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); - Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); - ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; - Assert.NotNull(colorMap); - Assert.Equal(48, colorMap.Length); - Assert.Equal(10537, colorMap[0]); - Assert.Equal(14392, colorMap[1]); - Assert.Equal(58596, colorMap[46]); - Assert.Equal(3855, colorMap[47]); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); - Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); - - ImageMetadata imageMetaData = image.Metadata; - Assert.NotNull(imageMetaData); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); - Assert.Equal(10, imageMetaData.HorizontalResolution); - Assert.Equal(10, imageMetaData.VerticalResolution); - - TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetaData); - Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); - } + using Image image = provider.GetImage(TiffDecoder); + ImageFrame rootFrame = image.Frames.RootFrame; + Assert.Equal(32, rootFrame.Width); + Assert.Equal(32, rootFrame.Height); + Assert.NotNull(rootFrame.Metadata.XmpProfile); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); + + ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; + TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); + Assert.NotNull(exifProfile); + + // The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData + // and removed from the profile on decode. + Assert.Equal(26, exifProfile.Values.Count); + Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); + Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value); + Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value); + Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); + Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); + Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); + Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); + Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); + Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); + var expectedResolution = new Rational(10000, 1000, simplify: false); + Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); + Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); + Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); + Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); + Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); + Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); + Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); + Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); + ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + Assert.NotNull(colorMap); + Assert.Equal(48, colorMap.Length); + Assert.Equal(10537, colorMap[0]); + Assert.Equal(14392, colorMap[1]); + Assert.Equal(58596, colorMap[46]); + Assert.Equal(3855, colorMap[47]); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); + Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); + + ImageMetadata imageMetaData = image.Metadata; + Assert.NotNull(imageMetaData); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); + Assert.Equal(10, imageMetaData.HorizontalResolution); + Assert.Equal(10, imageMetaData.VerticalResolution); + + TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetaData); + Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); } [Theory] @@ -222,23 +219,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void SubfileType(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(TiffDecoder)) - { - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - Assert.NotNull(meta); + using Image image = provider.GetImage(TiffDecoder); + TiffMetadata meta = image.Metadata.GetTiffMetadata(); + Assert.NotNull(meta); - Assert.Equal(2, image.Frames.Count); + Assert.Equal(2, image.Frames.Count); - ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile; - Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value); - Assert.Equal(255, image.Frames[0].Width); - Assert.Equal(255, image.Frames[0].Height); + ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile; + Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value); + Assert.Equal(255, image.Frames[0].Width); + Assert.Equal(255, image.Frames[0].Height); - ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile; - Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value); - Assert.Equal(255, image.Frames[1].Width); - Assert.Equal(255, image.Frames[1].Height); - } + ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile; + Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value); + Assert.Equal(255, image.Frames[1].Width); + Assert.Equal(255, image.Frames[1].Height); } [Theory] @@ -247,7 +242,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel { // Load Tiff image - using Image image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = false }); + DecoderOptions options = new() { SkipMetadata = false }; + using Image image = provider.GetImage(new TiffDecoder(), options); ImageMetadata inputMetaData = image.Metadata; ImageFrame rootFrameInput = image.Frames.RootFrame; diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index f9b949e214..bbf35e51b5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.PixelFormats; #if SUPPORTS_RUNTIME_INTRINSICS @@ -104,7 +103,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.Equal(expectedData, transformData); } - // Test image: Input\Png\Bike.png private static void RunColorSpaceTransformTestWithBikeImage() { // arrange @@ -119,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp // Convert image pixels to bgra array. byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall)); - using var image = Image.Load(imgBytes, new WebpDecoder()); + using var image = Image.Load(imgBytes); uint[] bgra = ToBgra(image); int colorTransformBits = 4; diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index fe5520cc6f..90587e36c5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -2,8 +2,8 @@ // Licensed under the Six Labors Split License. using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -44,14 +44,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); - } + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); } [Theory] @@ -67,11 +65,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithoutFilter(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -83,11 +79,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -106,11 +100,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -121,11 +113,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_VerySmall(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -140,11 +130,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithPartitions(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -154,11 +142,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithSegmentation(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -171,11 +157,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -196,11 +180,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -208,11 +190,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -221,11 +201,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -240,11 +218,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -256,11 +232,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -269,11 +243,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -282,11 +254,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -306,11 +276,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -325,11 +293,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -337,18 +303,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void Decode_AnimatedLossless_VerifyAllFrames(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); - - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - - Assert.Equal(0, webpMetaData.AnimationLoopCount); - Assert.Equal(150U, frameMetaData.FrameDuration); - Assert.Equal(12, image.Frames.Count); - } + using Image image = provider.GetImage(WebpDecoder); + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); + + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + + Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(150U, frameMetaData.FrameDuration); + Assert.Equal(12, image.Frames.Count); } [Theory] @@ -356,18 +320,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); - - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); - - Assert.Equal(0, webpMetaData.AnimationLoopCount); - Assert.Equal(150U, frameMetaData.FrameDuration); - Assert.Equal(12, image.Frames.Count); - } + using Image image = provider.GetImage(WebpDecoder); + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); + + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); + + Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(150U, frameMetaData.FrameDuration); + Assert.Equal(12, image.Frames.Count); } [Theory] @@ -375,10 +337,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new WebpDecoder() { DecodingMode = FrameDecodingMode.First })) - { - Assert.Equal(1, image.Frames.Count); - } + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(new WebpDecoder(), options); + Assert.Equal(1, image.Frames.Count); } [Theory] @@ -389,10 +350,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp where TPixel : unmanaged, IPixel { // Just make sure no exception is thrown. The reference decoder fails to load the image. - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); } // https://github.com/SixLabors/ImageSharp/issues/1594 @@ -401,11 +360,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void WebpDecoder_CanDecode_Issue1594(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Theory] @@ -424,41 +381,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static void RunDecodeLossyWithHorizontalFilter() { var provider = TestImageProvider.File(TestImageLossyHorizontalFilterPath); - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } private static void RunDecodeLossyWithVerticalFilter() { var provider = TestImageProvider.File(TestImageLossyVerticalFilterPath); - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } private static void RunDecodeLossyWithSimpleFilterTest() { var provider = TestImageProvider.File(TestImageLossySimpleFilterPath); - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } private static void RunDecodeLossyWithComplexFilterTest() { var provider = TestImageProvider.File(TestImageLossyComplexFilterPath); - using (Image image = provider.GetImage(WebpDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 05d0d6c5a8..1288538723 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -15,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpMetaDataTests { - private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false }; + private static WebpDecoder WebpDecoder => new(); [Theory] [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, false)] @@ -23,9 +24,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLossyImage(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; - - using Image image = provider.GetImage(decoder); + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(WebpDecoder, options); if (ignoreMetadata) { Assert.Null(image.Metadata.ExifProfile); @@ -45,9 +45,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLosslessImage(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; - - using Image image = provider.GetImage(decoder); + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(WebpDecoder, options); if (ignoreMetadata) { Assert.Null(image.Metadata.ExifProfile); @@ -71,9 +70,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; - - using Image image = provider.GetImage(decoder); + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(WebpDecoder, options); if (ignoreMetadata) { Assert.Null(image.Metadata.IccProfile); @@ -91,9 +89,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { - var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; - - using Image image = await provider.GetImageAsync(decoder); + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = await provider.GetImageAsync(WebpDecoder, options); if (ignoreMetadata) { Assert.Null(image.Metadata.XmpProfile); @@ -178,29 +175,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void Encode_PreservesColorProfile(TestImageProvider provider, WebpFileFormatType fileFormat) where TPixel : unmanaged, IPixel { - using (Image input = provider.GetImage(new WebpDecoder())) + using Image input = provider.GetImage(WebpDecoder); + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); + + using var memStream = new MemoryStream(); + input.Save(memStream, new WebpEncoder() { - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using (var memStream = new MemoryStream()) - { - input.Save(memStream, new WebpEncoder() - { - FileFormat = fileFormat - }); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } - } - } + FileFormat = fileFormat + }); + + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); + + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); } [Theory] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs index c9be2a74e1..d1848281f7 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -15,101 +15,138 @@ namespace SixLabors.ImageSharp.Tests public class Decode_Cancellation : ImageLoadTestBase { private bool isTestStreamSeekable; - private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); - private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); - private readonly CancellationTokenSource cts = new CancellationTokenSource(); + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new(0); + private readonly SemaphoreSlim continueSemaphore = new(0); + private readonly CancellationTokenSource cts = new(); - public Decode_Cancellation() - { - this.TopLevelConfiguration.StreamProcessingBufferSize = 128; - } + public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128; [Theory] [InlineData(false)] [InlineData(true)] - public async Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) + public Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.DataStream, this.cts.Token)); } [Theory] [InlineData(false)] [InlineData(true)] - public async Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) + public Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.DataStream, this.cts.Token)); } [Fact] - public async Task LoadAsync_Agnostic_Path() + public Task LoadAsync_Agnostic_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token)); } [Fact] - public async Task LoadAsync_Specific_Path() + public Task LoadAsync_Specific_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.LoadAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token)); } [Theory] [InlineData(false)] [InlineData(true)] - public async Task IdentifyAsync_Stream(bool isInputStreamSeekable) + public Task IdentifyAsync_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.IdentifyAsync(options, this.DataStream, this.cts.Token)); } [Fact] - public async Task IdentifyAsync_CustomConfiguration_Path() + public Task IdentifyAsync_CustomConfiguration_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.IdentifyAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.IdentifyAsync(options, this.MockFilePath, this.cts.Token)); } [Theory] [InlineData(false)] [InlineData(true)] - public async Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) + public Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) { this.isTestStreamSeekable = isInputStreamSeekable; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.DataStream, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(options, this.DataStream, this.cts.Token)); } [Fact] - public async Task IdentifyWithFormatAsync_CustomConfiguration_Path() + public Task IdentifyWithFormatAsync_CustomConfiguration_Path() { this.isTestStreamSeekable = true; _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.TopLevelConfiguration, this.MockFilePath, this.cts.Token)); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(options, this.MockFilePath, this.cts.Token)); } [Fact] - public async Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() + public Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() { _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - await Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); + return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); } private async Task DoCancel() diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index 446d36c062..dab6000f3c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -29,26 +29,23 @@ namespace SixLabors.ImageSharp.Tests private static readonly IImageFormat ExpectedGlobalFormat = Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void FromBytes_GlobalConfiguration(bool useSpan) + [Fact] + public void FromBytes_GlobalConfiguration() { - IImageFormat type = useSpan - ? Image.DetectFormat(this.ActualImageSpan) - : Image.DetectFormat(this.ActualImageBytes); + IImageFormat type = Image.DetectFormat(this.ActualImageSpan); Assert.Equal(ExpectedGlobalFormat, type); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void FromBytes_CustomConfiguration(bool useSpan) + [Fact] + public void FromBytes_CustomConfiguration() { - IImageFormat type = useSpan - ? Image.DetectFormat(this.LocalConfiguration, this.ByteArray.AsSpan()) - : Image.DetectFormat(this.LocalConfiguration, this.ByteArray); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageFormat type = Image.DetectFormat(options, this.ByteArray); Assert.Equal(this.LocalImageFormat, type); } @@ -63,7 +60,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromFileSystemPath_CustomConfiguration() { - IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.MockFilePath); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageFormat type = Image.DetectFormat(options, this.MockFilePath); Assert.Equal(this.LocalImageFormat, type); } @@ -80,14 +82,24 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromStream_CustomConfiguration() { - IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageFormat type = Image.DetectFormat(options, this.DataStream); Assert.Equal(this.LocalImageFormat, type); } [Fact] public void WhenNoMatchingFormatFound_ReturnsNull() { - IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream); + DecoderOptions options = new() + { + Configuration = new() + }; + + IImageFormat type = Image.DetectFormat(options, this.DataStream); Assert.Null(type); } @@ -104,14 +116,24 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromStreamAsync_CustomConfiguration() { - IImageFormat type = await Image.DetectFormatAsync(this.LocalConfiguration, new AsyncStreamWrapper(this.DataStream, () => false)); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); Assert.Equal(this.LocalImageFormat, type); } [Fact] public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() { - IImageFormat type = await Image.DetectFormatAsync(new Configuration(), new AsyncStreamWrapper(this.DataStream, () => false)); + DecoderOptions options = new() + { + Configuration = new() + }; + + IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); Assert.Null(type); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 5a50ad96e1..e7f5b52ac5 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests { private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); - private static readonly Size ExpectedImageSize = new Size(108, 202); + private static readonly Size ExpectedImageSize = new(108, 202); private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; @@ -43,7 +43,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromBytes_CustomConfiguration() { - IImageInfo info = Image.Identify(this.LocalConfiguration, this.ByteArray, out IImageFormat type); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type); Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageFormat, type); @@ -61,7 +66,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromFileSystemPath_CustomConfiguration() { - IImageInfo info = Image.Identify(this.LocalConfiguration, this.MockFilePath, out IImageFormat type); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type); Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageFormat, type); @@ -70,24 +80,20 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromStream_GlobalConfiguration() { - using (var stream = new MemoryStream(ActualImageBytes)) - { - IImageInfo info = Image.Identify(stream, out IImageFormat type); + using var stream = new MemoryStream(ActualImageBytes); + IImageInfo info = Image.Identify(stream, out IImageFormat type); - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, type); - } + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); } [Fact] public void FromStream_GlobalConfiguration_NoFormat() { - using (var stream = new MemoryStream(ActualImageBytes)) - { - IImageInfo info = Image.Identify(stream); + using var stream = new MemoryStream(ActualImageBytes); + IImageInfo info = Image.Identify(stream); - Assert.NotNull(info); - } + Assert.NotNull(info); } [Fact] @@ -116,7 +122,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromStream_CustomConfiguration() { - IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream, out IImageFormat type); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); Assert.Equal(this.LocalImageInfo, info); Assert.Equal(this.LocalImageFormat, type); @@ -125,7 +136,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FromStream_CustomConfiguration_NoFormat() { - IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageInfo info = Image.Identify(options, this.DataStream); Assert.Equal(this.LocalImageInfo, info); } @@ -133,7 +149,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WhenNoMatchingFormatFound_ReturnsNull() { - IImageInfo info = Image.Identify(new Configuration(), this.DataStream, out IImageFormat type); + DecoderOptions options = new() + { + Configuration = new() + }; + + IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); Assert.Null(info); Assert.Null(type); @@ -168,26 +189,22 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromStreamAsync_GlobalConfiguration_NoFormat() { - using (var stream = new MemoryStream(ActualImageBytes)) - { - var asyncStream = new AsyncStreamWrapper(stream, () => false); - IImageInfo info = await Image.IdentifyAsync(asyncStream); + using var stream = new MemoryStream(ActualImageBytes); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + IImageInfo info = await Image.IdentifyAsync(asyncStream); - Assert.NotNull(info); - } + Assert.NotNull(info); } [Fact] public async Task FromStreamAsync_GlobalConfiguration() { - using (var stream = new MemoryStream(ActualImageBytes)) - { - var asyncStream = new AsyncStreamWrapper(stream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); + using var stream = new MemoryStream(ActualImageBytes); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); - Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); - Assert.Equal(ExpectedGlobalFormat, res.Format); - } + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); } [Fact] @@ -244,14 +261,24 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromPathAsync_CustomConfiguration() { - IImageInfo info = await Image.IdentifyAsync(this.LocalConfiguration, this.MockFilePath); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); Assert.Equal(this.LocalImageInfo, info); } [Fact] public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() { - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, this.MockFilePath); + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, this.MockFilePath); Assert.NotNull(info.ImageInfo); Assert.Equal(this.LocalImageFormat, info.Format); } @@ -276,8 +303,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task FromStreamAsync_CustomConfiguration() { + DecoderOptions options = new() + { + Configuration = this.LocalConfiguration + }; + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(this.LocalConfiguration, asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); Assert.Equal(this.LocalImageInfo, info.ImageInfo); Assert.Equal(this.LocalImageFormat, info.Format); @@ -286,8 +318,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() { + DecoderOptions options = new() + { + Configuration = new() + }; + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(new Configuration(), asyncStream); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); Assert.Null(info.ImageInfo); } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 134387ae6b..1cfc744d00 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -63,28 +63,27 @@ namespace SixLabors.ImageSharp.Tests this.localImageFormatMock = new Mock(); var detector = new Mock(); - detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(this.localImageInfoMock.Object); this.localDecoder = detector.As(); - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((c, s, ct) => + this.localDecoder + .Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((c, s, ct) => { - using (var ms = new MemoryStream()) - { - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - } + using var ms = new MemoryStream(); + s.CopyTo(ms); + this.DecodedData = ms.ToArray(); }) .Returns(this.localStreamReturnImageRgba32); - this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((c, s, ct) => + this.localDecoder + .Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((c, s, ct) => { - using (var ms = new MemoryStream()) - { - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - } + using var ms = new MemoryStream(); + s.CopyTo(ms); + this.DecodedData = ms.ToArray(); }) .Returns(this.localStreamReturnImageAgnostic); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index 8546b73a33..df68eda0fa 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -16,7 +16,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Path_Specific() { - var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.MockFilePath); Assert.NotNull(img); Assert.Equal(this.TestFormat.Sample(), img); @@ -27,7 +32,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Path_Agnostic() { - var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.MockFilePath); Assert.NotNull(img); Assert.Equal(this.TestFormat.SampleAgnostic(), img); @@ -35,28 +45,15 @@ namespace SixLabors.ImageSharp.Tests this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); } - [Fact] - public void Configuration_Path_Decoder_Specific() - { - var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); - - Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default)); - } - - [Fact] - public void Configuration_Path_Decoder_Agnostic() - { - var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, this.localDecoder.Object); - - Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, this.DataStream, default)); - } - [Fact] public void Configuration_Path_OutFormat_Specific() { - var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, out IImageFormat format); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.MockFilePath, out IImageFormat format); Assert.NotNull(img); Assert.Equal(this.TestFormat, format); @@ -67,7 +64,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Path_OutFormat_Agnostic() { - var img = Image.Load(this.TopLevelConfiguration, this.MockFilePath, out IImageFormat format); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.MockFilePath, out IImageFormat format); Assert.NotNull(img); Assert.Equal(this.TestFormat, format); @@ -77,23 +79,28 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WhenFileNotFound_Throws() - { - Assert.Throws( + => Assert.Throws( () => { - Image.Load(this.TopLevelConfiguration, Guid.NewGuid().ToString()); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + Image.Load(options, Guid.NewGuid().ToString()); }); - } [Fact] public void WhenPathIsNull_Throws() - { - Assert.Throws( + => Assert.Throws( () => { - Image.Load(this.TopLevelConfiguration, (string)null); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + Image.Load(options, (string)null); }); - } } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs index 0b0cf9f712..1ce8fd4eba 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System; +using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; @@ -17,10 +18,7 @@ namespace SixLabors.ImageSharp.Tests { private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); - private static void VerifyDecodedImage(Image img) - { - Assert.Equal(new Size(127, 64), img.Size()); - } + private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); [Fact] public void Path_Specific() @@ -39,49 +37,21 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task Path_Agnostic_Async() { - using var img = await Image.LoadAsync(this.Path); + using Image img = await Image.LoadAsync(this.Path); VerifyDecodedImage(img); } [Fact] public async Task Path_Specific_Async() { - using var img = await Image.LoadAsync(this.Path); + using Image img = await Image.LoadAsync(this.Path); VerifyDecodedImage(img); } [Fact] public async Task Path_Agnostic_Configuration_Async() { - using var img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } - - [Fact] - public void Path_Decoder_Specific() - { - using var img = Image.Load(this.Path, new BmpDecoder()); - VerifyDecodedImage(img); - } - - [Fact] - public void Path_Decoder_Agnostic() - { - using var img = Image.Load(this.Path, new BmpDecoder()); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Path_Decoder_Agnostic_Async() - { - using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); - VerifyDecodedImage(img); - } - - [Fact] - public async Task Path_Decoder_Specific_Async() - { - using var img = await Image.LoadAsync(this.Path, new BmpDecoder()); + using Image img = await Image.LoadAsync(this.Path); VerifyDecodedImage(img); } @@ -103,37 +73,19 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WhenFileNotFound_Throws() - { - Assert.Throws( - () => - { - Image.Load(Guid.NewGuid().ToString()); - }); - } + => Assert.Throws(() => Image.Load(Guid.NewGuid().ToString())); [Fact] public void WhenPathIsNull_Throws() - { - Assert.Throws( - () => - { - Image.Load((string)null); - }); - } + => Assert.Throws(() => Image.Load((string)null)); [Fact] public Task Async_WhenFileNotFound_Throws() - { - return Assert.ThrowsAsync( - () => Image.LoadAsync(Guid.NewGuid().ToString())); - } + => Assert.ThrowsAsync(() => Image.LoadAsync(Guid.NewGuid().ToString())); [Fact] public Task Async_WhenPathIsNull_Throws() - { - return Assert.ThrowsAsync( - () => Image.LoadAsync((string)null)); - } + => Assert.ThrowsAsync(() => Image.LoadAsync((string)null)); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs index aa3f38f629..d4e8adf114 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs @@ -15,14 +15,15 @@ namespace SixLabors.ImageSharp.Tests { private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Configuration_Bytes_Specific(bool useSpan) + [Fact] + public void Configuration_Bytes_Specific() { - var img = useSpan - ? Image.Load(this.TopLevelConfiguration, this.ByteSpan) - : Image.Load(this.TopLevelConfiguration, this.ByteArray); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.ByteSpan); Assert.NotNull(img); Assert.Equal(this.TestFormat.Sample(), img); @@ -30,14 +31,15 @@ namespace SixLabors.ImageSharp.Tests this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Configuration_Bytes_Agnostic(bool useSpan) + [Fact] + public void Configuration_Bytes_Agnostic() { - var img = useSpan - ? Image.Load(this.TopLevelConfiguration, this.ByteSpan) - : Image.Load(this.TopLevelConfiguration, this.ByteArray); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.ByteSpan); Assert.NotNull(img); Assert.Equal(this.TestFormat.SampleAgnostic(), img); @@ -45,45 +47,15 @@ namespace SixLabors.ImageSharp.Tests this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Configuration_Bytes_Decoder_Specific(bool useSpan) - { - var localFormat = new TestFormat(); - - var img = useSpan ? - Image.Load(this.TopLevelConfiguration, this.ByteSpan, localFormat.Decoder) : - Image.Load(this.TopLevelConfiguration, this.ByteArray, localFormat.Decoder); - - Assert.NotNull(img); - localFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Configuration_Bytes_Decoder_Agnostic(bool useSpan) + [Fact] + public void Configuration_Bytes_OutFormat_Specific() { - var localFormat = new TestFormat(); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; - var img = useSpan ? - Image.Load(this.TopLevelConfiguration, this.ByteSpan, localFormat.Decoder) : - Image.Load(this.TopLevelConfiguration, this.ByteArray, localFormat.Decoder); - - Assert.NotNull(img); - localFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Configuration_Bytes_OutFormat_Specific(bool useSpan) - { - IImageFormat format; - var img = useSpan ? - Image.Load(this.TopLevelConfiguration, this.ByteSpan, out format) : - Image.Load(this.TopLevelConfiguration, this.ByteArray, out format); + var img = Image.Load(options, this.ByteSpan, out IImageFormat format); Assert.NotNull(img); Assert.Equal(this.TestFormat, format); @@ -91,15 +63,15 @@ namespace SixLabors.ImageSharp.Tests this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Configuration_Bytes_OutFormat_Agnostic(bool useSpan) + [Fact] + public void Configuration_Bytes_OutFormat_Agnostic() { - IImageFormat format; - var img = useSpan ? - Image.Load(this.TopLevelConfiguration, this.ByteSpan, out format) : - Image.Load(this.TopLevelConfiguration, this.ByteArray, out format); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.ByteSpan, out IImageFormat format); Assert.NotNull(img); Assert.Equal(this.TestFormat, format); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs index b9435f487a..2b57f9aa13 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -16,81 +16,38 @@ namespace SixLabors.ImageSharp.Tests { private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - private static Span ByteSpan => new Span(ByteArray); + private static Span ByteSpan => new(ByteArray); - private static void VerifyDecodedImage(Image img) - { - Assert.Equal(new Size(127, 64), img.Size()); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Bytes_Specific(bool useSpan) - { - using (var img = useSpan ? Image.Load(ByteSpan) : Image.Load(ByteArray)) - { - VerifyDecodedImage(img); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Bytes_Agnostic(bool useSpan) - { - using (var img = useSpan ? Image.Load(ByteSpan) : Image.Load(ByteArray)) - { - VerifyDecodedImage(img); - } - } + private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Bytes_Decoder_Specific(bool useSpan) + [Fact] + public void Bytes_Specific() { - using (var img = useSpan ? Image.Load(ByteSpan, new BmpDecoder()) : Image.Load(ByteArray, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(ByteSpan); + VerifyDecodedImage(img); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Bytes_Decoder_Agnostic(bool useSpan) + [Fact] + public void Bytes_Agnostic() { - using (var img = useSpan ? Image.Load(ByteSpan, new BmpDecoder()) : Image.Load(ByteArray, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(ByteSpan); + VerifyDecodedImage(img); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Bytes_OutFormat_Specific(bool useSpan) + [Fact] + public void Bytes_OutFormat_Specific() { - IImageFormat format; - using (var img = useSpan ? Image.Load(ByteSpan, out format) : Image.Load(ByteArray, out format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(ByteSpan, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Bytes_OutFormat_Agnostic(bool useSpan) + [Fact] + public void Bytes_OutFormat_Agnostic() { - IImageFormat format; - using (var img = useSpan ? Image.Load(ByteSpan, out format) : Image.Load(ByteArray, out format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(ByteSpan, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs index c33a932c6d..8931ab46c3 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; @@ -17,7 +16,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Stream_Specific() { - var img = Image.Load(this.TopLevelConfiguration, this.DataStream); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.DataStream); Assert.NotNull(img); Assert.Equal(this.TestFormat.Sample(), img); @@ -28,7 +32,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Stream_Agnostic() { - var img = Image.Load(this.TopLevelConfiguration, this.DataStream); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.DataStream); Assert.NotNull(img); Assert.Equal(this.TestFormat.SampleAgnostic(), img); @@ -39,8 +48,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void NonSeekableStream() { + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + var stream = new NonSeekableStream(this.DataStream); - var img = Image.Load(this.TopLevelConfiguration, stream); + var img = Image.Load(options, stream); Assert.NotNull(img); @@ -50,38 +64,28 @@ namespace SixLabors.ImageSharp.Tests [Fact] public async Task NonSeekableStreamAsync() { + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + var stream = new NonSeekableStream(this.DataStream); - Image img = await Image.LoadAsync(this.TopLevelConfiguration, stream); + Image img = await Image.LoadAsync(options, stream); Assert.NotNull(img); this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); } - [Fact] - public void Configuration_Stream_Decoder_Specific() - { - var stream = new MemoryStream(); - var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); - - Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default)); - } - - [Fact] - public void Configuration_Stream_Decoder_Agnostic() - { - var stream = new MemoryStream(); - var img = Image.Load(this.TopLevelConfiguration, stream, this.localDecoder.Object); - - Assert.NotNull(img); - this.localDecoder.Verify(x => x.Decode(this.TopLevelConfiguration, stream, default)); - } - [Fact] public void Configuration_Stream_OutFormat_Specific() { - var img = Image.Load(this.TopLevelConfiguration, this.DataStream, out IImageFormat format); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.DataStream, out IImageFormat format); Assert.NotNull(img); Assert.Equal(this.TestFormat, format); @@ -92,7 +96,12 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Stream_OutFormat_Agnostic() { - var img = Image.Load(this.TopLevelConfiguration, this.DataStream, out IImageFormat format); + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + var img = Image.Load(options, this.DataStream, out IImageFormat format); Assert.NotNull(img); Assert.Equal(this.TestFormat, format); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs index b65d0071ea..7ad660c68c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs @@ -20,30 +20,23 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Image_Load_Throws_UnknownImageFormatException() - { - Assert.Throws(() => + => Assert.Throws(() => { - using (Image.Load(Configuration.Default, this.Stream, out IImageFormat format)) + using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) { } }); - } [Fact] public void Image_Load_T_Throws_UnknownImageFormatException() - { - Assert.Throws(() => + => Assert.Throws(() => { - using (Image.Load(Configuration.Default, this.Stream, out IImageFormat format)) + using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) { } }); - } - public void Dispose() - { - this.Stream?.Dispose(); - } + public void Dispose() => this.Stream?.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs index 74799eb30f..aacc0cee9a 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -31,71 +31,43 @@ namespace SixLabors.ImageSharp.Tests } private static void VerifyDecodedImage(Image img) - { - Assert.Equal(new Size(127, 64), img.Size()); - } + => Assert.Equal(new Size(127, 64), img.Size()); [Fact] public void Stream_Specific() { - using (var img = Image.Load(this.Stream)) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Stream); + VerifyDecodedImage(img); } [Fact] public void Stream_Agnostic() { - using (var img = Image.Load(this.Stream)) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Stream); + VerifyDecodedImage(img); } [Fact] public void Stream_OutFormat_Specific() { - using (var img = Image.Load(this.Stream, out IImageFormat format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } - } - - [Fact] - public void Stream_Decoder_Specific() - { - using (var img = Image.Load(this.Stream, new BmpDecoder())) - { - VerifyDecodedImage(img); - } - } - - [Fact] - public void Stream_Decoder_Agnostic() - { - using (var img = Image.Load(this.Stream, new BmpDecoder())) - { - VerifyDecodedImage(img); - } + using var img = Image.Load(this.Stream, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } [Fact] public void Stream_OutFormat_Agnostic() { - using (var img = Image.Load(this.Stream, out IImageFormat format)) - { - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(this.Stream, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } [Fact] public async Task Async_Stream_OutFormat_Agnostic() { this.AllowSynchronousIO = false; - var formattedImage = await Image.LoadWithFormatAsync(this.Stream); + (Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); using (formattedImage.Image) { VerifyDecodedImage(formattedImage.Image); @@ -107,27 +79,23 @@ namespace SixLabors.ImageSharp.Tests public async Task Async_Stream_Specific() { this.AllowSynchronousIO = false; - using (var img = await Image.LoadAsync(this.Stream)) - { - VerifyDecodedImage(img); - } + using Image img = await Image.LoadAsync(this.Stream); + VerifyDecodedImage(img); } [Fact] public async Task Async_Stream_Agnostic() { this.AllowSynchronousIO = false; - using (var img = await Image.LoadAsync(this.Stream)) - { - VerifyDecodedImage(img); - } + using Image img = await Image.LoadAsync(this.Stream); + VerifyDecodedImage(img); } [Fact] public async Task Async_Stream_OutFormat_Specific() { this.AllowSynchronousIO = false; - var formattedImage = await Image.LoadWithFormatAsync(this.Stream); + (Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); using (formattedImage.Image) { VerifyDecodedImage(formattedImage.Image); @@ -135,30 +103,7 @@ namespace SixLabors.ImageSharp.Tests } } - [Fact] - public async Task Async_Stream_Decoder_Specific() - { - this.AllowSynchronousIO = false; - using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder())) - { - VerifyDecodedImage(img); - } - } - - [Fact] - public async Task Async_Stream_Decoder_Agnostic() - { - this.AllowSynchronousIO = false; - using (var img = await Image.LoadAsync(this.Stream, new BmpDecoder())) - { - VerifyDecodedImage(img); - } - } - - public void Dispose() - { - this.BaseStream?.Dispose(); - } + public void Dispose() => this.BaseStream?.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index c5a94ec4db..6d4198c595 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -6,7 +6,6 @@ using System.IO; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; @@ -62,13 +61,18 @@ namespace SixLabors.ImageSharp.Tests IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( configuration.ImageFormatsManager.FindFormatByFileExtension(formatInner)); string dir = TestEnvironment.CreateOutputDirectory(".Temp"); - string path = Path.Combine(dir, $"{Guid.NewGuid().ToString()}.{formatInner}"); + string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}"); using (Image temp = new(2048, 2048)) { temp.Save(path, encoder); } - using var image = Image.Load(configuration, path); + DecoderOptions options = new() + { + Configuration = configuration + }; + + using var image = Image.Load(options, path); File.Delete(path); Assert.Equal(1, image.GetPixelMemoryGroup().Count); } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index c4ad11cbfc..a101e6e706 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif WebpLossy } - private static readonly Dictionary TestProfileValues = new Dictionary + private static readonly Dictionary TestProfileValues = new() { { ExifTag.Software, "Software" }, { ExifTag.Copyright, "Copyright" }, @@ -125,42 +125,40 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif [InlineData(TestImageWriteFormat.WebpLossy)] public void WriteFraction(TestImageWriteFormat imageFormat) { - using (var memStream = new MemoryStream()) - { - double exposureTime = 1.0 / 1600; + using var memStream = new MemoryStream(); + double exposureTime = 1.0 / 1600; - ExifProfile profile = GetExifProfile(); + ExifProfile profile = GetExifProfile(); - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); - var image = new Image(1, 1); - image.Metadata.ExifProfile = profile; + var image = new Image(1, 1); + image.Metadata.ExifProfile = profile; - image = WriteAndRead(image, imageFormat); + image = WriteAndRead(image, imageFormat); - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); - IExifValue value = profile.GetValue(ExifTag.ExposureTime); - Assert.NotNull(value); - Assert.NotEqual(exposureTime, value.Value.ToDouble()); + IExifValue value = profile.GetValue(ExifTag.ExposureTime); + Assert.NotNull(value); + Assert.NotEqual(exposureTime, value.Value.ToDouble()); - memStream.Position = 0; - profile = GetExifProfile(); + memStream.Position = 0; + profile = GetExifProfile(); - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.Metadata.ExifProfile = profile; + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); + image.Metadata.ExifProfile = profile; - image = WriteAndRead(image, imageFormat); + image = WriteAndRead(image, imageFormat); - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); - value = profile.GetValue(ExifTag.ExposureTime); - Assert.Equal(exposureTime, value.Value.ToDouble()); + value = profile.GetValue(ExifTag.ExposureTime); + Assert.Equal(exposureTime, value.Value.ToDouble()); - image.Dispose(); - } + image.Dispose(); } [Theory] @@ -553,38 +551,32 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif private static Image WriteAndReadJpeg(Image image) { - using (var memStream = new MemoryStream()) - { - image.SaveAsJpeg(memStream); - image.Dispose(); + using var memStream = new MemoryStream(); + image.SaveAsJpeg(memStream); + image.Dispose(); - memStream.Position = 0; - return Image.Load(memStream); - } + memStream.Position = 0; + return Image.Load(memStream); } private static Image WriteAndReadPng(Image image) { - using (var memStream = new MemoryStream()) - { - image.SaveAsPng(memStream); - image.Dispose(); + using var memStream = new MemoryStream(); + image.SaveAsPng(memStream); + image.Dispose(); - memStream.Position = 0; - return Image.Load(memStream); - } + memStream.Position = 0; + return Image.Load(memStream); } private static Image WriteAndReadWebp(Image image, WebpFileFormatType fileFormat) { - using (var memStream = new MemoryStream()) - { - image.SaveAsWebp(memStream, new WebpEncoder() { FileFormat = fileFormat }); - image.Dispose(); + using var memStream = new MemoryStream(); + image.SaveAsWebp(memStream, new WebpEncoder() { FileFormat = fileFormat }); + image.Dispose(); - memStream.Position = 0; - return Image.Load(memStream, new WebpDecoder()); - } + memStream.Position = 0; + return Image.Load(memStream); } private static void TestProfile(ExifProfile profile) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index c9972aa25d..a91d12e568 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { public class IptcProfileTests { - private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + private static JpegDecoder JpegDecoder => new(); - private static TiffDecoder TiffDecoder => new TiffDecoder() { IgnoreMetadata = false }; + private static TiffDecoder TiffDecoder => new(); public static IEnumerable AllIptcTags() { @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { // arrange var profile = new IptcProfile(); - var value = new string('s', tag.MaxLength() + 1); + string value = new('s', tag.MaxLength() + 1); int expectedLength = tag.MaxLength(); // act @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { // arrange var profile = new IptcProfile(); - var value = new string('s', tag.MaxLength() + 1); + string value = new('s', tag.MaxLength() + 1); int expectedLength = value.Length; // act @@ -164,8 +164,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { // arrange var profile = new IptcProfile(); - var expectedCaptionWriter = "unittest"; - var expectedCaption = "test"; + const string expectedCaptionWriter = "unittest"; + const string expectedCaption = "test"; profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); profile.SetValue(IptcTag.Caption, expectedCaption); @@ -185,8 +185,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { // arrange var profile = new IptcProfile(); - var captionWriter = "unittest"; - var caption = "test"; + const string captionWriter = "unittest"; + const string caption = "test"; profile.SetValue(IptcTag.CaptionWriter, captionWriter); profile.SetValue(IptcTag.Caption, caption); @@ -222,8 +222,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC // arrange var image = new Image(1, 1); image.Metadata.IptcProfile = new IptcProfile(); - var expectedCaptionWriter = "unittest"; - var expectedCaption = "test"; + const string expectedCaptionWriter = "unittest"; + const string expectedCaption = "test"; image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); @@ -257,8 +257,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { // arrange var profile = new IptcProfile(); - var expectedValue1 = "test"; - var expectedValue2 = "another one"; + const string expectedValue1 = "test"; + const string expectedValue2 = "another one"; profile.SetValue(tag, expectedValue1, false); // act @@ -309,7 +309,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { // arrange var profile = new IptcProfile(); - var expectedValue = "another one"; + const string expectedValue = "another one"; profile.SetValue(tag, "test", false); // act @@ -330,7 +330,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC profile.SetValue(IptcTag.Byline, "test2"); // act - var result = profile.RemoveValue(IptcTag.Byline); + bool result = profile.RemoveValue(IptcTag.Byline); // assert Assert.True(result, "removed result should be true"); @@ -346,7 +346,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC profile.SetValue(IptcTag.Byline, "test2"); // act - var result = profile.RemoveValue(IptcTag.Byline, "test2"); + bool result = profile.RemoveValue(IptcTag.Byline, "test2"); // assert Assert.True(result, "removed result should be true"); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 5a901a5cde..8a40ad8ad1 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -20,15 +20,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { public class XmpProfileTests { - private static GifDecoder GifDecoder => new() { IgnoreMetadata = false }; + private static GifDecoder GifDecoder => new(); - private static JpegDecoder JpegDecoder => new() { IgnoreMetadata = false }; + private static JpegDecoder JpegDecoder => new(); - private static PngDecoder PngDecoder => new() { IgnoreMetadata = false }; + private static PngDecoder PngDecoder => new(); - private static TiffDecoder TiffDecoder => new() { IgnoreMetadata = false }; + private static TiffDecoder TiffDecoder => new(); - private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false }; + private static WebpDecoder WebpDecoder => new(); [Theory] [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index e4f2bdd25d..b824587255 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { } - public static readonly TheoryData DecodeJpegData = new TheoryData + public static readonly TheoryData DecodeJpegData = new() { { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, @@ -38,16 +38,17 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks [MemberData(nameof(DecodeJpegData))] public void DecodeJpeg(string fileName, int executionCount) { - var decoder = new JpegDecoder() + DecoderOptions options = new() { - IgnoreMetadata = true + SkipMetadata = true }; - this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); + + this.DecodeJpegBenchmarkImpl(fileName, options, executionCount); } - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) + private void DecodeJpegBenchmarkImpl(string fileName, DecoderOptions options, int executionCount) { - // do not run this on CI even by accident + // Do not run this on CI even by accident if (TestEnvironment.RunsOnCI) { return; @@ -65,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks executionCount, () => { - var img = Image.Load(bytes, decoder); + var img = Image.Load(options, bytes); img.Dispose(); }, #pragma warning disable SA1515 // Single-line comment should be preceded by blank line @@ -123,8 +124,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } }, #pragma warning disable SA1515 // Single-line comment should be preceded by blank line - // ReSharper disable once ExplicitCallerInfoArgument - $@"Encode {testFiles.Length} images"); + // ReSharper disable once ExplicitCallerInfoArgument + $"Encode {testFiles.Length} images"); #pragma warning restore SA1515 // Single-line comment should be preceded by blank line } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 82425d50d9..869530f013 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Processing; using Xunit; @@ -23,23 +24,26 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks var configuration = Configuration.CreateDefaultInstance(); configuration.MaxDegreeOfParallelism = 1; + DecoderOptions options = new() + { + Configuration = configuration + }; + byte[] imageBytes = TestFile.Create(imagePath).Bytes; - using (var ms = new MemoryStream()) - { - this.Measure( - 30, - () => + using var ms = new MemoryStream(); + this.Measure( + 30, + () => + { + using (var image = Image.Load(options, imageBytes)) { - using (var image = Image.Load(configuration, imageBytes)) - { - image.Mutate(x => x.Resize(image.Size() / 4)); - image.SaveAsJpeg(ms); - } - - ms.Seek(0, SeekOrigin.Begin); - }); - } + image.Mutate(x => x.Resize(image.Size() / 4)); + image.SaveAsJpeg(ms); + } + + ms.Seek(0, SeekOrigin.Begin); + }); } } } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 825f0263cb..ed5c4a0f09 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -13,18 +13,18 @@ namespace SixLabors.ImageSharp.Tests /// /// A test image file. /// - public class TestFile + public sealed class TestFile { /// /// The test file cache. /// - private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary Cache = new(); /// /// The "Formats" directory, as lazy value /// // ReSharper disable once InconsistentNaming - private static readonly Lazy InputImagesDirectoryValue = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); + private static readonly Lazy InputImagesDirectoryValue = new(() => TestEnvironment.InputImagesDirectoryFullPath); /// /// The image (lazy initialized value) @@ -40,15 +40,12 @@ namespace SixLabors.ImageSharp.Tests /// Initializes a new instance of the class. /// /// The file. - private TestFile(string file) - { - this.FullPath = file; - } + private TestFile(string file) => this.FullPath = file; /// /// Gets the image bytes. /// - public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); + public byte[] Bytes => this.bytes ??= File.ReadAllBytes(this.FullPath); /// /// Gets the full path to file. @@ -68,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the image with lazy initialization. /// - private Image Image => this.image ?? (this.image = ImageSharp.Image.Load(this.Bytes)); + private Image Image => this.image ??= ImageSharp.Image.Load(this.Bytes); /// /// Gets the input image directory. @@ -85,9 +82,7 @@ namespace SixLabors.ImageSharp.Tests /// The . /// public static string GetInputFileFullPath(string file) - { - return Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); - } + => Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); /// /// Creates a new test file or returns one from the cache. @@ -97,9 +92,7 @@ namespace SixLabors.ImageSharp.Tests /// The . /// public static TestFile Create(string file) - { - return Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(file))); - } + => Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(fileName))); /// /// Gets the file name. @@ -109,9 +102,7 @@ namespace SixLabors.ImageSharp.Tests /// The . /// public string GetFileName(object value) - { - return $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; - } + => $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; /// /// Gets the file name without extension. @@ -121,30 +112,37 @@ namespace SixLabors.ImageSharp.Tests /// The . /// public string GetFileNameWithoutExtension(object value) - { - return this.FileNameWithoutExtension + "-" + value; - } + => this.FileNameWithoutExtension + "-" + value; /// - /// Creates a new image. + /// Creates a new image. /// /// - /// The . + /// The . /// public Image CreateRgba32Image() - { - return this.Image.Clone(); - } + => this.Image.Clone(); /// - /// Creates a new image. + /// Creates a new image. /// /// - /// The . + /// The . /// public Image CreateRgba32Image(IImageDecoder decoder) + => this.CreateRgba32Image(decoder, new()); + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateRgba32Image(IImageDecoder decoder, DecoderOptions options) { - return ImageSharp.Image.Load(this.Image.GetConfiguration(), this.Bytes, decoder); + options.Configuration = this.Image.GetConfiguration(); + using MemoryStream stream = new(this.Bytes); + return decoder.Decode(options, stream, default); } } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 8060183240..eecdb4bb97 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests /// public class TestFormat : IConfigurationModule, IImageFormat { - private readonly Dictionary sampleImages = new Dictionary(); + private readonly Dictionary sampleImages = new(); // We should not change Configuration.Default in individual tests! // Create new configuration instances with new Configuration(TestFormat.GlobalTestFormat) instead! @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests public Stream CreateAsyncSemaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512) { - var buffer = new byte[size]; + byte[] buffer = new byte[size]; this.header.CopyTo(buffer, 0); var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore); return seeakable ? semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false); @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests { DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); - Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); + Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) { @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests { DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); - Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); + Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) { @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; } - public class TestDecoder : IImageDecoder, IImageInfoDetector + public class TestDecoder : ImageDecoder { private readonly TestFormat testFormat; @@ -206,20 +206,21 @@ namespace SixLabors.ImageSharp.Tests public int HeaderSize => this.testFormat.HeaderSize; - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => this.DecodeImpl(configuration, stream); + public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - private Image DecodeImpl(Configuration config, Stream stream) - where TPixel : unmanaged, IPixel + public override IImageInfo IdentifySpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + + public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Configuration configuration = options.GeneralOptions.Configuration; var ms = new MemoryStream(); - stream.CopyTo(ms, config.StreamProcessingBufferSize); + stream.CopyTo(ms, configuration.StreamProcessingBufferSize); byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { Marker = marker, - Config = config, + Config = configuration, PixelType = typeof(TPixel) }); @@ -227,12 +228,13 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); + public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + } - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) => - this.DecodeImpl(configuration, stream); + public class TestDecoderOptions : ISpecializedDecoderOptions + { + public DecoderOptions GeneralOptions { get; set; } = new(); } public class TestEncoder : IImageEncoder diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index cb19802ceb..603a7b90fa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading.Tasks; -using SixLabors.ImageSharp.Diagnostics; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -90,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests return false; } - if (!object.Equals(kv.Value, otherVal)) + if (!Equals(kv.Value, otherVal)) { return false; } @@ -126,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests public static bool operator !=(Key left, Key right) => !Equals(left, right); } - private static readonly ConcurrentDictionary> Cache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> Cache = new(); // Needed for deserialization! // ReSharper disable once UnusedMember.Local @@ -149,35 +148,78 @@ namespace SixLabors.ImageSharp.Tests return this.GetImage(decoder); } - public override Image GetImage(IImageDecoder decoder) + public override Image GetImage(IImageDecoder decoder, DecoderOptions options) { Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) { - return this.LoadImage(decoder); + return this.DecodeImage(decoder, options); } // do not cache so we can track allocation correctly when validating memory if (MemoryAllocatorValidator.MonitoringAllocations) { - return this.LoadImage(decoder); + return this.DecodeImage(decoder, options); } var key = new Key(this.PixelType, this.FilePath, decoder); - Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); + Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); return cachedImage.Clone(this.Configuration); } - public override Task> GetImageAsync(IImageDecoder decoder) + public override Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) { Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); + + options.Configuration = this.Configuration; // Used in small subset of decoder tests, no caching. + // TODO: Check Path here. Why combined? string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); - return Image.LoadAsync(this.Configuration, path, decoder); + using Stream stream = System.IO.File.OpenRead(path); + return Task.FromResult(decoder.Decode(options, stream, default)); + } + + public override Image GetImage(ImageDecoder decoder, T options) + { + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); + + // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator + if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) + { + return this.DecodeImage(decoder, options); + } + + // do not cache so we can track allocation correctly when validating memory + if (MemoryAllocatorValidator.MonitoringAllocations) + { + return this.DecodeImage(decoder, options); + } + + var key = new Key(this.PixelType, this.FilePath, decoder); + Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); + + return cachedImage.Clone(this.Configuration); + } + + public override Task> GetImageAsync(ImageDecoder decoder, T options) + { + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); + + options.GeneralOptions.Configuration = this.Configuration; + + // Used in small subset of decoder tests, no caching. + // TODO: Check Path here. Why combined? + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); + using Stream stream = System.IO.File.OpenRead(path); + return Task.FromResult(decoder.DecodeSpecialized(options, stream, default)); } public override void Deserialize(IXunitSerializationInfo info) @@ -193,10 +235,23 @@ namespace SixLabors.ImageSharp.Tests info.AddValue("path", this.FilePath); } - private Image LoadImage(IImageDecoder decoder) + private Image DecodeImage(IImageDecoder decoder, DecoderOptions options) + { + options.Configuration = this.Configuration; + + var testFile = TestFile.Create(this.FilePath); + using Stream stream = new MemoryStream(testFile.Bytes); + return decoder.Decode(options, stream, default); + } + + private Image DecodeImage(ImageDecoder decoder, T options) + where T : class, ISpecializedDecoderOptions, new() { + options.GeneralOptions.Configuration = this.Configuration; + var testFile = TestFile.Create(this.FilePath); - return Image.Load(this.Configuration, testFile.Bytes, decoder); + using Stream stream = new MemoryStream(testFile.Bytes); + return decoder.DecodeSpecialized(options, stream, default); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index d6d867ab7e..8d186bdab0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -53,19 +53,17 @@ namespace SixLabors.ImageSharp.Tests => new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); public static TestImageProvider Blank( - int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); public static TestImageProvider File( string filePath, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - { - return new FileProvider(filePath).Init(testMethod, pixelTypeOverride); - } + => new FileProvider(filePath).Init(testMethod, pixelTypeOverride); public static TestImageProvider Lambda( string declaringTypeName, @@ -83,9 +81,7 @@ namespace SixLabors.ImageSharp.Tests byte a = 255, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - { - return new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); - } + => new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); /// /// Returns an instance to the test case with the necessary traits. @@ -93,15 +89,25 @@ namespace SixLabors.ImageSharp.Tests /// A test image. public abstract Image GetImage(); - public virtual Image GetImage(IImageDecoder decoder) - { - throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); - } + public Image GetImage(IImageDecoder decoder) + => this.GetImage(decoder, new()); - public virtual Task> GetImageAsync(IImageDecoder decoder) - { - throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); - } + public Task> GetImageAsync(IImageDecoder decoder) + => this.GetImageAsync(decoder, new()); + + public virtual Image GetImage(IImageDecoder decoder, DecoderOptions options) + => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); + + public virtual Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) + => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); + + public virtual Image GetImage(ImageDecoder decoder, T options) + where T : class, ISpecializedDecoderOptions, new() + => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); + + public virtual Task> GetImageAsync(ImageDecoder decoder, T options) + where T : class, ISpecializedDecoderOptions, new() + => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); /// /// Returns an instance to the test case with the necessary traits. @@ -163,14 +169,13 @@ namespace SixLabors.ImageSharp.Tests protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) { - string subfolder = testMethod?.DeclaringType.GetAttribute()?.Subfolder - ?? string.Empty; + string subfolder = + testMethod?.DeclaringType.GetAttribute()?.Subfolder ?? string.Empty; + return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); } public override string ToString() - { - return $"{this.SourceFileOrDescription}[{this.PixelType}]"; - } + => $"{this.SourceFileOrDescription}[{this.PixelType}]"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 011d89e9fc..ded1adee3c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -15,7 +15,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - public class MagickReferenceDecoder : IImageDecoder + public class MagickReferenceDecoder : ImageDecoder { private readonly bool validate; @@ -28,39 +28,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); - private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); - foreach (Memory m in destinationGroup) - { - Span destBuffer = m.Span; - PixelOperations.Instance.FromRgba32( - configuration, - sourcePixels.Slice(0, destBuffer.Length), - destBuffer); - sourcePixels = sourcePixels.Slice(destBuffer.Length); - } - } - - private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - foreach (Memory m in destinationGroup) - { - Span destBuffer = m.Span; - PixelOperations.Instance.FromRgba64Bytes( - configuration, - rgbaBytes, - destBuffer, - destBuffer.Length); - rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); - } - } - - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + public override Image DecodeSpecialized(MagickReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Configuration configuration = options.GeneralOptions.Configuration; var bmpReadDefines = new BmpReadDefines { IgnoreFileSize = !this.validate @@ -100,6 +70,45 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new Image(configuration, new ImageMetadata(), framesList); } - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); + public override Image DecodeSpecialized(MagickReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + + public override IImageInfo IdentifySpecialized(MagickReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + + private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba32( + configuration, + sourcePixels.Slice(0, destBuffer.Length), + destBuffer); + sourcePixels = sourcePixels.Slice(destBuffer.Length); + } + } + + private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba64Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); + } + } + } + + public class MagickReferenceDecoderOptions : ISpecializedDecoderOptions + { + public DecoderOptions GeneralOptions { get; set; } = new(); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 743a62797b..21f0fd8650 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -6,52 +6,53 @@ using System.Threading; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SDBitmap = System.Drawing.Bitmap; +using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - public class SystemDrawingReferenceDecoder : IImageDecoder, IImageInfoDetector + public class SystemDrawingReferenceDecoder : ImageDecoder { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override IImageInfo IdentifySpecialized(SystemDrawingReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - using (var sourceBitmap = new System.Drawing.Bitmap(stream)) - { - if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) - { - return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); - } - - using (var convertedBitmap = new System.Drawing.Bitmap( - sourceBitmap.Width, - sourceBitmap.Height, - System.Drawing.Imaging.PixelFormat.Format32bppArgb)) - { - using (var g = System.Drawing.Graphics.FromImage(convertedBitmap)) - { - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - - g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); - } - - return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); - } - } + using var sourceBitmap = new SDBitmap(stream); + PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat)); + return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } - public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public override Image DecodeSpecialized(SystemDrawingReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) { - using (var sourceBitmap = new System.Drawing.Bitmap(stream)) + using var sourceBitmap = new SDBitmap(stream); + if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) + { + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); + } + + using var convertedBitmap = new SDBitmap( + sourceBitmap.Width, + sourceBitmap.Height, + System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var g = System.Drawing.Graphics.FromImage(convertedBitmap)) { - var pixelType = new PixelTypeInfo(System.Drawing.Image.GetPixelFormatSize(sourceBitmap.PixelFormat)); - return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + + g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); } + + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); } - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); + public override Image DecodeSpecialized(SystemDrawingReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + } + + public class SystemDrawingReferenceDecoderOptions : ISpecializedDecoderOptions + { + public DecoderOptions GeneralOptions { get; set; } = new(); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 97fc00514e..2b7316f0cc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests { public static partial class TestEnvironment { - private static readonly Lazy ConfigurationLazy = new Lazy(CreateDefaultConfiguration); + private static readonly Lazy ConfigurationLazy = new(CreateDefaultConfiguration); internal static Configuration Configuration => ConfigurationLazy.Value; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 30715161bb..3081edfa7c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -327,7 +327,8 @@ namespace SixLabors.ImageSharp.Tests decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); - return Image.Load(referenceOutputFile, decoder); + using FileStream stream = File.OpenRead(referenceOutputFile); + return decoder.Decode(DecoderOptions.Default, stream, default); } public static Image GetReferenceOutputImageMultiFrame( @@ -355,7 +356,8 @@ namespace SixLabors.ImageSharp.Tests throw new Exception("Reference output file missing: " + path); } - var tempImage = Image.Load(path, decoder); + using FileStream stream = File.OpenRead(path); + Image tempImage = decoder.Decode(DecoderOptions.Default, stream, default); temporaryFrameImages.Add(tempImage); } @@ -383,13 +385,11 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true) where TPixel : unmanaged, IPixel { - using (Image referenceImage = provider.GetReferenceOutputImage( + using Image referenceImage = provider.GetReferenceOutputImage( testOutputDetails, extension, - appendPixelTypeToFileName)) - { - return comparer.CompareImages(referenceImage, image); - } + appendPixelTypeToFileName); + return comparer.CompareImages(referenceImage, image); } public static Image ComparePixelBufferTo( @@ -534,9 +534,10 @@ namespace SixLabors.ImageSharp.Tests var testFile = TestFile.Create(path); - referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(path); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); - using (var original = Image.Load(testFile.Bytes, referenceDecoder)) + using var stream = new MemoryStream(testFile.Bytes); + using (Image original = referenceDecoder.Decode(DecoderOptions.Default, stream, default)) { comparer.VerifySimilarity(original, image); } @@ -559,9 +560,10 @@ namespace SixLabors.ImageSharp.Tests var testFile = TestFile.Create(path); - referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(path); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); - using (var original = Image.Load(testFile.Bytes, referenceDecoder)) + using var stream = new MemoryStream(testFile.Bytes); + using (Image original = referenceDecoder.Decode(DecoderOptions.Default, stream, default)) { comparer.VerifySimilarity(original, image); } @@ -584,23 +586,21 @@ namespace SixLabors.ImageSharp.Tests bool appendSourceFileOrDescription = true) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - operation(image); - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - - image.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - } + using Image image = provider.GetImage(); + operation(image); + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + + image.CompareToReferenceOutput( + comparer, + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); } /// @@ -682,11 +682,11 @@ namespace SixLabors.ImageSharp.Tests referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var encodedImage = Image.Load(actualOutputFile, referenceDecoder)) - { - ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(encodedImage, image); - } + using FileStream stream = File.OpenRead(actualOutputFile); + using Image encodedImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); + + ImageComparer comparer = customComparer ?? ImageComparer.Exact; + comparer.VerifySimilarity(encodedImage, image); } internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index 81ec462f1a..f97d1341f3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -1,17 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.IO; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; +using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - - using Xunit.Abstractions; - public class MagickReferenceCodecTests { public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output; @@ -39,17 +39,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests ImageComparer comparer = ImageComparer.Exact; - using (var mImage = Image.Load(path, magickDecoder)) - using (var sdImage = Image.Load(path, sdDecoder)) - { - ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); + using FileStream mStream = File.OpenRead(path); + using FileStream sdStream = File.OpenRead(path); + + using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream, default); + using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream, default); + + ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - mImage.DebugSave(dummyProvider); + mImage.DebugSave(dummyProvider); - if (TestEnvironment.IsWindows) - { - Assert.True(report.IsEmpty); - } + if (TestEnvironment.IsWindows) + { + Assert.True(report.IsEmpty); } } @@ -69,18 +71,17 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests // 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space) var comparer = ImageComparer.TolerantPercentage(1, 1020); + using FileStream mStream = File.OpenRead(path); + using FileStream sdStream = File.OpenRead(path); + using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream, default); + using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream, default); + ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - using (var mImage = Image.Load(path, magickDecoder)) - using (var sdImage = Image.Load(path, sdDecoder)) - { - ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - - mImage.DebugSave(dummyProvider); + mImage.DebugSave(dummyProvider); - if (TestEnvironment.IsWindows) - { - Assert.True(report.IsEmpty); - } + if (TestEnvironment.IsWindows) + { + Assert.True(report.IsEmpty); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs index 573dbd9b02..e7e215d708 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -16,24 +18,17 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { private ITestOutputHelper Output { get; } - public SystemDrawingReferenceCodecTests(ITestOutputHelper output) - { - this.Output = output; - } + public SystemDrawingReferenceCodecTests(ITestOutputHelper output) => this.Output = output; [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) - { - string fileName = provider.Utility.GetTestOutputFileName("png"); - sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); - } - } + using Image image = provider.GetImage(); + using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); + string fileName = provider.Utility.GetTestOutputFileName("png"); + sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); } [Theory] @@ -43,28 +38,22 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - using (var sdBitmap = new System.Drawing.Bitmap(path)) - { - using (Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap)) - { - image.DebugSave(dummyProvider); - } - } + using var sdBitmap = new System.Drawing.Bitmap(path); + using Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); + image.DebugSave(dummyProvider); } private static string SavePng(TestImageProvider provider, PngColorType pngColorType) where TPixel : unmanaged, IPixel { - using (Image sourceImage = provider.GetImage()) + using Image sourceImage = provider.GetImage(); + if (pngColorType != PngColorType.RgbWithAlpha) { - if (pngColorType != PngColorType.RgbWithAlpha) - { - sourceImage.Mutate(c => c.MakeOpaque()); - } - - var encoder = new PngEncoder { ColorType = pngColorType }; - return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); + sourceImage.Mutate(c => c.MakeOpaque()); } + + var encoder = new PngEncoder { ColorType = pngColorType }; + return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); } [Theory] @@ -79,15 +68,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests string path = SavePng(provider, PngColorType.RgbWithAlpha); - using (var sdBitmap = new System.Drawing.Bitmap(path)) - { - using (Image original = provider.GetImage()) - using (Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap)) - { - ImageComparer comparer = ImageComparer.Exact; - comparer.VerifySimilarity(original, resaved); - } - } + using var sdBitmap = new System.Drawing.Bitmap(path); + using Image original = provider.GetImage(); + using Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); + ImageComparer comparer = ImageComparer.Exact; + comparer.VerifySimilarity(original, resaved); } [Theory] @@ -97,17 +82,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { string path = SavePng(provider, PngColorType.Rgb); - using (Image original = provider.GetImage()) - { - using (var sdBitmap = new System.Drawing.Bitmap(path)) - { - using (Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap)) - { - ImageComparer comparer = ImageComparer.Exact; - comparer.VerifySimilarity(original, resaved); - } - } - } + using Image original = provider.GetImage(); + using var sdBitmap = new System.Drawing.Bitmap(path); + using Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap); + ImageComparer comparer = ImageComparer.Exact; + comparer.VerifySimilarity(original, resaved); } [Theory] @@ -116,10 +95,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - using (var image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) - { - image.DebugSave(dummyProvider); - } + using FileStream stream = File.OpenRead(path); + using Image image = SystemDrawingReferenceDecoder.Instance.Decode(DecoderOptions.Default, stream, default); + image.DebugSave(dummyProvider); } [Theory] @@ -127,10 +105,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests public void SaveWithReferenceEncoder(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - provider.Utility.SaveTestOutputFile(image, "png", SystemDrawingReferenceEncoder.Png); - } + using Image image = provider.GetImage(); + provider.Utility.SaveTestOutputFile(image, "png", SystemDrawingReferenceEncoder.Png); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index a272303fa9..fed4c75871 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -18,13 +18,13 @@ namespace SixLabors.ImageSharp.Tests { public class TestImageProviderTests { - public static readonly TheoryData BasicData = new TheoryData + public static readonly TheoryData BasicData = new() { TestImageProvider.Blank(10, 20), TestImageProvider.Blank(10, 20), }; - public static readonly TheoryData FileData = new TheoryData + public static readonly TheoryData FileData = new() { TestImageProvider.File(TestImages.Bmp.Car), TestImageProvider.File(TestImages.Bmp.F) @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests /// A test image. public static Image CreateTestImage() where TPixel : unmanaged, IPixel => - new Image(3, 3); + new(3, 3); [Theory] [MemberData(nameof(BasicData))] @@ -179,16 +179,14 @@ namespace SixLabors.ImageSharp.Tests public void SaveTestOutputFileMultiFrame(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); + using Image image = provider.GetImage(); + string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); - Assert.True(files.Length > 2); - foreach (string path in files) - { - this.Output.WriteLine(path); - Assert.True(File.Exists(path)); - } + Assert.True(files.Length > 2); + foreach (string path in files) + { + this.Output.WriteLine(path); + Assert.True(File.Exists(path)); } } @@ -199,10 +197,8 @@ namespace SixLabors.ImageSharp.Tests public void Use_WithBasicTestPatternImages(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image img = provider.GetImage()) - { - img.DebugSave(provider); - } + using Image img = provider.GetImage(); + img.DebugSave(provider); } [Theory] @@ -238,24 +234,20 @@ namespace SixLabors.ImageSharp.Tests where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - using (Image img = provider.GetImage()) - { - Assert.True(img.Width * img.Height > 0); + using Image img = provider.GetImage(); + Assert.True(img.Width * img.Height > 0); - Assert.Equal(123, yo); + Assert.Equal(123, yo); - string fn = provider.Utility.GetTestOutputFileName("jpg"); - this.Output.WriteLine(fn); - } + string fn = provider.Utility.GetTestOutputFileName("jpg"); + this.Output.WriteLine(fn); } [Theory] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) where TPixel : unmanaged, IPixel - { - EnsureCustomConfigurationIsApplied(provider); - } + => EnsureCustomConfigurationIsApplied(provider); [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] @@ -263,10 +255,8 @@ namespace SixLabors.ImageSharp.Tests where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - using (Image image = provider.GetImage()) - { - provider.Utility.SaveTestOutputFile(image, "png"); - } + using Image image = provider.GetImage(); + provider.Utility.SaveTestOutputFile(image, "png"); } [Theory] @@ -312,19 +302,15 @@ namespace SixLabors.ImageSharp.Tests public void Use_WithTestPatternImages(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image img = provider.GetImage()) - { - img.DebugSave(provider); - } + using Image img = provider.GetImage(); + img.DebugSave(provider); } [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) where TPixel : unmanaged, IPixel - { - EnsureCustomConfigurationIsApplied(provider); - } + => EnsureCustomConfigurationIsApplied(provider); private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -334,22 +320,19 @@ namespace SixLabors.ImageSharp.Tests var customConfiguration = Configuration.CreateDefaultInstance(); provider.Configuration = customConfiguration; - using (Image image2 = provider.GetImage()) - using (Image image3 = provider.GetImage()) - { - Assert.Same(customConfiguration, image2.GetConfiguration()); - Assert.Same(customConfiguration, image3.GetConfiguration()); - } + using Image image2 = provider.GetImage(); + using Image image3 = provider.GetImage(); + Assert.Same(customConfiguration, image2.GetConfiguration()); + Assert.Same(customConfiguration, image3.GetConfiguration()); } } - private class TestDecoder : IImageDecoder + private class TestDecoder : ImageDecoder { // Couldn't make xUnit happy without this hackery: - private static readonly ConcurrentDictionary InvocationCounts = - new ConcurrentDictionary(); + private static readonly ConcurrentDictionary InvocationCounts = new(); - private static readonly object Monitor = new object(); + private static readonly object Monitor = new(); private string callerName; @@ -361,13 +344,18 @@ namespace SixLabors.ImageSharp.Tests } } - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override IImageInfo IdentifySpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + + public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) { InvocationCounts[this.callerName]++; return new Image(42, 42); } + public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal void InitCaller(string name) @@ -375,16 +363,13 @@ namespace SixLabors.ImageSharp.Tests this.callerName = name; InvocationCounts[name] = 0; } - - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); } - private class TestDecoderWithParameters : IImageDecoder + private class TestDecoderWithParameters : ImageDecoder { - private static readonly ConcurrentDictionary InvocationCounts = - new ConcurrentDictionary(); + private static readonly ConcurrentDictionary InvocationCounts = new(); - private static readonly object Monitor = new object(); + private static readonly object Monitor = new(); private string callerName; @@ -400,13 +385,18 @@ namespace SixLabors.ImageSharp.Tests } } - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public override IImageInfo IdentifySpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + + public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) { InvocationCounts[this.callerName]++; return new Image(42, 42); } + public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal void InitCaller(string name) @@ -414,8 +404,11 @@ namespace SixLabors.ImageSharp.Tests this.callerName = name; InvocationCounts[name] = 0; } + } - public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); + private class TestDecoderOptions : ISpecializedDecoderOptions + { + public DecoderOptions GeneralOptions { get; set; } = new(); } } } From 8d50996ce74b5b9fb252ad967c41359c87645ccc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 01:58:19 +1000 Subject: [PATCH 44/98] Fix cache key resolution --- .../Formats/Webp/WebpDecoderCore.cs | 2 +- .../ImageProviders/FileProvider.cs | 50 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 9cae89252b..ecb7b1b87d 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// The webp features. private void ParseOptionalChunks(WebpFeatures features) { - if (this.skipMetadata || (features.ExifProfile == false && features.XmpMetaData == false)) + if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) { return; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 603a7b90fa..5bf28b3c8b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -28,38 +28,68 @@ namespace SixLabors.ImageSharp.Tests private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) + public Key( + PixelTypes pixelType, + string filePath, + IImageDecoder customDecoder, + DecoderOptions options, + ISpecializedDecoderOptions specialized) { Type customType = customDecoder?.GetType(); this.commonValues = new Tuple( pixelType, filePath, customType); - this.decoderParameters = GetDecoderParameters(customDecoder); + this.decoderParameters = GetDecoderParameters(options, specialized); } - private static Dictionary GetDecoderParameters(IImageDecoder customDecoder) + private static Dictionary GetDecoderParameters( + DecoderOptions options, + ISpecializedDecoderOptions specialized) { - Type type = customDecoder.GetType(); + Type type = options.GetType(); var data = new Dictionary(); while (type != null && type != typeof(object)) { - PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); - foreach (PropertyInfo p in properties) + foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { string key = $"{type.FullName}.{p.Name}"; - object value = p.GetValue(customDecoder); - data[key] = value; + data[key] = p.GetValue(options); } type = type.GetTypeInfo().BaseType; } + GetSpecializedDecoderParameters(data, specialized); + return data; } + private static void GetSpecializedDecoderParameters( + Dictionary data, + ISpecializedDecoderOptions options) + { + if (options is null) + { + return; + } + + Type type = options.GetType(); + + while (type != null && type != typeof(object)) + { + foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + string key = $"{type.FullName}.{p.Name}"; + data[key] = p.GetValue(options); + } + + type = type.GetTypeInfo().BaseType; + } + } + public bool Equals(Key other) { if (other is null) @@ -165,7 +195,7 @@ namespace SixLabors.ImageSharp.Tests return this.DecodeImage(decoder, options); } - var key = new Key(this.PixelType, this.FilePath, decoder); + var key = new Key(this.PixelType, this.FilePath, decoder, options, null); Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); return cachedImage.Clone(this.Configuration); @@ -202,7 +232,7 @@ namespace SixLabors.ImageSharp.Tests return this.DecodeImage(decoder, options); } - var key = new Key(this.PixelType, this.FilePath, decoder); + var key = new Key(this.PixelType, this.FilePath, decoder, options.GeneralOptions, options); Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); return cachedImage.Clone(this.Configuration); From 6dbc5ec70a396b0d8dc20d74077706112d27bd88 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 02:32:47 +1000 Subject: [PATCH 45/98] Fix param caching test --- .../ImageProviders/FileProvider.cs | 5 ++ .../Tests/TestImageProviderTests.cs | 65 ++++++++++++++----- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 5bf28b3c8b..1fc0100a69 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -82,6 +82,11 @@ namespace SixLabors.ImageSharp.Tests { foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { + if (p.PropertyType == typeof(DecoderOptions)) + { + continue; + } + string key = $"{type.FullName}.{p.Name}"; data[key] = p.GetValue(options); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index fed4c75871..d75ac21dca 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -109,19 +109,31 @@ namespace SixLabors.ImageSharp.Tests TestDecoderWithParameters.DoTestThreadSafe( () => { - string testName = nameof(this + const string testName = nameof(this .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); - var decoder1 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 42 }; + TestDecoderWithParameters decoder1 = new(); + TestDecoderWithParametersOptions options1 = new() + { + Param1 = "Lol", + Param2 = 42 + }; + decoder1.InitCaller(testName); - var decoder2 = new TestDecoderWithParameters { Param1 = "LoL", Param2 = 42 }; + TestDecoderWithParameters decoder2 = new(); + TestDecoderWithParametersOptions options2 = new() + { + Param1 = "LoL", + Param2 = 42 + }; + decoder2.InitCaller(testName); - provider.GetImage(decoder1); + provider.GetImage(decoder1, options1); Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - provider.GetImage(decoder2); + provider.GetImage(decoder2, options2); Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); }); } @@ -143,19 +155,31 @@ namespace SixLabors.ImageSharp.Tests TestDecoderWithParameters.DoTestThreadSafe( () => { - string testName = nameof(this + const string testName = nameof(this .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); - var decoder1 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 666 }; + TestDecoderWithParameters decoder1 = new(); + TestDecoderWithParametersOptions options1 = new() + { + Param1 = "Lol", + Param2 = 666 + }; + decoder1.InitCaller(testName); - var decoder2 = new TestDecoderWithParameters { Param1 = "Lol", Param2 = 666 }; + TestDecoderWithParameters decoder2 = new(); + TestDecoderWithParametersOptions options2 = new() + { + Param1 = "Lol", + Param2 = 666 + }; + decoder2.InitCaller(testName); - provider.GetImage(decoder1); + provider.GetImage(decoder1, options1); Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - provider.GetImage(decoder2); + provider.GetImage(decoder2, options2); Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); }); } @@ -365,7 +389,7 @@ namespace SixLabors.ImageSharp.Tests } } - private class TestDecoderWithParameters : ImageDecoder + private class TestDecoderWithParameters : ImageDecoder { private static readonly ConcurrentDictionary InvocationCounts = new(); @@ -373,10 +397,6 @@ namespace SixLabors.ImageSharp.Tests private string callerName; - public string Param1 { get; set; } - - public int Param2 { get; set; } - public static void DoTestThreadSafe(Action action) { lock (Monitor) @@ -385,16 +405,16 @@ namespace SixLabors.ImageSharp.Tests } } - public override IImageInfo IdentifySpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo IdentifySpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) => this.DecodeSpecialized(options, stream, cancellationToken); - public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) => this.DecodeSpecialized(options, stream, cancellationToken); internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; @@ -410,5 +430,14 @@ namespace SixLabors.ImageSharp.Tests { public DecoderOptions GeneralOptions { get; set; } = new(); } + + private class TestDecoderWithParametersOptions : ISpecializedDecoderOptions + { + public string Param1 { get; set; } + + public int Param2 { get; set; } + + public DecoderOptions GeneralOptions { get; set; } = new(); + } } } From a42101e743238c993fafcab873739c37d024bcbb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 12:58:04 +1000 Subject: [PATCH 46/98] Migrate new Tiff WebPDecoder --- .../Decompressors/WebpTiffCompression.cs | 10 ++++++---- .../Tiff/Compression/TiffDecompressorsFactory.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.Internal.cs | 14 +------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs index 0d63382ff1..e918837c19 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs @@ -16,22 +16,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// internal class WebpTiffCompression : TiffBaseDecompressor { + private readonly DecoderOptions options; + /// /// Initializes a new instance of the class. /// + /// The general decoder options. /// The memory allocator. /// The width of the image. /// The bits per pixel. /// The predictor. - public WebpTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) : base(memoryAllocator, width, bitsPerPixel, predictor) - { - } + => this.options = options; /// protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) { - using var image = Image.Load(stream, new WebpDecoder()); + using Image image = new WebpDecoder().Decode(this.options, stream, default); CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 2b121f4de3..213746d7de 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression case TiffDecoderCompressionType.Webp: DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new WebpTiffCompression(allocator, width, bitsPerPixel); + return new WebpTiffCompression(options, allocator, width, bitsPerPixel); default: throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs index 6bf7ae88fa..5375452852 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -1,22 +1,10 @@ // 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.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 From 204fddd8355f3dcbb75dbec613102d7582010dc0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 13:11:24 +1000 Subject: [PATCH 47/98] Pass cancellation token to decompressors. --- .../Decompressors/DeflateTiffCompression.cs | 3 ++- .../Decompressors/JpegTiffCompression.cs | 16 +++++++--------- .../Decompressors/LzwTiffCompression.cs | 3 ++- .../ModifiedHuffmanTiffCompression.cs | 3 ++- .../Decompressors/NoneTiffCompression.cs | 3 ++- .../Decompressors/PackBitsTiffCompression.cs | 3 ++- .../Decompressors/T4TiffCompression.cs | 3 ++- .../Decompressors/T6TiffCompression.cs | 3 ++- .../Decompressors/WebpTiffCompression.cs | 5 +++-- .../Tiff/Compression/TiffBaseDecompressor.cs | 9 ++++++--- src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs | 6 ++++-- .../Compression/DeflateTiffCompressionTests.cs | 12 +++++------- .../Tiff/Compression/LzwTiffCompressionTests.cs | 6 +++--- .../Tiff/Compression/NoneTiffCompressionTests.cs | 2 +- .../Compression/PackBitsTiffCompressionTests.cs | 2 +- 15 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index 37debc9f6f..ae89fce0ee 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -3,6 +3,7 @@ using System; using System.IO.Compression; +using System.Threading; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { long pos = stream.Position; using (var deframeStream = new ZlibInflateStream( diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index babcb45161..ef44263a56 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { if (this.jpegTables != null) { @@ -60,12 +60,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors case TiffPhotometricInterpretation.WhiteIsZero: { using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(configuration); - var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None); + var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, cancellationToken); jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); - jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None); + jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken); - // TODO: Should we pass through the CancellationToken from the tiff decoder? - using Buffer2D decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None); + using Buffer2D decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken); CopyImageBytesToBuffer(buffer, decompressedBuffer); break; } @@ -75,12 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { using SpectralConverter spectralConverter = new TiffJpegSpectralConverter(configuration, this.photometricInterpretation); - var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); + var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); - jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None); + jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken); - // TODO: Should we pass through the CancellationToken from the tiff decoder? - using Buffer2D decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None); + using Buffer2D decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken); CopyImageBytesToBuffer(buffer, decompressedBuffer); break; } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index 57da76f6c7..a53704abf6 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System; +using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; @@ -35,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { var decoder = new TiffLzwDecoder(stream); decoder.DecodePixels(buffer); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index 16d3f125bd..c95048b686 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System; +using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors private TiffFillOrder FillOrder { get; } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs index 291d40c859..6bec4e2ce5 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System; +using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs index 9df331b6ea..230f9f5c92 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { if (this.compressedDataMemory == null) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index 2fed2b7427..e8513c474b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System; +using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors private TiffFillOrder FillOrder { get; } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs index 2373e7b9be..1a105e96a9 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors private TiffFillOrder FillOrder { get; } /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { int height = stripHeight; buffer.Clear(); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs index e918837c19..d2461bfa2b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.IO; @@ -31,9 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors => this.options = options; /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer) + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { - using Image image = new WebpDecoder().Decode(this.options, stream, default); + using Image image = new WebpDecoder().Decode(this.options, stream, cancellationToken); CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index d828a7f4f5..aaa3e94993 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -34,13 +35,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// The number of bytes to read from the input stream. /// The height of the strip. /// The output buffer for uncompressed data. - public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer) + /// The token to monitor cancellation. + public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset)); DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); stream.Seek((long)stripOffset, SeekOrigin.Begin); - this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); + this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken); if ((long)stripOffset + (long)stripByteCount < stream.Position) { @@ -55,6 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// The number of bytes to read from the input stream. /// The height of the strip. /// The output buffer for uncompressed data. - protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer); + /// The token to monitor cancellation. + protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index f2e5fc7dec..6b3abbc9e7 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -410,7 +410,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff stripOffsets[stripIndex], stripByteCounts[stripIndex], stripHeight, - stripBuffers[planeIndex].GetSpan()); + stripBuffers[planeIndex].GetSpan(), + cancellationToken); stripIndex += stripsPerPlane; } @@ -498,7 +499,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff stripOffsets[stripIndex], stripByteCounts[stripIndex], stripHeight, - stripBufferSpan); + stripBufferSpan, + cancellationToken); colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index ac775b7121..12f20cd300 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -23,16 +23,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence public void Compress_Decompress_Roundtrip_Works(byte[] data) { - using (BufferedReadStream stream = CreateCompressedStream(data)) - { - var buffer = new byte[data.Length]; + using BufferedReadStream stream = CreateCompressedStream(data); + byte[] buffer = new byte[data.Length]; - using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); + using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); - decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer); + decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); - Assert.Equal(data, buffer); - } + Assert.Equal(data, buffer); } private static BufferedReadStream CreateCompressedStream(byte[] data) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index b2bd316a41..197beade27 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression public void Compress_Works(byte[] inputData, byte[] expectedCompressedData) { - var compressedData = new byte[expectedCompressedData.Length]; + byte[] compressedData = new byte[expectedCompressedData.Length]; Stream streamData = CreateCompressedStream(inputData); streamData.Read(compressedData, 0, expectedCompressedData.Length); @@ -37,10 +37,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression public void Compress_Decompress_Roundtrip_Works(byte[] data) { using BufferedReadStream stream = CreateCompressedStream(data); - var buffer = new byte[data.Length]; + byte[] buffer = new byte[data.Length]; using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); - decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer); + decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); Assert.Equal(data, buffer); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs index 33cac791e6..ca457d15e2 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression byte[] buffer = new byte[expectedResult.Length]; using var decompressor = new NoneTiffCompression(default, default, default); - decompressor.Decompress(stream, 0, byteCount, 1, buffer); + decompressor.Decompress(stream, 0, byteCount, 1, buffer, default); Assert.Equal(expectedResult, buffer); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index 7f1452c418..4ed4330ec5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression byte[] buffer = new byte[expectedResult.Length]; using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default); - decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer); + decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer, default); Assert.Equal(expectedResult, buffer); } From 1d9b20914f1157bf9455b060ad405196604b84b0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 13:36:33 +1000 Subject: [PATCH 48/98] Update TestImageExtensions.cs --- tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index e8d894ce96..a4956ffb7d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -688,6 +688,8 @@ namespace SixLabors.ImageSharp.Tests ImageComparer comparer = customComparer ?? ImageComparer.Exact; comparer.VerifySimilarity(encodedImage, image); + + return actualOutputFile; } internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( From 8826e87718d50b24976e128b73c1e148aac262bd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 21:45:58 +1000 Subject: [PATCH 49/98] Update src/ImageSharp/Formats/DecoderOptions.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Formats/DecoderOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 5e03efb6e9..1fa28983d2 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -37,6 +37,6 @@ namespace SixLabors.ImageSharp.Formats /// /// Gets or sets the maximum number of image frames to decode, inclusive. /// - public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Min(Math.Max(value, 1), int.MaxValue); } + public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); } } } From 2355de686633c416a2600e6e94a65639433dc0aa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 21:46:08 +1000 Subject: [PATCH 50/98] Update src/ImageSharp/Image.FromBytes.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Image.FromBytes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 9635ec76ef..1e33fc8977 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp /// public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan data, out IImageFormat format) { - fixed (byte* ptr = &data.GetPinnableReference()) + fixed (byte* ptr = data) { using var stream = new UnmanagedMemoryStream(ptr, data.Length); return Identify(options, stream, out format); From 01e497f5690d92764a62de3efb8963b1329992e7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 21:46:16 +1000 Subject: [PATCH 51/98] Update src/ImageSharp/Image.FromBytes.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Image.FromBytes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 1e33fc8977..d4743eabf6 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp public static unsafe Image Load(DecoderOptions options, ReadOnlySpan data) where TPixel : unmanaged, IPixel { - fixed (byte* ptr = &data.GetPinnableReference()) + fixed (byte* ptr = data) { using var stream = new UnmanagedMemoryStream(ptr, data.Length); return Load(options, stream); From 91a641301b7216407e2b37cdf2fb4d5d12c6157a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 21:46:21 +1000 Subject: [PATCH 52/98] Update src/ImageSharp/Image.FromBytes.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Image.FromBytes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index d4743eabf6..eace98f99d 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp out IImageFormat format) where TPixel : unmanaged, IPixel { - fixed (byte* ptr = &data.GetPinnableReference()) + fixed (byte* ptr = data) { using var stream = new UnmanagedMemoryStream(ptr, data.Length); return Load(options, stream, out format); From 37d020cb2356b645886e87286ef968819600de83 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 21:46:27 +1000 Subject: [PATCH 53/98] Update src/ImageSharp/Image.FromBytes.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Image.FromBytes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index eace98f99d..341961a2b9 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp ReadOnlySpan data, out IImageFormat format) { - fixed (byte* ptr = &data.GetPinnableReference()) + fixed (byte* ptr = data) { using var stream = new UnmanagedMemoryStream(ptr, data.Length); return Load(options, stream, out format); From 085ff1ae465e67e27a80dae0601a56ae3149c66d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2022 22:07:44 +1000 Subject: [PATCH 54/98] Normalize not loaded exception --- src/ImageSharp/Image.FromStream.cs | 90 +++++++++++------------------- 1 file changed, 33 insertions(+), 57 deletions(-) diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 86baa1c1a9..f7ff9966d5 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using System.Threading; @@ -393,20 +394,12 @@ namespace SixLabors.ImageSharp format = data.Format; - if (data.Image != null) + if (data.Image is null) { - return data.Image; + ThrowNotLoaded(options); } - var sb = new StringBuilder(); - sb.AppendLine("Image cannot be loaded. Available decoders:"); - - foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) - { - sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } - - throw new UnknownImageFormatException(sb.ToString()); + return data.Image; } /// @@ -426,27 +419,16 @@ namespace SixLabors.ImageSharp Stream stream, CancellationToken cancellationToken = default) { - (Image Image, IImageFormat Format) data = await WithSeekableStreamAsync( - options, - stream, - (s, ct) => Decode(options, s, ct), - cancellationToken) - .ConfigureAwait(false); - - if (data.Image != null) - { - return data; - } + (Image Image, IImageFormat Format) data = + await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) + .ConfigureAwait(false); - var sb = new StringBuilder(); - sb.AppendLine("Image cannot be loaded. Available decoders:"); - - foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) + if (data.Image is null) { - sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + ThrowNotLoaded(options); } - throw new UnknownImageFormatException(sb.ToString()); + return data; } /// @@ -469,27 +451,15 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel { (Image Image, IImageFormat Format) data = - await WithSeekableStreamAsync( - options, - stream, - (s, ct) => Decode(options, s, ct), - cancellationToken) - .ConfigureAwait(false); - - if (data.Image != null) - { - return data; - } - - var sb = new StringBuilder(); - sb.AppendLine("Image cannot be loaded. Available decoders:"); + await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) + .ConfigureAwait(false); - foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) + if (data.Image is null) { - sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + ThrowNotLoaded(options); } - throw new UnknownImageFormatException(sb.ToString()); + return data; } /// @@ -512,7 +482,7 @@ namespace SixLabors.ImageSharp where TPixel : unmanaged, IPixel { (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); return img; } @@ -535,20 +505,12 @@ namespace SixLabors.ImageSharp format = fmt; - if (img != null) + if (img is null) { - return img; + ThrowNotLoaded(options); } - var sb = new StringBuilder(); - sb.AppendLine("Image cannot be loaded. Available decoders:"); - - foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) - { - sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } - - throw new UnknownImageFormatException(sb.ToString()); + return img; } /// @@ -633,5 +595,19 @@ namespace SixLabors.ImageSharp return action(memoryStream, cancellationToken); } + + [DoesNotReturn] + private static void ThrowNotLoaded(DecoderOptions options) + { + var sb = new StringBuilder(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); + + foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) + { + sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); + } + + throw new UnknownImageFormatException(sb.ToString()); + } } } From 0472ffd3f177ca1350a756898035a93e8cedd482 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2022 22:15:47 +1000 Subject: [PATCH 55/98] Make default DecoderOptions internal --- src/ImageSharp/Formats/DecoderOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 1fa28983d2..a608fcd524 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats /// /// Gets the shared default general decoder options instance. /// - public static DecoderOptions Default { get; } = LazyOptions.Value; + internal static DecoderOptions Default { get; } = LazyOptions.Value; /// /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. From 95ae124adb34e1870e27bbf452ad0a2f41df368b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 30 Jul 2022 18:22:32 +0200 Subject: [PATCH 56/98] Use DangerousGetRowSpan when run length encode image --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 94 ++++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 16910040c5..91a738c26d 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -117,7 +117,6 @@ namespace SixLabors.ImageSharp.Formats.Tga fileHeader.WriteTo(buffer); stream.Write(buffer, 0, TgaFileHeader.Size); - if (this.compression is TgaCompression.RunLength) { this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); @@ -175,50 +174,50 @@ namespace SixLabors.ImageSharp.Formats.Tga { Rgba32 color = default; Buffer2D pixels = image.PixelBuffer; - int totalPixels = image.Width * image.Height; - int encodedPixels = 0; - while (encodedPixels < totalPixels) + for (int y = 0; y < image.Height; y++) { - int x = encodedPixels % pixels.Width; - int y = encodedPixels / pixels.Width; - TPixel currentPixel = pixels[x, y]; - currentPixel.ToRgba32(ref color); - byte equalPixelCount = this.FindEqualPixels(pixels, x, y); - - // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. - stream.WriteByte((byte)(equalPixelCount | 128)); - switch (this.bitsPerPixel) + Span pixelRow = pixels.DangerousGetRowSpan(y); + for (int x = 0; x < image.Width;) { - case TgaBitsPerPixel.Pixel8: - int luminance = GetLuminance(currentPixel); - stream.WriteByte((byte)luminance); - break; - - case TgaBitsPerPixel.Pixel16: - var bgra5551 = new Bgra5551(color.ToVector4()); - BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); - stream.WriteByte(this.buffer[0]); - stream.WriteByte(this.buffer[1]); - - break; - - case TgaBitsPerPixel.Pixel24: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - break; - - case TgaBitsPerPixel.Pixel32: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - stream.WriteByte(color.A); - break; - default: - break; + TPixel currentPixel = pixelRow[x]; + currentPixel.ToRgba32(ref color); + byte equalPixelCount = this.FindEqualPixels(pixelRow, x); + + // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. + stream.WriteByte((byte)(equalPixelCount | 128)); + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + int luminance = GetLuminance(currentPixel); + stream.WriteByte((byte)luminance); + break; + + case TgaBitsPerPixel.Pixel16: + var bgra5551 = new Bgra5551(color.ToVector4()); + BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); + stream.WriteByte(this.buffer[0]); + stream.WriteByte(this.buffer[1]); + + break; + + case TgaBitsPerPixel.Pixel24: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + break; + + case TgaBitsPerPixel.Pixel32: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + stream.WriteByte(color.A); + break; + default: + break; + } + + x += equalPixelCount + 1; } - - encodedPixels += equalPixelCount + 1; } } @@ -226,18 +225,17 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Finds consecutive pixels which have the same value. /// /// The pixel type. - /// The pixels of the image. + /// A pixel row of the image to encode. /// X coordinate to start searching for the same pixels. - /// Y coordinate to searching for the same pixels in only one scan line. /// The number of equal pixels. - private byte FindEqualPixels(Buffer2D pixels, int xStart, int yPos) + private byte FindEqualPixels(Span pixelRow, int xStart) where TPixel : unmanaged, IPixel { byte equalPixelCount = 0; - TPixel startPixel = pixels[xStart, yPos]; - for (int x = xStart + 1; x < pixels.Width; x++) + TPixel startPixel = pixelRow[xStart]; + for (int x = xStart + 1; x < pixelRow.Length; x++) { - TPixel nextPixel = pixels[x, yPos]; + TPixel nextPixel = pixelRow[x]; if (startPixel.Equals(nextPixel)) { equalPixelCount++; From d1d672801a18c2cb2687a9f1f2cafdc725cd61c5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 30 Jul 2022 18:29:28 +0200 Subject: [PATCH 57/98] A little cleanup --- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 60 ++++++++------------ 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index eaf4e63a0a..18a5d02a64 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Gets the dimensions of the image. /// - public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); + public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tga this.currentStream.Skip(this.fileHeader.IdLength); // Parse the color map, if present. - if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1) + if (this.fileHeader.ColorMapType is not 0 and not 1) { TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); } @@ -308,8 +308,7 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadPalettedRle(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { - int bytesPerPixel = 1; - using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) { TPixel color = default; Span bufferSpan = buffer.GetSpan(); @@ -319,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { int newY = InvertY(y, height, origin); Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width * bytesPerPixel; + int rowStartIdx = y * width; for (int x = 0; x < width; x++) { int idx = rowStartIdx + x; @@ -579,7 +578,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { TPixel color = default; - var alphaBits = this.tgaMetadata.AlphaChannelBits; + byte alphaBits = this.tgaMetadata.AlphaChannelBits; using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) { Span bufferSpan = buffer.GetSpan(); @@ -624,8 +623,8 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; - color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); + byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; + color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha)); } break; @@ -662,7 +661,7 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadL8Pixel(TPixel color, int x, Span pixelSpan) where TPixel : unmanaged, IPixel { - var pixelValue = (byte)this.currentStream.ReadByte(); + byte pixelValue = (byte)this.currentStream.ReadByte(); color.FromL8(Unsafe.As(ref pixelValue)); pixelSpan[x] = color; } @@ -690,7 +689,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(this.scratchBuffer, 0, 4); - var alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; + byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); pixelRow[x] = color; } @@ -757,7 +756,7 @@ namespace SixLabors.ImageSharp.Formats.Tga private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel) { int uncompressedPixels = 0; - var pixel = new byte[bytesPerPixel]; + Span pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel); int totalPixels = width * height; while (uncompressedPixels < totalPixels) { @@ -772,7 +771,7 @@ namespace SixLabors.ImageSharp.Formats.Tga int bufferIdx = uncompressedPixels * bytesPerPixel; for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) { - pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx)); + pixel.CopyTo(buffer.Slice(bufferIdx)); bufferIdx += bytesPerPixel; } } @@ -784,7 +783,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) { this.currentStream.Read(pixel, 0, bytesPerPixel); - pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx)); + pixel.CopyTo(buffer.Slice(bufferIdx)); bufferIdx += bytesPerPixel; } } @@ -815,17 +814,12 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The image origin. /// True, if y coordinate needs to be inverted. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool InvertY(TgaImageOrigin origin) + private static bool InvertY(TgaImageOrigin origin) => origin switch { - switch (origin) - { - case TgaImageOrigin.BottomLeft: - case TgaImageOrigin.BottomRight: - return true; - default: - return false; - } - } + TgaImageOrigin.BottomLeft => true, + TgaImageOrigin.BottomRight => true, + _ => false + }; /// /// Returns the x- value based on the given width. @@ -851,17 +845,13 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The image origin. /// True, if x coordinate needs to be inverted. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool InvertX(TgaImageOrigin origin) - { - switch (origin) + private static bool InvertX(TgaImageOrigin origin) => + origin switch { - case TgaImageOrigin.TopRight: - case TgaImageOrigin.BottomRight: - return true; - default: - return false; - } - } + TgaImageOrigin.TopRight => true, + TgaImageOrigin.BottomRight => true, + _ => false + }; /// /// Reads the tga file header from the stream. @@ -880,8 +870,8 @@ namespace SixLabors.ImageSharp.Formats.Tga this.tgaMetadata = this.metadata.GetTgaMetadata(); this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; - var alphaBits = this.fileHeader.ImageDescriptor & 0xf; - if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8) + int alphaBits = this.fileHeader.ImageDescriptor & 0xf; + if (alphaBits is not 0 and not 1 and not 8) { TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits"); } From 23ae060ebb92080fcb180c9306e3caa4edb144b4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 30 Jul 2022 19:28:50 +0200 Subject: [PATCH 58/98] Throw exception, if not enough data is available in the stream --- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 83 +++++++++++++++++--- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 18a5d02a64..af586e2544 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -117,7 +117,11 @@ namespace SixLabors.ImageSharp.Formats.Tga using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean)) { Span paletteSpan = palette.GetSpan(); - this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); + int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); + if (bytesRead != colorMapSizeInBytes) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map"); + } if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) { @@ -417,7 +421,12 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int x = width - 1; x >= 0; x--) { - this.currentStream.Read(this.scratchBuffer, 0, 2); + int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2); + if (bytesRead != 2) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); + } + if (!this.hasAlpha) { this.scratchBuffer[1] |= 1 << 7; @@ -437,7 +446,11 @@ namespace SixLabors.ImageSharp.Formats.Tga } else { - this.currentStream.Read(rowSpan); + int bytesRead = this.currentStream.Read(rowSpan); + if (bytesRead != rowSpan.Length) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); + } if (!this.hasAlpha) { @@ -652,7 +665,12 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadL8Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { - this.currentStream.Read(row); + int bytesRead = this.currentStream.Read(row); + if (bytesRead != row.Length) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); + } + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width); } @@ -670,7 +688,12 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan) where TPixel : unmanaged, IPixel { - this.currentStream.Read(this.scratchBuffer, 0, 3); + int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3); + if (bytesRead != 3) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel"); + } + color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0])); pixelSpan[x] = color; } @@ -679,7 +702,12 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { - this.currentStream.Read(row); + int bytesRead = this.currentStream.Read(row); + if (bytesRead != row.Length) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); + } + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width); } @@ -688,7 +716,12 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow) where TPixel : unmanaged, IPixel { - this.currentStream.Read(this.scratchBuffer, 0, 4); + int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4); + if (bytesRead != 4) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel"); + } + byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); pixelRow[x] = color; @@ -698,7 +731,12 @@ namespace SixLabors.ImageSharp.Formats.Tga private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y) where TPixel : unmanaged, IPixel { - this.currentStream.Read(row); + int bytesRead = this.currentStream.Read(row); + if (bytesRead != row.Length) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); + } + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width); } @@ -708,6 +746,11 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); + if (colorIndex == -1) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); + } + this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color); pixelRow[x] = color; } @@ -733,6 +776,11 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); + if (colorIndex == -1) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); + } + color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); pixelRow[x] = color; } @@ -742,6 +790,11 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { int colorIndex = this.currentStream.ReadByte(); + if (colorIndex == -1) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); + } + color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); pixelRow[x] = color; } @@ -767,7 +820,12 @@ namespace SixLabors.ImageSharp.Formats.Tga if (highBit == 1) { int runLength = runLengthByte & 127; - this.currentStream.Read(pixel, 0, bytesPerPixel); + int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); + if (bytesRead != bytesPerPixel) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); + } + int bufferIdx = uncompressedPixels * bytesPerPixel; for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) { @@ -782,7 +840,12 @@ namespace SixLabors.ImageSharp.Formats.Tga int bufferIdx = uncompressedPixels * bytesPerPixel; for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) { - this.currentStream.Read(pixel, 0, bytesPerPixel); + int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); + if (bytesRead != bytesPerPixel) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); + } + pixel.CopyTo(buffer.Slice(bufferIdx)); bufferIdx += bytesPerPixel; } From 57c21370a150f6a0c5e607fb38abd3ee43069e3f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Aug 2022 13:39:48 +0200 Subject: [PATCH 59/98] Add test for legacy tga format --- .../Formats/Tga/TgaDecoderTests.cs | 14 ++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Tga/Github_RLE_legacy.tga | 3 +++ 3 files changed, 19 insertions(+) create mode 100644 tests/Images/Input/Tga/Github_RLE_legacy.tga diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index f3aa38df99..57d8aeff51 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -733,6 +733,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } + // Test case for legacy format, when RLE crosses multiple lines: + // https://github.com/SixLabors/ImageSharp/pull/2172 + [Theory] + [WithFile(Github_RLE_legacy, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_LegacyFormat(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) + { + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); + } + } + [Theory] [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 3efb528a82..558a04290b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -544,6 +544,8 @@ namespace SixLabors.ImageSharp.Tests public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga"; public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; + + public const string Github_RLE_legacy = "Tga/Github_RLE_legacy.tga"; } public static class Webp diff --git a/tests/Images/Input/Tga/Github_RLE_legacy.tga b/tests/Images/Input/Tga/Github_RLE_legacy.tga new file mode 100644 index 0000000000..0cb1f73c19 --- /dev/null +++ b/tests/Images/Input/Tga/Github_RLE_legacy.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3570d2883a10a764577dd5174a9168320e8653b220800714da8e3880f752ab5e +size 51253 From 5fff1e424154b794170d937905e84471e0c04710 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Aug 2022 14:56:48 +0200 Subject: [PATCH 60/98] Only write RLE packet, if more then 1 equal pixel is found. Write Raw Packet otherwise --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 129 ++++++++++++++----- 1 file changed, 96 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 91a738c26d..b891885793 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -183,46 +183,76 @@ namespace SixLabors.ImageSharp.Formats.Tga currentPixel.ToRgba32(ref color); byte equalPixelCount = this.FindEqualPixels(pixelRow, x); - // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. - stream.WriteByte((byte)(equalPixelCount | 128)); - switch (this.bitsPerPixel) + if (equalPixelCount > 0) { - case TgaBitsPerPixel.Pixel8: - int luminance = GetLuminance(currentPixel); - stream.WriteByte((byte)luminance); - break; - - case TgaBitsPerPixel.Pixel16: - var bgra5551 = new Bgra5551(color.ToVector4()); - BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); - stream.WriteByte(this.buffer[0]); - stream.WriteByte(this.buffer[1]); - - break; - - case TgaBitsPerPixel.Pixel24: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - break; - - case TgaBitsPerPixel.Pixel32: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - stream.WriteByte(color.A); - break; - default: - break; + // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. + stream.WriteByte((byte)(equalPixelCount | 128)); + this.WritePixel(stream, currentPixel, color); + x += equalPixelCount + 1; + } + else + { + // Write Raw Packet (i.e., Non-Run-Length Encoded): + byte unEqualPixelCount = this.FindUnEqualPixels(pixelRow, x); + stream.WriteByte(unEqualPixelCount); + this.WritePixel(stream, currentPixel, color); + x++; + for (int i = 0; i < unEqualPixelCount; i++) + { + currentPixel = pixelRow[x]; + currentPixel.ToRgba32(ref color); + this.WritePixel(stream, currentPixel, color); + x++; + } } - - x += equalPixelCount + 1; } } } /// - /// Finds consecutive pixels which have the same value. + /// Writes a the pixel to the stream. + /// + /// The type of the pixel. + /// The stream to write to. + /// The current pixel. + /// The color of the pixel to write. + private void WritePixel(Stream stream, TPixel currentPixel, Rgba32 color) + where TPixel : unmanaged, IPixel + { + switch (this.bitsPerPixel) + { + case TgaBitsPerPixel.Pixel8: + int luminance = GetLuminance(currentPixel); + stream.WriteByte((byte)luminance); + break; + + case TgaBitsPerPixel.Pixel16: + var bgra5551 = new Bgra5551(color.ToVector4()); + BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); + stream.WriteByte(this.buffer[0]); + stream.WriteByte(this.buffer[1]); + + break; + + case TgaBitsPerPixel.Pixel24: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + break; + + case TgaBitsPerPixel.Pixel32: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + stream.WriteByte(color.A); + break; + default: + break; + } + } + + /// + /// Finds consecutive pixels which have the same value up to 128 pixels maximum. /// /// The pixel type. /// A pixel row of the image to encode. @@ -254,6 +284,39 @@ namespace SixLabors.ImageSharp.Formats.Tga return equalPixelCount; } + /// + /// Finds consecutive pixels which are unequal up to 128 pixels maximum. + /// + /// The pixel type. + /// A pixel row of the image to encode. + /// X coordinate to start searching for the unequal pixels. + /// The number of equal pixels. + private byte FindUnEqualPixels(Span pixelRow, int xStart) + where TPixel : unmanaged, IPixel + { + byte unEqualPixelCount = 0; + TPixel currentPixel = pixelRow[xStart]; + for (int x = xStart + 1; x < pixelRow.Length; x++) + { + TPixel nextPixel = pixelRow[x]; + if (currentPixel.Equals(nextPixel)) + { + return (byte)Math.Max(0, unEqualPixelCount - 1); + } + + unEqualPixelCount++; + + if (unEqualPixelCount >= 127) + { + return unEqualPixelCount; + } + + currentPixel = nextPixel; + } + + return (byte)Math.Max(0, unEqualPixelCount - 1); + } + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); From e7173328ba393e6f2346221704558e6afd877723 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 3 Aug 2022 14:39:12 +0200 Subject: [PATCH 61/98] Add test making sure not always RLE packets are written --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 4 +-- .../Formats/Tga/TgaEncoderTests.cs | 26 ++++++++++++++----- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tga/whitestripes.png | 3 +++ 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 tests/Images/Input/Tga/whitestripes.png diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index b891885793..fa0ea6f90b 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -301,7 +301,7 @@ namespace SixLabors.ImageSharp.Formats.Tga TPixel nextPixel = pixelRow[x]; if (currentPixel.Equals(nextPixel)) { - return (byte)Math.Max(0, unEqualPixelCount - 1); + return unEqualPixelCount; } unEqualPixelCount++; @@ -314,7 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Tga currentPixel = nextPixel; } - return (byte)Math.Max(0, unEqualPixelCount - 1); + return unEqualPixelCount; } private IMemoryOwner AllocateRow(int width, int bytesPerPixel) diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 12e24622a8..50b4850caf 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -56,12 +56,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [MemberData(nameof(TgaBitsPerPixelFiles))] public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) { - var options = new TgaEncoder() - { - Compression = TgaCompression.RunLength - }; - - TestFile testFile = TestFile.Create(imagePath); + var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; + var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) @@ -121,6 +117,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaEncoder_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + [Theory] + [WithFile(WhiteStripesPattern, PixelTypes.Rgba32, 2748)] + public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets(TestImageProvider provider, int expectedBytes) + where TPixel : unmanaged, IPixel + { + // The test image has alternating black and white pixels, which should make using always RLE data inefficient. + using (Image image = provider.GetImage()) + { + var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; + using (var memStream = new MemoryStream()) + { + image.Save(memStream, options); + byte[] imageBytes = memStream.ToArray(); + Assert.Equal(expectedBytes, imageBytes.Length); + } + } + } + [Theory] [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 558a04290b..306a28dae9 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -546,6 +546,7 @@ namespace SixLabors.ImageSharp.Tests public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; public const string Github_RLE_legacy = "Tga/Github_RLE_legacy.tga"; + public const string WhiteStripesPattern = "Tga/whitestripes.png"; } public static class Webp diff --git a/tests/Images/Input/Tga/whitestripes.png b/tests/Images/Input/Tga/whitestripes.png new file mode 100644 index 0000000000..b7f6c94b48 --- /dev/null +++ b/tests/Images/Input/Tga/whitestripes.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bc5d67ce368d2a40fb99df994c6973287fca2d8c8cff78227996f9acb5c6e1e +size 127 From 343b4afc67cca8aa2ba115f1e6da64b35730f38e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Aug 2022 17:19:28 +0200 Subject: [PATCH 62/98] Add another test case for #2172 --- .../Formats/Tga/TgaEncoderTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 50b4850caf..abe9e2a2d8 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -135,6 +135,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } + // Run length encoded pixels should not exceed row boundaries. + // https://github.com/SixLabors/ImageSharp/pull/2172 + [Fact] + public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries() + { + var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; + + using (var input = new Image(30, 30)) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + byte[] imageBytes = memStream.ToArray(); + Assert.Equal(138, imageBytes.Length); + } + } + } + [Theory] [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] From dd504b2413bdb60dca431e721d7cee244a2b7d40 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 6 Aug 2022 17:56:34 +0300 Subject: [PATCH 63/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [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)); From a9c7f4bb3cb24ffff4b56d5c682d3a4323038d80 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 6 Aug 2022 19:33:16 +0300 Subject: [PATCH 64/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { } - 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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.IO; 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 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // 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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // 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 65/98] 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 66/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { int length = dataLength; // Number of bytes to write. - if (length > MaxData) + if (length > maxData) { - length = MaxData; + length = maxData; } dataLength -= length; @@ -410,9 +410,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { int length = dataLength; // Number of bytes to write. - if (length > MaxData) + if (length > maxData) { - length = MaxData; + length = maxData; } dataLength -= length; @@ -468,7 +468,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg 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 d29cf8abf48d7a6fd57ead59381473cf23263744 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 7 Aug 2022 21:12:06 +1000 Subject: [PATCH 67/98] Implement specialized options for limited types, add extensions. --- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 34 +++-- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 17 ++- src/ImageSharp/Formats/Gif/GifDecoder.cs | 30 +++-- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 12 +- .../Formats/Gif/GifDecoderOptions.cs | 14 --- src/ImageSharp/Formats/IImageDecoder.cs | 6 + ...ernals{T}.cs => IImageDecoderInternals.cs} | 10 +- .../Formats/IImageDecoderSpecialized{T}.cs | 39 ++++++ src/ImageSharp/Formats/IImageInfoDetector.cs | 3 + src/ImageSharp/Formats/ImageDecoder.cs | 65 ++++++++++ .../ImageDecoderSpecializedExtensions.cs | 82 ++++++++++++ .../Formats/ImageDecoderUtilities.cs | 17 ++- src/ImageSharp/Formats/ImageDecoder{T}.cs | 119 ------------------ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 47 ++++--- .../Formats/Jpeg/JpegDecoderCore.cs | 14 ++- .../Formats/Jpeg/JpegDecoderOptions.cs | 5 + .../Formats/Jpeg/JpegDecoderResizeMode.cs | 27 ++++ src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 32 ++--- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 8 +- .../Formats/Pbm/PbmDecoderOptions.cs | 14 --- src/ImageSharp/Formats/Png/PngDecoder.cs | 63 +++++----- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 14 +-- .../Formats/Png/PngDecoderOptions.cs | 14 --- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 30 +++-- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 8 +- .../Formats/Tga/TgaDecoderOptions.cs | 14 --- .../TiffJpegSpectralConverter{TPixel}.cs | 4 +- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 30 +++-- .../Formats/Tiff/TiffDecoderCore.cs | 16 +-- .../Formats/Tiff/TiffDecoderOptions.cs | 14 --- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 31 +++-- .../Formats/Webp/WebpDecoderCore.cs | 12 +- .../Formats/Webp/WebpDecoderOptions.cs | 14 --- src/ImageSharp/Image.FromStream.cs | 4 +- .../Codecs/Jpeg/DecodeJpeg.cs | 1 - tests/ImageSharp.Tests/TestFormat.cs | 17 ++- .../ImageProviders/FileProvider.cs | 6 +- .../ImageProviders/TestImageProvider.cs | 4 +- .../ReferenceCodecs/MagickReferenceDecoder.cs | 19 ++- .../SystemDrawingReferenceDecoder.cs | 15 +-- .../Tests/TestImageProviderTests.cs | 34 +++-- 42 files changed, 524 insertions(+), 437 deletions(-) delete mode 100644 src/ImageSharp/Formats/Gif/GifDecoderOptions.cs rename src/ImageSharp/Formats/{IImageDecoderInternals{T}.cs => IImageDecoderInternals.cs} (88%) create mode 100644 src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs create mode 100644 src/ImageSharp/Formats/ImageDecoder.cs create mode 100644 src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs delete mode 100644 src/ImageSharp/Formats/ImageDecoder{T}.cs create mode 100644 src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs delete mode 100644 src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Png/PngDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 5e8b899a4d..1732a6bdbf 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -210,7 +210,7 @@ namespace SixLabors.ImageSharp.Advanced } /// - /// This method pre-seeds the all in the AoT compiler. + /// This method pre-seeds the all in the AoT compiler. /// /// The pixel format. [Preserve] diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 3474606828..5b0e803f39 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -10,31 +10,41 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Image decoder for generating an image out of a Windows bitmap stream. /// - public class BmpDecoder : ImageDecoder + public class BmpDecoder : ImageDecoder, IImageDecoderSpecialized { /// - public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - BmpDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - Resize(options.GeneralOptions, image); - - return image; + return new BmpDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); } /// - public override Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); /// - public override IImageInfo IdentifySpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + Image image = new BmpDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + Resize(options.GeneralOptions, image); + + return image; } + + /// + public Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index d9e2c17b8e..1adc34849e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// A useful decoding source example can be found at /// - internal sealed class BmpDecoderCore : IImageDecoderInternals + internal sealed class BmpDecoderCore : IImageDecoderInternals { /// /// The default mask for the red part of the color for 16 bit rgb bitmaps. @@ -99,19 +99,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private readonly MemoryAllocator memoryAllocator; + /// + /// How to deal with skipped pixels, + /// which can occur during decoding run length encoded bitmaps. + /// + private readonly RleSkippedPixelHandling rleSkippedPixelHandling; + /// /// Initializes a new instance of the class. /// /// The options. public BmpDecoderCore(BmpDecoderOptions options) { - this.Options = options; + this.Options = options.GeneralOptions; + this.rleSkippedPixelHandling = options.RleSkippedPixelHandling; this.configuration = options.GeneralOptions.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; } /// - public BmpDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); @@ -322,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp byte colorIdx = bufferRow[x]; if (undefinedPixelsSpan[rowStartIdx + x]) { - switch (this.Options.RleSkippedPixelHandling) + switch (this.rleSkippedPixelHandling) { case RleSkippedPixelHandling.FirstColorOfPalette: color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); @@ -394,7 +401,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int idx = rowStartIdx + (x * 3); if (undefinedPixelsSpan[yMulWidth + x]) { - switch (this.Options.RleSkippedPixelHandling) + switch (this.rleSkippedPixelHandling) { case RleSkippedPixelHandling.FirstColorOfPalette: color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 5115486a45..3729e42084 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -10,29 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Decoder for generating an image out of a gif encoded stream. /// - public sealed class GifDecoder : ImageDecoder + public sealed class GifDecoder : ImageDecoder { /// - public override Image DecodeSpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - GifDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - Resize(options.GeneralOptions, image); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return image; + return new GifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); } /// - public override Image DecodeSpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(GifDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new GifDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + GifDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + Resize(options, image); + + return image; } + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index ff4b97b3c8..b307ffd60f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Performs the gif decoding operation. /// - internal sealed class GifDecoderCore : IImageDecoderInternals + internal sealed class GifDecoderCore : IImageDecoderInternals { /// /// The temp buffer used to reduce allocations. @@ -90,17 +90,17 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Initializes a new instance of the class. /// /// The decoder options. - public GifDecoderCore(GifDecoderOptions options) + public GifDecoderCore(DecoderOptions options) { this.Options = options; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - this.maxFrames = options.GeneralOptions.MaxFrames; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.maxFrames = options.MaxFrames; this.memoryAllocator = this.configuration.MemoryAllocator; } /// - public GifDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height); diff --git a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs deleted file mode 100644 index 3c5f7bddc1..0000000000 --- a/src/ImageSharp/Formats/Gif/GifDecoderOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Gif -{ - /// - /// Configuration options for decoding Gif images. - /// - public sealed class GifDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } -} diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index cdec1e9289..b0363ccf9f 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -15,6 +15,9 @@ namespace SixLabors.ImageSharp.Formats /// /// Decodes the image from the specified stream to an of a specific pixel type. /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// /// The pixel format. /// The general decoder options. /// The containing image data. @@ -27,6 +30,9 @@ namespace SixLabors.ImageSharp.Formats /// /// Decodes the image from the specified stream to an . /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// /// The general decoder options. /// The containing image data. /// The token to monitor for cancellation requests. diff --git a/src/ImageSharp/Formats/IImageDecoderInternals{T}.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs similarity index 88% rename from src/ImageSharp/Formats/IImageDecoderInternals{T}.cs rename to src/ImageSharp/Formats/IImageDecoderInternals.cs index 27b8a434b4..63596266fb 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals{T}.cs +++ b/src/ImageSharp/Formats/IImageDecoderInternals.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; @@ -11,14 +11,12 @@ namespace SixLabors.ImageSharp.Formats /// /// Abstraction for shared internals for XXXDecoderCore implementations to be used with . /// - /// The type of specialized decoder options. - internal interface IImageDecoderInternals - where T : ISpecializedDecoderOptions + internal interface IImageDecoderInternals { /// - /// Gets the specialized decoder options. + /// Gets the general decoder options. /// - T Options { get; } + DecoderOptions Options { get; } /// /// Gets the dimensions of the image being decoded. diff --git a/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs b/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs new file mode 100644 index 0000000000..46ad284da3 --- /dev/null +++ b/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// The base class for all specialized image decoders. + /// + /// The type of specialized options. + public interface IImageDecoderSpecialized : IImageDecoder + where T : ISpecializedDecoderOptions + { + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + public Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + public Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 443e8b9809..1bc8a44092 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -14,6 +14,9 @@ namespace SixLabors.ImageSharp.Formats /// /// Reads the raw image information from the specified stream. /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// /// The general decoder options. /// The containing image data. /// The token to monitor for cancellation requests. diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs new file mode 100644 index 0000000000..4863f5b67d --- /dev/null +++ b/src/ImageSharp/Formats/ImageDecoder.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.IO; +using System.Threading; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// The base class for all image decoders. + /// + public abstract class ImageDecoder : IImageInfoDetector, IImageDecoder + { + /// + public abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); + + /// + public abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; + + /// + public abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); + + /// + /// Performs a resize operation against the decoded image. If the target size is not set, or the image size + /// already matches the target size, the image is untouched. + /// + /// The decoder options. + /// The decoded image. + protected static void Resize(DecoderOptions options, Image image) + { + if (ShouldResize(options, image)) + { + ResizeOptions resizeOptions = new() + { + Size = options.TargetSize.Value, + Sampler = KnownResamplers.Box, + Mode = ResizeMode.Max + }; + + image.Mutate(x => x.Resize(resizeOptions)); + } + } + + /// + /// Determines whether the decoded image should be resized. + /// + /// The decoder options. + /// The decoded image. + /// if the image should be resized, otherwise; . + private static bool ShouldResize(DecoderOptions options, Image image) + { + if (options.TargetSize is null) + { + return false; + } + + Size targetSize = options.TargetSize.Value; + Size currentSize = image.Size(); + return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; + } + } +} diff --git a/src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs b/src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs new file mode 100644 index 0000000000..d23f088aaa --- /dev/null +++ b/src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Extensions methods for . + /// + public static class ImageDecoderSpecializedExtensions + { + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The pixel format. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image DecodeSpecialized(this IImageDecoderSpecialized decoder, T options, Stream stream) + where T : ISpecializedDecoderOptions + where TPixel : unmanaged, IPixel + => decoder.DecodeSpecialized(options, stream, default); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image DecodeSpecialized(this IImageDecoderSpecialized decoder, T options, Stream stream) + where T : ISpecializedDecoderOptions + => decoder.DecodeSpecialized(options, stream, default); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The pixel format. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task> DecodeSpecializedAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) + where T : ISpecializedDecoderOptions + where TPixel : unmanaged, IPixel + => Image.WithSeekableStreamAsync( + options.GeneralOptions, + stream, + (s, ct) => decoder.DecodeSpecialized(options, s, ct), + cancellationToken); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task DecodeSpecializedAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) + where T : ISpecializedDecoderOptions + => Image.WithSeekableStreamAsync( + options.GeneralOptions, + stream, + (s, ct) => decoder.DecodeSpecialized(options, s, ct), + cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 1dc0841f28..b6cefd2c89 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -12,12 +12,11 @@ namespace SixLabors.ImageSharp.Formats { internal static class ImageDecoderUtilities { - public static IImageInfo Identify( - this IImageDecoderInternals decoder, + public static IImageInfo Identify( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, CancellationToken cancellationToken) - where T : ISpecializedDecoderOptions { using var bufferedReadStream = new BufferedReadStream(configuration, stream); @@ -31,22 +30,20 @@ namespace SixLabors.ImageSharp.Formats } } - public static Image Decode( - this IImageDecoderInternals decoder, + public static Image Decode( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, CancellationToken cancellationToken) - where T : ISpecializedDecoderOptions where TPixel : unmanaged, IPixel - => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); - public static Image Decode( - this IImageDecoderInternals decoder, + public static Image Decode( + this IImageDecoderInternals decoder, Configuration configuration, Stream stream, Func largeImageExceptionFactory, CancellationToken cancellationToken) - where T : ISpecializedDecoderOptions where TPixel : unmanaged, IPixel { using var bufferedReadStream = new BufferedReadStream(configuration, stream); diff --git a/src/ImageSharp/Formats/ImageDecoder{T}.cs b/src/ImageSharp/Formats/ImageDecoder{T}.cs deleted file mode 100644 index 6f43b053f9..0000000000 --- a/src/ImageSharp/Formats/ImageDecoder{T}.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.IO; -using System.Threading; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats -{ - /// - /// The base class for all image decoders. - /// - /// The type of specialized decoder options. - public abstract class ImageDecoder : IImageInfoDetector, IImageDecoder - where T : class, ISpecializedDecoderOptions, new() - { - /// - public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - T specializedOptions = new() { GeneralOptions = options }; - return this.IdentifySpecialized(specializedOptions, stream, cancellationToken); - } - - /// - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - T specializedOptions = new() { GeneralOptions = options }; - Image image = this.DecodeSpecialized(specializedOptions, stream, cancellationToken); - - Resize(options, image); - - return image; - } - - /// - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - T specializedOptions = new() { GeneralOptions = options }; - Image image = this.DecodeSpecialized(specializedOptions, stream, cancellationToken); - - Resize(options, image); - - return image; - } - - /// - /// Reads the raw image information from the specified stream. - /// - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object. - /// Thrown if the encoded image contains errors. - public abstract IImageInfo IdentifySpecialized(T options, Stream stream, CancellationToken cancellationToken); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - public abstract Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - public abstract Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken); - - /// - /// Performs a resize operation against the decoded image. If the target size is not set, or the image size - /// already matches the target size, the image is untouched. - /// - /// The decoder options. - /// The decoded image. - protected static void Resize(DecoderOptions options, Image image) - { - if (ShouldResize(options, image)) - { - ResizeOptions resizeOptions = new() - { - Size = options.TargetSize.Value, - Sampler = KnownResamplers.Box, - Mode = ResizeMode.Max - }; - - image.Mutate(x => x.Resize(resizeOptions)); - } - } - - /// - /// Determines whether the decoded image should be resized. - /// - /// The decoder options. - /// The decoded image. - /// if the image should be resized, otherwise; . - private static bool ShouldResize(DecoderOptions options, Image image) - { - if (options.TargetSize is null) - { - return false; - } - - Size targetSize = options.TargetSize.Value; - Size currentSize = image.Size(); - return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 1675fd6d74..a655acbce0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -10,38 +10,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Decoder for generating an image out of a jpeg encoded stream. /// - public sealed class JpegDecoder : ImageDecoder + public sealed class JpegDecoder : ImageDecoder, IImageDecoderSpecialized { /// - /// - /// Unlike , when - /// is passed, the codec may not be able to scale efficiently to - /// the exact scale factor requested, so returns a size that approximates that scale. - /// Upscaling is not supported, so the original size will be returned. - /// - public override Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - using JpegDecoderCore decoder = new(options); - return decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + Guard.NotNull(stream, nameof(stream)); + + using JpegDecoderCore decoder = new(new() { GeneralOptions = options }); + return decoder.Identify(options.Configuration, stream, cancellationToken); } /// - /// - /// Unlike , when - /// is passed, the codec may not be able to scale efficiently to - /// the exact scale factor requested, so returns a size that approximates that scale. - /// Upscaling is not supported, so the original size will be returned. - /// - public override Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); /// - public override IImageInfo IdentifySpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); using JpegDecoderCore decoder = new(options); - return decoder.Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + + if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly) + { + Resize(options.GeneralOptions, image); + } + + return image; } + + /// + public Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 30af0b4e26..18201d49b3 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Originally ported from /// with additional fixes for both performance and common encoding errors. /// - internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals + internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals { /// /// The only supported precision @@ -125,19 +125,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private readonly bool skipMetadata; + /// + /// The jpeg specific resize options. + /// + private readonly JpegDecoderResizeMode resizeMode; + /// /// Initializes a new instance of the class. /// /// The decoder options. public JpegDecoderCore(JpegDecoderOptions options) { - this.Options = options; + this.Options = options.GeneralOptions; + this.resizeMode = options.ResizeMode; this.configuration = options.GeneralOptions.Configuration; this.skipMetadata = options.GeneralOptions.SkipMetadata; } /// - public JpegDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => this.Frame.PixelSize; @@ -207,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - using var spectralConverter = new SpectralConverter(this.configuration, this.Options.GeneralOptions.TargetSize); + using var spectralConverter = new SpectralConverter(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize); this.ParseStream(stream, spectralConverter, cancellationToken); this.InitExifProfile(); this.InitIccProfile(); diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs index 80e680cd0e..95d1c81d23 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs @@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public sealed class JpegDecoderOptions : ISpecializedDecoderOptions { + /// + /// Gets or sets the resize mode. + /// + public JpegDecoderResizeMode ResizeMode { get; set; } + /// public DecoderOptions GeneralOptions { get; set; } = new(); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs new file mode 100644 index 0000000000..d99d522c5b --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Provides enumeration for resize modes taken during decoding. + /// Applicable only when has a value. + /// + public enum JpegDecoderResizeMode + { + /// + /// Both and . + /// + Combined, + + /// + /// IDCT-only to nearest block scale. + /// + IdctOnly, + + /// + /// Opt-out the IDCT part and only Resize. Can be useful in case of quality concerns. + /// + ScaleOnly + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 34dca4ca4e..1127f8e090 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -26,29 +26,33 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// The specification of these images is found at . /// - public sealed class PbmDecoder : ImageDecoder + public sealed class PbmDecoder : ImageDecoder { - /// - public override Image DecodeSpecialized(PbmDecoderOptions options, Stream stream, CancellationToken cancellationToken) + /// + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - PbmDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - Resize(options.GeneralOptions, image); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return image; + return new PbmDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); } /// - public override Image DecodeSpecialized(PbmDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(PbmDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new PbmDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + PbmDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + Resize(options, image); + + return image; } + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index cac1a9c640..05f06deaae 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// Performs the PBM decoding operation. /// - internal sealed class PbmDecoderCore : IImageDecoderInternals + internal sealed class PbmDecoderCore : IImageDecoderInternals { private int maxPixelValue; @@ -52,14 +52,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// Initializes a new instance of the class. /// /// The decoder options. - public PbmDecoderCore(PbmDecoderOptions options) + public PbmDecoderCore(DecoderOptions options) { this.Options = options; - this.configuration = options.GeneralOptions.Configuration; + this.configuration = options.Configuration; } /// - public PbmDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => this.pixelSize; diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs deleted file mode 100644 index 1253d72b39..0000000000 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Pbm -{ - /// - /// Configuration options for decoding Pbm images. - /// - public sealed class PbmDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } -} diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 5771e5b09a..9f23982d09 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -10,24 +10,39 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Decoder for generating an image out of a png encoded stream. /// - public sealed class PngDecoder : ImageDecoder + public sealed class PngDecoder : ImageDecoder { /// - public override Image DecodeSpecialized(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new PngDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + PngDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options.GeneralOptions, image); + Resize(options, image); return image; } /// - public override Image DecodeSpecialized(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + PngDecoderCore decoder = new(options, true); - IImageInfo info = decoder.Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + IImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken); stream.Position = 0; PngMetadata meta = info.Metadata.GetPngMetadata(); @@ -39,50 +54,42 @@ namespace SixLabors.ImageSharp.Formats.Png if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.DecodeSpecialized(options, stream, cancellationToken) - : this.DecodeSpecialized(options, stream, cancellationToken); + ? this.Decode(options, stream, cancellationToken) + : this.Decode(options, stream, cancellationToken); } return !meta.HasTransparency - ? this.DecodeSpecialized(options, stream, cancellationToken) - : this.DecodeSpecialized(options, stream, cancellationToken); + ? this.Decode(options, stream, cancellationToken) + : this.Decode(options, stream, cancellationToken); case PngColorType.Rgb: if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.DecodeSpecialized(options, stream, cancellationToken) - : this.DecodeSpecialized(options, stream, cancellationToken); + ? this.Decode(options, stream, cancellationToken) + : this.Decode(options, stream, cancellationToken); } return !meta.HasTransparency - ? this.DecodeSpecialized(options, stream, cancellationToken) - : this.DecodeSpecialized(options, stream, cancellationToken); + ? this.Decode(options, stream, cancellationToken) + : this.Decode(options, stream, cancellationToken); case PngColorType.Palette: - return this.DecodeSpecialized(options, stream, cancellationToken); + return this.Decode(options, stream, cancellationToken); case PngColorType.GrayscaleWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.DecodeSpecialized(options, stream, cancellationToken) - : this.DecodeSpecialized(options, stream, cancellationToken); + ? this.Decode(options, stream, cancellationToken) + : this.Decode(options, stream, cancellationToken); case PngColorType.RgbWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.DecodeSpecialized(options, stream, cancellationToken) - : this.DecodeSpecialized(options, stream, cancellationToken); + ? this.Decode(options, stream, cancellationToken) + : this.Decode(options, stream, cancellationToken); default: - return this.DecodeSpecialized(options, stream, cancellationToken); + return this.Decode(options, stream, cancellationToken); } } - - /// - public override IImageInfo IdentifySpecialized(PngDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(stream, nameof(stream)); - - return new PngDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); - } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 8d6a1719e4..0ec937a207 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Performs the png decoding operation. /// - internal sealed class PngDecoderCore : IImageDecoderInternals + internal sealed class PngDecoderCore : IImageDecoderInternals { /// /// Reusable buffer. @@ -123,25 +123,25 @@ namespace SixLabors.ImageSharp.Formats.Png /// Initializes a new instance of the class. /// /// The decoder options. - public PngDecoderCore(PngDecoderOptions options) + public PngDecoderCore(DecoderOptions options) { this.Options = options; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; this.memoryAllocator = this.configuration.MemoryAllocator; } - internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) + internal PngDecoderCore(DecoderOptions options, bool colorMetadataOnly) { this.Options = options; this.colorMetadataOnly = colorMetadataOnly; this.skipMetadata = true; - this.configuration = options.GeneralOptions.Configuration; + this.configuration = options.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; } /// - public PngDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => new(this.header.Width, this.header.Height); diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs deleted file mode 100644 index 722d803254..0000000000 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png -{ - /// - /// Configuration options for decoding Png images. - /// - public sealed class PngDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } -} diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index ca4d9a5f55..2ad845adb1 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -10,29 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Image decoder for Truevision TGA images. /// - public sealed class TgaDecoder : ImageDecoder + public sealed class TgaDecoder : ImageDecoder { /// - public override Image DecodeSpecialized(TgaDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - TgaDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - Resize(options.GeneralOptions, image); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return image; + return new TgaDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); } /// - public override Image DecodeSpecialized(TgaDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(TgaDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + TgaDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + Resize(options, image); + + return image; } + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 056aa9e840..830f2cc3df 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Performs the tga decoding operation. /// - internal sealed class TgaDecoderCore : IImageDecoderInternals + internal sealed class TgaDecoderCore : IImageDecoderInternals { /// /// A scratch buffer to reduce allocations. @@ -61,15 +61,15 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Initializes a new instance of the class. /// /// The options. - public TgaDecoderCore(TgaDecoderOptions options) + public TgaDecoderCore(DecoderOptions options) { this.Options = options; - this.configuration = options.GeneralOptions.Configuration; + this.configuration = options.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; } /// - public TgaDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height); diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs deleted file mode 100644 index 89ec90430d..0000000000 --- a/src/ImageSharp/Formats/Tga/TgaDecoderOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tga -{ - /// - /// Configuration options for decoding Tga images. - /// - public sealed class TgaDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } -} diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs index ced6b9027c..5d09bd7905 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs @@ -35,10 +35,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); } - /// + /// /// This converter must be used only for RGB and YCbCr color spaces for performance reasons. /// For grayscale images must be used. - /// + /// private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation) => interpretation switch { diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 68d27262f8..012adc5863 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -10,29 +10,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Image decoder for generating an image out of a TIFF stream. /// - public class TiffDecoder : ImageDecoder + public class TiffDecoder : ImageDecoder { /// - public override Image DecodeSpecialized(TiffDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - TiffDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - Resize(options.GeneralOptions, image); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return image; + return new TiffDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); } /// - public override Image DecodeSpecialized(TiffDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(TiffDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new TiffDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + TiffDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + Resize(options, image); + + return image; } + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 6b3abbc9e7..8e3f8c16b7 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Performs the tiff decoding operation. /// - internal class TiffDecoderCore : IImageDecoderInternals + internal class TiffDecoderCore : IImageDecoderInternals { /// /// General configuration options. @@ -61,12 +61,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// Initializes a new instance of the class. /// /// The decoder options. - public TiffDecoderCore(TiffDecoderOptions options) + public TiffDecoderCore(DecoderOptions options) { this.Options = options; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - this.maxFrames = options.GeneralOptions.MaxFrames; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.maxFrames = options.MaxFrames; this.memoryAllocator = this.configuration.MemoryAllocator; } @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff public TiffPredictor Predictor { get; set; } /// - public TiffDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions { get; private set; } @@ -373,7 +373,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Options.GeneralOptions, + this.Options, this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, @@ -454,7 +454,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff Buffer2D pixels = frame.PixelBuffer; using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Options.GeneralOptions, + this.Options, this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs deleted file mode 100644 index b22cf001e1..0000000000 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Tiff -{ - /// - /// Configuration options for decoding Tiff images. - /// - public sealed class TiffDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } -} diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index dc653705d2..b2dbbcba98 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -10,29 +10,34 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Image decoder for generating an image out of a webp stream. /// - public sealed class WebpDecoder : ImageDecoder + public sealed class WebpDecoder : ImageDecoder { /// - public override Image DecodeSpecialized(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - WebpDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - - Resize(options.GeneralOptions, image); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return image; + using WebpDecoderCore decoder = new(options); + return decoder.Identify(options.Configuration, stream, cancellationToken); } /// - public override Image DecodeSpecialized(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - - /// - public override IImageInfo IdentifySpecialized(WebpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { + Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new WebpDecoderCore(options).Identify(options.GeneralOptions.Configuration, stream, cancellationToken); + using WebpDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + Resize(options, image); + + return image; } + + /// + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index ecb7b1b87d..75f5f88f8b 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Performs the webp decoding operation. /// - internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable + internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { /// /// Reusable buffer. @@ -76,17 +76,17 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Initializes a new instance of the class. /// /// The decoder options. - public WebpDecoderCore(WebpDecoderOptions options) + public WebpDecoderCore(DecoderOptions options) { this.Options = options; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - this.maxFrames = options.GeneralOptions.MaxFrames; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.maxFrames = options.MaxFrames; this.memoryAllocator = this.configuration.MemoryAllocator; } /// - public WebpDecoderOptions Options { get; } + public DecoderOptions Options { get; } /// public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs b/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs deleted file mode 100644 index 8e8218472f..0000000000 --- a/src/ImageSharp/Formats/Webp/WebpDecoderOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Webp -{ - /// - /// Configuration options for decoding Webp images. - /// - public sealed class WebpDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } -} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index f7ff9966d5..5c73590aa1 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -521,7 +521,7 @@ namespace SixLabors.ImageSharp /// The input stream. /// The action to perform. /// The . - private static T WithSeekableStream( + internal static T WithSeekableStream( DecoderOptions options, Stream stream, Func action) @@ -562,7 +562,7 @@ namespace SixLabors.ImageSharp /// The action to perform. /// The cancellation token. /// The . - private static async Task WithSeekableStreamAsync( + internal static async Task WithSeekableStreamAsync( DecoderOptions options, Stream stream, Func action, diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 1c8977f5e3..5df2267703 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -65,7 +65,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } - /* BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1348 (20H2/October2020Update) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index eecdb4bb97..96fcb6afd1 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; } - public class TestDecoder : ImageDecoder + public class TestDecoder : ImageDecoder, IImageDecoderSpecialized { private readonly TestFormat testFormat; @@ -208,10 +208,17 @@ namespace SixLabors.ImageSharp.Tests public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - public override IImageInfo IdentifySpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); - public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { Configuration configuration = options.GeneralOptions.Configuration; var ms = new MemoryStream(); @@ -228,7 +235,7 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.DecodeSpecialized(options, stream, cancellationToken); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 1fc0100a69..6cc96dedeb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Tests return Task.FromResult(decoder.Decode(options, stream, default)); } - public override Image GetImage(ImageDecoder decoder, T options) + public override Image GetImage(IImageDecoderSpecialized decoder, T options) { Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(options, nameof(options)); @@ -243,7 +243,7 @@ namespace SixLabors.ImageSharp.Tests return cachedImage.Clone(this.Configuration); } - public override Task> GetImageAsync(ImageDecoder decoder, T options) + public override Task> GetImageAsync(IImageDecoderSpecialized decoder, T options) { Guard.NotNull(decoder, nameof(decoder)); Guard.NotNull(options, nameof(options)); @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Tests return decoder.Decode(options, stream, default); } - private Image DecodeImage(ImageDecoder decoder, T options) + private Image DecodeImage(IImageDecoderSpecialized decoder, T options) where T : class, ISpecializedDecoderOptions, new() { options.GeneralOptions.Configuration = this.Configuration; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 8d186bdab0..bb0e196499 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -101,11 +101,11 @@ namespace SixLabors.ImageSharp.Tests public virtual Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); - public virtual Image GetImage(ImageDecoder decoder, T options) + public virtual Image GetImage(IImageDecoderSpecialized decoder, T options) where T : class, ISpecializedDecoderOptions, new() => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); - public virtual Task> GetImageAsync(ImageDecoder decoder, T options) + public virtual Task> GetImageAsync(IImageDecoderSpecialized decoder, T options) where T : class, ISpecializedDecoderOptions, new() => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 02abbbb430..92b7aeac9b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -15,7 +15,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - public class MagickReferenceDecoder : ImageDecoder + public class MagickReferenceDecoder : ImageDecoder { private readonly bool validate; @@ -28,9 +28,9 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static MagickReferenceDecoder Instance { get; } = new(); - public override Image DecodeSpecialized(MagickReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - Configuration configuration = options.GeneralOptions.Configuration; + Configuration configuration = options.Configuration; var bmpReadDefines = new BmpReadDefines { IgnoreFileSize = !this.validate @@ -70,11 +70,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new Image(configuration, new ImageMetadata(), framesList); } - public override Image DecodeSpecialized(MagickReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); - public override IImageInfo IdentifySpecialized(MagickReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel @@ -106,9 +106,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs } } } - - public class MagickReferenceDecoderOptions : ISpecializedDecoderOptions - { - public DecoderOptions GeneralOptions { get; set; } = new(); - } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 21f0fd8650..ddf09ebddc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -11,18 +11,18 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - public class SystemDrawingReferenceDecoder : ImageDecoder + public class SystemDrawingReferenceDecoder : ImageDecoder { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public override IImageInfo IdentifySpecialized(SystemDrawingReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { using var sourceBitmap = new SDBitmap(stream); PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat)); return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } - public override Image DecodeSpecialized(SystemDrawingReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { using var sourceBitmap = new SDBitmap(stream); if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) @@ -47,12 +47,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); } - public override Image DecodeSpecialized(SystemDrawingReferenceDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); - } - - public class SystemDrawingReferenceDecoderOptions : ISpecializedDecoderOptions - { - public DecoderOptions GeneralOptions { get; set; } = new(); + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index d75ac21dca..9255d5d9fa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp.Tests } } - private class TestDecoder : ImageDecoder + private class TestDecoder : ImageDecoder, IImageDecoderSpecialized { // Couldn't make xUnit happy without this hackery: private static readonly ConcurrentDictionary InvocationCounts = new(); @@ -368,16 +368,23 @@ namespace SixLabors.ImageSharp.Tests } } - public override IImageInfo IdentifySpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); - public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public override Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.DecodeSpecialized(options, stream, cancellationToken); internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; @@ -389,7 +396,7 @@ namespace SixLabors.ImageSharp.Tests } } - private class TestDecoderWithParameters : ImageDecoder + private class TestDecoderWithParameters : ImageDecoder, IImageDecoderSpecialized { private static readonly ConcurrentDictionary InvocationCounts = new(); @@ -405,16 +412,23 @@ namespace SixLabors.ImageSharp.Tests } } - public override IImageInfo IdentifySpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + + public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); - public override Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public override Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + public Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) => this.DecodeSpecialized(options, stream, cancellationToken); internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; From 7f14e347b428800d6e44abcc6d0b6bc3e6f8a00d Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Sat, 6 Aug 2022 21:08:16 +0300 Subject: [PATCH 68/98] Refactor `EnumUtils` and make it AOT-friendly. The `EnumUtils.Parse` method used to use the non-generic `Enum.GetValues` method to do its job, which is unsafe and produces AOT warnings. This PR refactors it to use `Enum.IsDefined` instead, which does not allocate. I also added asserts to methods in `EnumUtils` that ensure we use them with int-sized enums. --- src/ImageSharp/Common/Helpers/EnumUtils.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/EnumUtils.cs b/src/ImageSharp/Common/Helpers/EnumUtils.cs index d6bead6408..a598411629 100644 --- a/src/ImageSharp/Common/Helpers/EnumUtils.cs +++ b/src/ImageSharp/Common/Helpers/EnumUtils.cs @@ -19,15 +19,14 @@ namespace SixLabors.ImageSharp /// The default value to return. /// The . public static TEnum Parse(int value, TEnum defaultValue) - where TEnum : Enum + where TEnum : struct, Enum { - foreach (TEnum enumValue in Enum.GetValues(typeof(TEnum))) + DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); + + TEnum valueEnum = Unsafe.As(ref value); + if (Enum.IsDefined(valueEnum)) { - TEnum current = enumValue; - if (value == Unsafe.As(ref current)) - { - return enumValue; - } + return valueEnum; } return defaultValue; @@ -41,8 +40,10 @@ namespace SixLabors.ImageSharp /// The flag. /// The . public static bool HasFlag(TEnum value, TEnum flag) - where TEnum : Enum + where TEnum : struct, Enum { + DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); + uint flagValue = Unsafe.As(ref flag); return (Unsafe.As(ref value) & flagValue) == flagValue; } From cd68d14be5d458a1dc4a625eef4dc0a688add50b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 7 Aug 2022 15:35:19 +0200 Subject: [PATCH 69/98] Make sure 1 Bit compression is only used with 1 bit pixel type. --- src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 27c4490737..16071001d5 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -417,6 +417,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff return; case TiffPhotometricInterpretation.Rgb: + // Make sure 1 Bit compression is only used with 1 bit pixel type. + if (IsOneBitCompression(this.CompressionType)) + { + // Invalid compression / bits per pixel combination, fallback to no compression. + compression = DefaultCompression; + } + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor); return; } From 4ab4ae23526c8b34489076d20ca43e3c07fc920d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 7 Aug 2022 04:29:02 +0300 Subject: [PATCH 70/98] 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 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder 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; 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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 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 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 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; 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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.IO; 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; 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 71/98] 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 From ac4fb62eeb1d4ab69bd3a016865dfeed0feb8612 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 8 Aug 2022 13:05:36 +1000 Subject: [PATCH 72/98] Only expose sanitized decoder methods. --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 21 +- src/ImageSharp/Formats/Gif/GifDecoder.cs | 12 +- src/ImageSharp/Formats/IImageDecoder.cs | 2 +- .../Formats/IImageDecoderSpecialized{T}.cs | 12 +- src/ImageSharp/Formats/ImageDecoder.cs | 65 ------- .../Formats/ImageDecoderExtensions.cs | 182 ++++++++++++++++++ .../ImageDecoderSpecializedExtensions.cs | 82 -------- .../Formats/ImageDecoderUtilities.cs | 49 ++++- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 21 +- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 12 +- src/ImageSharp/Formats/Png/PngDecoder.cs | 40 ++-- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 12 +- .../Decompressors/WebpTiffCompression.cs | 2 +- src/ImageSharp/Formats/Tiff/TiffDecoder.cs | 12 +- src/ImageSharp/Formats/Webp/WebpDecoder.cs | 12 +- .../Codecs/Jpeg/DecodeJpeg.cs | 2 +- .../Codecs/Jpeg/IdentifyJpeg.cs | 2 +- .../LoadResizeSaveStressRunner.cs | 2 +- .../Formats/Bmp/BmpDecoderTests.cs | 2 +- .../Formats/Gif/GifDecoderTests.cs | 2 +- .../Formats/Gif/GifMetadataTests.cs | 10 +- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 10 +- .../Formats/Png/PngDecoderTests.Chunks.cs | 2 +- .../Formats/Png/PngEncoderTests.cs | 4 +- .../Formats/Png/PngMetadataTests.cs | 8 +- .../Formats/Png/PngSmokeTests.cs | 68 +------ tests/ImageSharp.Tests/TestFormat.cs | 21 +- .../ImageProviders/FileProvider.cs | 4 +- .../ReferenceCodecs/MagickReferenceDecoder.cs | 9 +- .../SystemDrawingReferenceDecoder.cs | 9 +- .../Tests/TestImageProviderTests.cs | 42 ++-- 31 files changed, 379 insertions(+), 354 deletions(-) delete mode 100644 src/ImageSharp/Formats/ImageDecoder.cs create mode 100644 src/ImageSharp/Formats/ImageDecoderExtensions.cs delete mode 100644 src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 5b0e803f39..0b93cf01fc 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Image decoder for generating an image out of a Windows bitmap stream. /// - public class BmpDecoder : ImageDecoder, IImageDecoderSpecialized + public class BmpDecoder : IImageDecoderSpecialized { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -22,29 +22,28 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); /// - public Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + Image IImageDecoderSpecialized.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); Image image = new BmpDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - Resize(options.GeneralOptions, image); + ImageDecoderUtilities.Resize(options.GeneralOptions, image); return image; } /// - public Image DecodeSpecialized(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + Image IImageDecoderSpecialized.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 3729e42084..a737ce74ed 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Decoder for generating an image out of a gif encoded stream. /// - public sealed class GifDecoder : ImageDecoder + public sealed class GifDecoder : IImageDecoder { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Gif GifDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options, image); + ImageDecoderUtilities.Resize(options, image); return image; } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index b0363ccf9f..949e2133ad 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats /// /// Encapsulates properties and methods required for decoding an image from a stream. /// - public interface IImageDecoder + public interface IImageDecoder : IImageInfoDetector { /// /// Decodes the image from the specified stream to an of a specific pixel type. diff --git a/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs b/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs index 46ad284da3..93a3abb7a7 100644 --- a/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs +++ b/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.IO; @@ -17,23 +17,29 @@ namespace SixLabors.ImageSharp.Formats /// /// Decodes the image from the specified stream to an of a specific pixel type. /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// /// The pixel format. /// The specialized decoder options. /// The containing image data. /// The token to monitor for cancellation requests. /// The . /// Thrown if the encoded image contains errors. - public Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken) + public Image Decode(T options, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel; /// /// Decodes the image from the specified stream to an of a specific pixel type. /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// /// The specialized decoder options. /// The containing image data. /// The token to monitor for cancellation requests. /// The . /// Thrown if the encoded image contains errors. - public Image DecodeSpecialized(T options, Stream stream, CancellationToken cancellationToken); + public Image Decode(T options, Stream stream, CancellationToken cancellationToken); } } diff --git a/src/ImageSharp/Formats/ImageDecoder.cs b/src/ImageSharp/Formats/ImageDecoder.cs deleted file mode 100644 index 4863f5b67d..0000000000 --- a/src/ImageSharp/Formats/ImageDecoder.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.IO; -using System.Threading; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats -{ - /// - /// The base class for all image decoders. - /// - public abstract class ImageDecoder : IImageInfoDetector, IImageDecoder - { - /// - public abstract IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - - /// - public abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - - /// - public abstract Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - - /// - /// Performs a resize operation against the decoded image. If the target size is not set, or the image size - /// already matches the target size, the image is untouched. - /// - /// The decoder options. - /// The decoded image. - protected static void Resize(DecoderOptions options, Image image) - { - if (ShouldResize(options, image)) - { - ResizeOptions resizeOptions = new() - { - Size = options.TargetSize.Value, - Sampler = KnownResamplers.Box, - Mode = ResizeMode.Max - }; - - image.Mutate(x => x.Resize(resizeOptions)); - } - } - - /// - /// Determines whether the decoded image should be resized. - /// - /// The decoder options. - /// The decoded image. - /// if the image should be resized, otherwise; . - private static bool ShouldResize(DecoderOptions options, Image image) - { - if (options.TargetSize is null) - { - return false; - } - - Size targetSize = options.TargetSize.Value; - Size currentSize = image.Size(); - return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; - } - } -} diff --git a/src/ImageSharp/Formats/ImageDecoderExtensions.cs b/src/ImageSharp/Formats/ImageDecoderExtensions.cs new file mode 100644 index 0000000000..0e3bcdf9a0 --- /dev/null +++ b/src/ImageSharp/Formats/ImageDecoderExtensions.cs @@ -0,0 +1,182 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Extensions methods for and . + /// + public static class ImageDecoderExtensions + { + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The object. + /// Thrown if the encoded image contains errors. + public static IImageInfo Identify(this IImageDecoder decoder, DecoderOptions options, Stream stream) + => Image.WithSeekableStream( + options, + stream, + s => decoder.Identify(options, s, default)); + + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The object. + /// Thrown if the encoded image contains errors. + public static Task IdentifyAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + => Image.WithSeekableStreamAsync( + options, + stream, + (s, ct) => decoder.Identify(options, s, ct), + cancellationToken); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream) + where TPixel : unmanaged, IPixel + => Image.WithSeekableStream( + options, + stream, + s => decoder.Decode(options, s, default)); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream) + => Image.WithSeekableStream( + options, + stream, + s => decoder.Decode(options, s, default)); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task> DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => Image.WithSeekableStreamAsync( + options, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + => Image.WithSeekableStreamAsync( + options, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The pixel format. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoderSpecialized decoder, T options, Stream stream) + where T : ISpecializedDecoderOptions + where TPixel : unmanaged, IPixel + => Image.WithSeekableStream( + options.GeneralOptions, + stream, + s => decoder.Decode(options, s, default)); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoderSpecialized decoder, T options, Stream stream) + where T : ISpecializedDecoderOptions + => Image.WithSeekableStream( + options.GeneralOptions, + stream, + s => decoder.Decode(options, s, default)); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The pixel format. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task> DecodeAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) + where T : ISpecializedDecoderOptions + where TPixel : unmanaged, IPixel + => Image.WithSeekableStreamAsync( + options.GeneralOptions, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); + + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task DecodeAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) + where T : ISpecializedDecoderOptions + => Image.WithSeekableStreamAsync( + options.GeneralOptions, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs b/src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs deleted file mode 100644 index d23f088aaa..0000000000 --- a/src/ImageSharp/Formats/ImageDecoderSpecializedExtensions.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats -{ - /// - /// Extensions methods for . - /// - public static class ImageDecoderSpecializedExtensions - { - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The pixel format. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public static Image DecodeSpecialized(this IImageDecoderSpecialized decoder, T options, Stream stream) - where T : ISpecializedDecoderOptions - where TPixel : unmanaged, IPixel - => decoder.DecodeSpecialized(options, stream, default); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public static Image DecodeSpecialized(this IImageDecoderSpecialized decoder, T options, Stream stream) - where T : ISpecializedDecoderOptions - => decoder.DecodeSpecialized(options, stream, default); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The pixel format. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public static Task> DecodeSpecializedAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) - where T : ISpecializedDecoderOptions - where TPixel : unmanaged, IPixel - => Image.WithSeekableStreamAsync( - options.GeneralOptions, - stream, - (s, ct) => decoder.DecodeSpecialized(options, s, ct), - cancellationToken); - - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public static Task DecodeSpecializedAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) - where T : ISpecializedDecoderOptions - => Image.WithSeekableStreamAsync( - options.GeneralOptions, - stream, - (s, ct) => decoder.DecodeSpecialized(options, s, ct), - cancellationToken); - } -} diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index b6cefd2c89..2c04820341 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -7,12 +7,55 @@ using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Formats { + /// + /// Utility methods for . + /// internal static class ImageDecoderUtilities { - public static IImageInfo Identify( + /// + /// Performs a resize operation against the decoded image. If the target size is not set, or the image size + /// already matches the target size, the image is untouched. + /// + /// The decoder options. + /// The decoded image. + public static void Resize(DecoderOptions options, Image image) + { + if (ShouldResize(options, image)) + { + ResizeOptions resizeOptions = new() + { + Size = options.TargetSize.Value, + Sampler = KnownResamplers.Box, + Mode = ResizeMode.Max + }; + + image.Mutate(x => x.Resize(resizeOptions)); + } + } + + /// + /// Determines whether the decoded image should be resized. + /// + /// The decoder options. + /// The decoded image. + /// if the image should be resized, otherwise; . + private static bool ShouldResize(DecoderOptions options, Image image) + { + if (options.TargetSize is null) + { + return false; + } + + Size targetSize = options.TargetSize.Value; + Size currentSize = image.Size(); + return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; + } + + internal static IImageInfo Identify( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, @@ -30,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats } } - public static Image Decode( + internal static Image Decode( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, @@ -38,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats where TPixel : unmanaged, IPixel => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); - public static Image Decode( + internal static Image Decode( this IImageDecoderInternals decoder, Configuration configuration, Stream stream, diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index a655acbce0..09b034c79b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Decoder for generating an image out of a jpeg encoded stream. /// - public sealed class JpegDecoder : ImageDecoder, IImageDecoderSpecialized + public sealed class JpegDecoder : IImageDecoderSpecialized { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); @@ -22,16 +22,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); /// - public Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + Image IImageDecoderSpecialized.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -41,14 +40,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly) { - Resize(options.GeneralOptions, image); + ImageDecoderUtilities.Resize(options.GeneralOptions, image); } return image; } /// - public Image DecodeSpecialized(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + Image IImageDecoderSpecialized.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 1127f8e090..394da0a60f 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -26,10 +26,10 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// The specification of these images is found at . /// - public sealed class PbmDecoder : ImageDecoder + public sealed class PbmDecoder : IImageDecoder { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -46,13 +46,13 @@ namespace SixLabors.ImageSharp.Formats.Pbm PbmDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options, image); + ImageDecoderUtilities.Resize(options, image); return image; } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 9f23982d09..2aa5634358 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Decoder for generating an image out of a png encoded stream. /// - public sealed class PngDecoder : ImageDecoder + public sealed class PngDecoder : IImageDecoder { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Png PngDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options, image); + ImageDecoderUtilities.Resize(options, image); return image; } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -48,47 +48,49 @@ namespace SixLabors.ImageSharp.Formats.Png PngMetadata meta = info.Metadata.GetPngMetadata(); PngColorType color = meta.ColorType.GetValueOrDefault(); PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); + + var imageDecoder = (IImageDecoder)this; switch (color) { case PngColorType.Grayscale: if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); case PngColorType.Rgb: if (bits == PngBitDepth.Bit16) { return !meta.HasTransparency - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); } return !meta.HasTransparency - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); case PngColorType.Palette: - return this.Decode(options, stream, cancellationToken); + return imageDecoder.Decode(options, stream, cancellationToken); case PngColorType.GrayscaleWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); case PngColorType.RgbWithAlpha: return (bits == PngBitDepth.Bit16) - ? this.Decode(options, stream, cancellationToken) - : this.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); default: - return this.Decode(options, stream, cancellationToken); + return imageDecoder.Decode(options, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index 2ad845adb1..ac99271cb9 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// Image decoder for Truevision TGA images. /// - public sealed class TgaDecoder : ImageDecoder + public sealed class TgaDecoder : IImageDecoder { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tga TgaDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options, image); + ImageDecoderUtilities.Resize(options, image); return image; } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs index d2461bfa2b..db93406a0d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) { - using Image image = new WebpDecoder().Decode(this.options, stream, cancellationToken); + using Image image = ((IImageDecoder)new WebpDecoder()).Decode(this.options, stream, cancellationToken); CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index 012adc5863..dfbee19a05 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Image decoder for generating an image out of a TIFF stream. /// - public class TiffDecoder : ImageDecoder + public class TiffDecoder : IImageDecoder { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options, image); + ImageDecoderUtilities.Resize(options, image); return image; } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index b2dbbcba98..9b05de5ea3 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -10,10 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// Image decoder for generating an image out of a webp stream. /// - public sealed class WebpDecoder : ImageDecoder + public sealed class WebpDecoder : IImageDecoder { /// - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); @@ -31,13 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Webp using WebpDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - Resize(options, image); + ImageDecoderUtilities.Resize(options, image); return image; } /// - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 5df2267703..1fee65fa0b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private void GenericBechmark() { this.preloadedImageStream.Position = 0; - using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream, default); + using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream); } [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index d1e1f0e77e..a9cbb418a2 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public IImageInfo Identify() { using var memoryStream = new MemoryStream(this.jpegBytes); - var decoder = new JpegDecoder(); + IImageDecoder decoder = new JpegDecoder(); return decoder.Identify(DecoderOptions.Default, memoryStream, default); } } diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index bee55600f3..81884ae3d1 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave }; var decoder = new JpegDecoder(); - using ImageSharpImage image = decoder.Decode(options, inputStream, default); + using ImageSharpImage image = decoder.Decode(options, inputStream); this.LogImageProcessed(image.Width, image.Height); // Reduce the size of the file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 8a5892e027..e303cb5164 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new BmpDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + using Image image = decoder.Decode(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 7c0447a98e..cf6abe23c9 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif fixed (byte* data = testFile.Bytes.AsSpan(0, length)) { using var stream = new UnmanagedMemoryStream(data, length); - using Image image = GifDecoder.Decode(DecoderOptions.Default, stream, default); + using Image image = GifDecoder.Decode(DecoderOptions.Default, stream); Assert.Equal((200, 200), (image.Width, image.Height)); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 23de14b638..56b991a024 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif input.Save(memoryStream, new GifEncoder()); memoryStream.Position = 0; - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream, default); + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); GifMetadata metadata = image.Metadata.GetGifMetadata(); Assert.Equal(2, metadata.Comments.Count); Assert.Equal(new string('c', 349), metadata.Comments[0]); @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new GifDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + using Image image = decoder.Decode(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount); } @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new GifDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + using Image image = decoder.Decode(DecoderOptions.Default, stream); GifMetadata meta = image.Metadata.GetGifMetadata(); Assert.Equal(repeatCount, meta.RepeatCount); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 9046ca14db..d9b07f9b5a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new JpegDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + using Image image = decoder.Decode(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); } @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); - using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream, default); + using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(quality, meta.Quality); } @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream, default); + IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream); JpegMetadata meta = image.Metadata.GetJpegMetadata(); Assert.Equal(expectedColorType, meta.ColorType); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 16627dd34f..9de9f32ec5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); ImageFormatException exception = - Assert.Throws(() => decoder.Decode(DecoderOptions.Default, memStream, default)); + Assert.Throws(() => decoder.Decode(DecoderOptions.Default, memStream)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index a35f8037c5..5e41530cda 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -289,7 +289,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var decoder = new PngDecoder(); - Image image = decoder.Decode(DecoderOptions.Default, stream, default); + Image image = decoder.Decode(DecoderOptions.Default, stream); PngMetadata metadata = image.Metadata.GetPngMetadata(); Assert.Equal(pngColorType, metadata.ColorType); @@ -597,7 +597,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // occurs within the encoder itself leaving the input image unaffected. // This means we are benefiting from testing our decoder also. using FileStream fileStream = File.OpenRead(actualOutputFile); - using Image imageSharpImage = new PngDecoder().Decode(DecoderOptions.Default, fileStream, default); + using Image imageSharpImage = new PngDecoder().Decode(DecoderOptions.Default, fileStream); fileStream.Position = 0; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index fe74c0b8ca..74217c9821 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png input.Save(memoryStream, new PngEncoder()); memoryStream.Position = 0; - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream, default); + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); VerifyTextDataIsPresent(meta); } @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }); memoryStream.Position = 0; - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream, default); + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); Assert.Contains(meta.TextData, m => m.Equals(expectedText)); Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); @@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new PngDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream, default); + using Image image = decoder.Decode(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); var decoder = new PngDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream, default); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); ImageMetadata meta = image.Metadata; Assert.Equal(xResolution, meta.HorizontalResolution); Assert.Equal(yResolution, meta.VerticalResolution); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 934b57b033..58843227d0 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -26,76 +26,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // image.Save(provider.Utility.GetTestOutputFileName("bmp")); image.Save(ms, new PngEncoder()); ms.Position = 0; - using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms, default); + using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms); ImageComparer.Tolerant().VerifySimilarity(image, img2); // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); } - /* JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the - paletted image has alpha of 0 - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void CanSaveIndexedPng(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // does saving a file then reopening mean both files are identical??? - using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) - { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder() { PaletteSize = 256 }); - ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) - { - ImageComparer.VerifySimilarity(image, img2, 0.03f); - } - } - }*/ - - /* JJS: Commented out for now since the test does not take into lossy nature of indexing. - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Color)] - public void CanSaveIndexedPngTwice(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // does saving a file then reopening mean both files are identical??? - using (Image source = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) - { - source.Metadata.Quality = 256; - source.Save(ms, new PngEncoder(), new PngEncoderOptions { - Threshold = 200 - }); - ms.Position = 0; - using (Image img1 = Image.Load(ms, new PngDecoder())) - { - using (MemoryStream ms2 = new MemoryStream()) - { - img1.Save(ms2, new PngEncoder(), new PngEncoderOptions - { - Threshold = 200 - }); - ms2.Position = 0; - using (Image img2 = Image.Load(ms2, new PngDecoder())) - { - using (PixelAccessor pixels1 = img1.Lock()) - using (PixelAccessor pixels2 = img2.Lock()) - { - for (int y = 0; y < img1.Height; y++) - { - for (int x = 0; x < img1.Width; x++) - { - Assert.Equal(pixels1[x, y], pixels2[x, y]); - } - } - } - } - } - } - } - }*/ - [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void Resize(TestImageProvider provider) @@ -111,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); image.Save(ms, new PngEncoder()); ms.Position = 0; - using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms, default); + using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms); ImageComparer.Tolerant().VerifySimilarity(image, img2); } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 96fcb6afd1..9ec7d8f0be 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -194,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; } - public class TestDecoder : ImageDecoder, IImageDecoderSpecialized + public class TestDecoder : IImageDecoderSpecialized { private readonly TestFormat testFormat; @@ -208,16 +208,17 @@ namespace SixLabors.ImageSharp.Tests public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Configuration configuration = options.GeneralOptions.Configuration; @@ -235,8 +236,8 @@ namespace SixLabors.ImageSharp.Tests return this.testFormat.Sample(); } - public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } public class TestDecoderOptions : ISpecializedDecoderOptions diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 6cc96dedeb..733e3a31f5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests // TODO: Check Path here. Why combined? string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); using Stream stream = System.IO.File.OpenRead(path); - return Task.FromResult(decoder.DecodeSpecialized(options, stream, default)); + return Task.FromResult(decoder.Decode(options, stream, default)); } public override void Deserialize(IXunitSerializationInfo info) @@ -286,7 +286,7 @@ namespace SixLabors.ImageSharp.Tests var testFile = TestFile.Create(this.FilePath); using Stream stream = new MemoryStream(testFile.Bytes); - return decoder.DecodeSpecialized(options, stream, default); + return decoder.Decode(options, stream, default); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 92b7aeac9b..1f1d7febe0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -15,7 +15,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - public class MagickReferenceDecoder : ImageDecoder + public class MagickReferenceDecoder : IImageDecoder { private readonly bool validate; @@ -28,7 +28,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static MagickReferenceDecoder Instance { get; } = new(); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { Configuration configuration = options.Configuration; var bmpReadDefines = new BmpReadDefines @@ -70,10 +71,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new Image(configuration, new ImageMetadata(), framesList); } - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.Decode(options, stream, cancellationToken); - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.Decode(options, stream, cancellationToken); private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index ddf09ebddc..e14866f818 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -11,18 +11,19 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { - public class SystemDrawingReferenceDecoder : ImageDecoder + public class SystemDrawingReferenceDecoder : IImageDecoder { public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { using var sourceBitmap = new SDBitmap(stream); PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat)); return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { using var sourceBitmap = new SDBitmap(stream); if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) @@ -47,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); } - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.Decode(options, stream, cancellationToken); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 9255d5d9fa..46e8b34bc1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp.Tests } } - private class TestDecoder : ImageDecoder, IImageDecoderSpecialized + private class TestDecoder : IImageDecoderSpecialized { // Couldn't make xUnit happy without this hackery: private static readonly ConcurrentDictionary InvocationCounts = new(); @@ -368,24 +368,25 @@ namespace SixLabors.ImageSharp.Tests } } - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderOptions)(new() { GeneralOptions = options }), stream, cancellationToken); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.Decode((TestDecoderOptions)(new() { GeneralOptions = options }), stream, cancellationToken); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderOptions)(new() { GeneralOptions = options }), stream, cancellationToken); - public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public Image DecodeSpecialized(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; @@ -396,7 +397,7 @@ namespace SixLabors.ImageSharp.Tests } } - private class TestDecoderWithParameters : ImageDecoder, IImageDecoderSpecialized + private class TestDecoderWithParameters : IImageDecoderSpecialized { private static readonly ConcurrentDictionary InvocationCounts = new(); @@ -412,24 +413,25 @@ namespace SixLabors.ImageSharp.Tests } } - public override IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderWithParametersOptions)(new() { GeneralOptions = options }), stream, cancellationToken); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.Decode((TestDecoderWithParametersOptions)(new() { GeneralOptions = options }), stream, cancellationToken); - public override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderWithParametersOptions)(new() { GeneralOptions = options }), stream, cancellationToken); - public Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { InvocationCounts[this.callerName]++; return new Image(42, 42); } - public Image DecodeSpecialized(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) - => this.DecodeSpecialized(options, stream, cancellationToken); + public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; From 0e13e6c96c8ef1adc9d7f2290785612447f53eec Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 8 Aug 2022 13:45:53 +1000 Subject: [PATCH 73/98] Add sampler option, add comment. --- src/ImageSharp/Formats/DecoderOptions.cs | 7 +++++++ src/ImageSharp/Formats/ImageDecoderUtilities.cs | 2 +- src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index a608fcd524..8f16c6ecc7 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Formats { @@ -29,6 +31,11 @@ namespace SixLabors.ImageSharp.Formats /// public Size? TargetSize { get; set; } = null; + /// + /// Gets or sets the sampler to use when resizing during decoding. + /// + public IResampler Sampler { get; set; } = KnownResamplers.Box; + /// /// Gets or sets a value indicating whether to ignore encoded metadata when decoding. /// diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 2c04820341..37a4ebe5fe 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats ResizeOptions resizeOptions = new() { Size = options.TargetSize.Value, - Sampler = KnownResamplers.Box, + Sampler = options.Sampler, Mode = ResizeMode.Max }; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs index d99d522c5b..5037c85809 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Processing; + namespace SixLabors.ImageSharp.Formats.Jpeg { /// @@ -15,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg Combined, /// - /// IDCT-only to nearest block scale. + /// IDCT-only to nearest block scale. Similar in output to . /// IdctOnly, From f78ec5108f2005e0572962bc4ab470229bdd0df9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 17 Aug 2022 22:30:06 +1000 Subject: [PATCH 74/98] Add jpeg tests --- .../Formats/Gif/GifMetadataTests.cs | 29 +++++ .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 28 ++++- .../Formats/Jpg/JpegDecoderTests.cs | 113 ++++++++++++++++++ ...code_Resize_Bicubic_Calliphora_150_150.png | 3 + ...coder_Decode_Resize_Calliphora_150_150.png | 3 + ...zed_Combined_Resize_Calliphora_150_150.png | 3 + ...ialized_IDCT_Resize_Calliphora_150_150.png | 3 + ...alized_Scale_Resize_Calliphora_150_150.png | 3 + 8 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 56b991a024..d2c667a6b8 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; @@ -126,6 +127,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif Assert.Equal(resolutionUnit, meta.ResolutionUnits); } + [Theory] + [MemberData(nameof(RatioFiles))] + public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + [Theory] [MemberData(nameof(RatioFiles))] public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) @@ -140,6 +155,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif Assert.Equal(resolutionUnit, meta.ResolutionUnits); } + [Theory] + [MemberData(nameof(RatioFiles))] + public async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + using Image image = await decoder.DecodeAsync(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + [Theory] [MemberData(nameof(RepeatFiles))] public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index d9b07f9b5a..46359b167c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; @@ -103,6 +104,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(resolutionUnit, meta.ResolutionUnits); } + [Theory] + [MemberData(nameof(RatioFiles))] + public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + [Theory] [MemberData(nameof(QualityFiles))] public void Identify_VerifyQuality(string imagePath, int quality) @@ -126,6 +141,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(quality, meta.Quality); } + [Theory] + [MemberData(nameof(QualityFiles))] + public async Task Decode_VerifyQualityAsync(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + using Image image = await JpegDecoder.DecodeAsync(DecoderOptions.Default, stream); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } + [Theory] [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)] [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)] @@ -164,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var stream = new MemoryStream(testFile.Bytes, false); if (useIdentify) { - IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(DecoderOptions.Default, stream, default); + IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default); test(imageInfo); } else diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 1706f2e217..684716dcfb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -120,6 +121,118 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg appendPixelTypeToFileName: false); } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + using Image image = provider.GetImage(JpegDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Resize_Bicubic(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 }, + Sampler = KnownResamplers.Bicubic + }; + using Image image = provider.GetImage(JpegDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Specialized_IDCT_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + JpegDecoderOptions specializedOptions = new() + { + GeneralOptions = options, + ResizeMode = JpegDecoderResizeMode.IdctOnly + }; + + using Image image = provider.GetImage(JpegDecoder, specializedOptions); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Specialized_Scale_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + JpegDecoderOptions specializedOptions = new() + { + GeneralOptions = options, + ResizeMode = JpegDecoderResizeMode.ScaleOnly + }; + + using Image image = provider.GetImage(JpegDecoder, specializedOptions); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Specialized_Combined_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + JpegDecoderOptions specializedOptions = new() + { + GeneralOptions = options, + ResizeMode = JpegDecoderResizeMode.Combined + }; + + using Image image = provider.GetImage(JpegDecoder, specializedOptions); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png new file mode 100644 index 0000000000..e982d9034d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bb6ed717a2af582d60ccd6c1c9c1ac92df0f8662755530b7e9063724835b23b +size 27709 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png new file mode 100644 index 0000000000..65aac22e90 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 +size 28178 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png new file mode 100644 index 0000000000..65aac22e90 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 +size 28178 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png new file mode 100644 index 0000000000..abe31b2be7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc67170d70378ad8b8c0e1c1695b5c268341f0d26a6c788d1a8dffa8c90482a0 +size 102165 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png new file mode 100644 index 0000000000..87087adc51 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae7f6ebfd9f2ddd85611827fda13eaf316d36d5187900458568f80b929effb9b +size 28291 From c5c15759c7980681e38d54964c88e7631f4f4be4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 17 Aug 2022 22:39:28 +1000 Subject: [PATCH 75/98] Fix warnings --- .../General/Buffer2D_DangerousGetRowSpan.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs index 68a2bcde97..7712fc4e69 100644 --- a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs +++ b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +11,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General { private const int Height = 1024; - [Params(0.5, 2.0, 10.0)] public double SizeMegaBytes { get; set; } + [Params(0.5, 2.0, 10.0)] + public double SizeMegaBytes { get; set; } private Buffer2D buffer; From a4c14e70f2a78a063a3b2377719461992892ad88 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 20 Aug 2022 11:56:52 +1000 Subject: [PATCH 76/98] Add additional tests --- .../Formats/Gif/GifDecoderTests.cs | 24 +++++++++++++++++- .../Formats/Pbm/PbmDecoderTests.cs | 25 +++++++++++++++++++ .../Formats/Png/PngDecoderTests.cs | 24 +++++++++++++++++- .../Formats/Tga/TgaDecoderTests.cs | 25 ++++++++++++++++++- .../Formats/Tiff/TiffDecoderTests.cs | 23 +++++++++++++++++ .../Formats/WebP/WebpDecoderTests.cs | 24 ++++++++++++++++++ .../TestUtilities/TestEnvironment.Formats.cs | 6 ++--- ...GifDecoder_Decode_Resize_giphy_150_150.png | 3 +++ ...ecoder_Decode_Resize_rgb_plain_150_150.png | 3 +++ ...ngDecoder_Decode_Resize_splash_150_150.png | 3 +++ ...der_Decode_Resize_rgb_a_rle_UL_150_150.png | 3 +++ ...size_RgbaUnassociatedAlpha3bit_150_150.png | 3 +++ ...er_Decode_Resize_bike_lossless_150_150.png | 3 +++ 13 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png create mode 100644 tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index cf6abe23c9..cbca125a53 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -7,7 +7,6 @@ using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -40,6 +39,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void GifDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 }, + MaxFrames = 1 + }; + + using Image image = provider.GetImage(GifDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + [Fact] public unsafe void Decode_NonTerminatedFinalFrame() { diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 570ede8cf1..0d37c6b815 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -1,9 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; @@ -97,5 +100,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm bool isGrayscale = extension is "pgm" or "pbm"; image.CompareToReferenceOutput(provider, grayscale: isGrayscale); } + + [Theory] + [WithFile(RgbPlain, PixelTypes.Rgb24)] + public void PbmDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(new PbmDecoder(), options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 29e0af9c68..7af6a80074 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -5,7 +5,7 @@ using System; using System.IO; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; - +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -112,6 +112,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png image.CompareToOriginal(provider, ImageComparer.Exact); } + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + public void PngDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(PngDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + [Theory] [WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)] [WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 57d8aeff51..8a5412ab76 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -1,8 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using Microsoft.DotNet.RemoteExecutor; - +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -747,6 +748,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } + [Theory] + [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(TgaDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + [Theory] [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 6aaeafdda3..443e42700f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -5,6 +5,7 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -710,5 +711,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSaveMultiFrame(provider); image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); } + + [Theory] + [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(TiffDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 90587e36c5..aae27cae28 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -1,8 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.IO; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -354,6 +356,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.DebugSave(provider); } + [Theory] + [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] + public void WebpDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(WebpDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + // https://github.com/SixLabors/ImageSharp/issues/1594 [Theory] [WithFile(Lossy.Issue1594, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 2b7316f0cc..7e4797c5ab 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -63,8 +63,8 @@ namespace SixLabors.ImageSharp.Tests new WebpConfigurationModule(), new TiffConfigurationModule()); - IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); - IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); + IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); + IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); // Magick codecs should work on all platforms cfg.ConfigureCodecs( @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests cfg.ConfigureCodecs( BmpFormat.Instance, - IsWindows ? (IImageDecoder)SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance, + IsWindows ? SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance, bmpEncoder, new BmpImageFormatDetector()); diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png new file mode 100644 index 0000000000..87c1fb7286 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfd3afda359646aa3d46e1cffbfa7060395fda4c36c419ed3a2d8865284d090d +size 4031 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png new file mode 100644 index 0000000000..b7e848d841 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/PbmDecoder_Decode_Resize_rgb_plain_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd9636f787948659673936798e594a8a100909312350ec9d15da0a3317e6c6b +size 200 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png new file mode 100644 index 0000000000..c697cd4c29 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c87a09b28b3f857e13a2bb420d16caa131a67c45b0fe31404756042f30de6d0 +size 28139 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png new file mode 100644 index 0000000000..f3b7b0e809 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb67006d156cff0fa8d4d1b131ac0eaa98279ae6dcc477818b2fc65f2dfb77aa +size 23396 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png new file mode 100644 index 0000000000..412af0ac7a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53b9dabffaae6a9250bf16f201ef8c9e933327874e8be91785c4fb6cd7e787a8 +size 10767 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png new file mode 100644 index 0000000000..35b99df985 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d73ae8bfad75c8b169fb050653eb4f8351edf173d1cc121ff1e23e3f1e594a97 +size 36342 From acbc7bc580eedacb6c327c0b7e8b949ebd88fb9e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 20 Aug 2022 12:15:37 +1000 Subject: [PATCH 77/98] Update PngDecoderTests.cs --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 7af6a80074..6733146d7f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.Exact, + ImageComparer.TolerantPercentage(0.0004F), // Magick decoder shows difference on Mac provider, testOutputDetails: details, appendPixelTypeToFileName: false); From 62427239b0ae4ca3c1ea710d188699d608d540f1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 20 Aug 2022 12:16:15 +1000 Subject: [PATCH 78/98] Update PngDecoderTests.cs --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 6733146d7f..4be5e4e444 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0004F), // Magick decoder shows difference on Mac + ImageComparer.TolerantPercentage(0.0003F), // Magick decoder shows difference on Mac provider, testOutputDetails: details, appendPixelTypeToFileName: false); From 3b6b30a2f5ebf57ccd9988f082de93fd25b2c54a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 20 Aug 2022 12:59:44 +1000 Subject: [PATCH 79/98] Change percentages for mac --- .../Formats/Gif/GifDecoderTests.cs | 2 +- .../Formats/Tga/TgaDecoderTests.cs | 2 +- .../Formats/WebP/WebpDecoderTests.cs | 2 +- ...ImageDifferenceIsOverThresholdException.cs | 28 +++++++++++++++++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index cbca125a53..3a8ae6f111 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.Exact, + ImageComparer.TolerantPercentage(0.0001F), provider, testOutputDetails: details, appendPixelTypeToFileName: false); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 8a5412ab76..5007aa3719 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -764,7 +764,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.Exact, + ImageComparer.TolerantPercentage(0.0001F), provider, testOutputDetails: details, appendPixelTypeToFileName: false); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index aae27cae28..417d3ce2d2 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -372,7 +372,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.Exact, + ImageComparer.TolerantPercentage(0.0006F), provider, testOutputDetails: details, appendPixelTypeToFileName: false); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index e77dce8612..7560308dbc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -24,8 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); - // TODO: We should add macOS. - sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux"); + sb.AppendFormat("Test Environment OS : {0}", GetEnvironmentName()); sb.Append(Environment.NewLine); sb.AppendFormat("Test Environment is CI : {0}", TestEnvironment.RunsOnCI); @@ -33,11 +32,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is Mono : {0}", TestEnvironment.IsMono); + sb.Append(Environment.NewLine); int i = 0; foreach (ImageSimilarityReport r in reports) { - sb.Append($"Report ImageFrame {i}: "); + sb.Append("Report ImageFrame {i}: "); sb.Append(r); sb.Append(Environment.NewLine); i++; @@ -45,5 +47,25 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison return sb.ToString(); } + + private static string GetEnvironmentName() + { + if (TestEnvironment.IsMacOS) + { + return "Mac OS"; + } + + if (TestEnvironment.IsMacOS) + { + return "Linux"; + } + + if (TestEnvironment.IsWindows) + { + return "Windows"; + } + + return "Unknown"; + } } } From efb2511633f5afff5cfd7bb4e1cd21216e274ab4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 20 Aug 2022 13:00:00 +1000 Subject: [PATCH 80/98] Fix whitespace --- .../Exceptions/ImageDifferenceIsOverThresholdException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 7560308dbc..eb446c3272 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); sb.Append(Environment.NewLine); - + sb.AppendFormat("Test Environment is Mono : {0}", TestEnvironment.IsMono); sb.Append(Environment.NewLine); From 751bf48b35763483775e236ab916091361597aa0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 20 Aug 2022 13:16:28 +1000 Subject: [PATCH 81/98] Tweak WebP test percentage for MacOS --- tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs | 2 +- .../Exceptions/ImageDifferenceIsOverThresholdException.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 417d3ce2d2..ac59189527 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -372,7 +372,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0006F), + ImageComparer.TolerantPercentage(0.0007F), provider, testOutputDetails: details, appendPixelTypeToFileName: false); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index eb446c3272..8857471d03 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { if (TestEnvironment.IsMacOS) { - return "Mac OS"; + return "MacOS"; } if (TestEnvironment.IsMacOS) From 9c7ba12ee28dca474f68bc82ca4f5bdabdeec4e8 Mon Sep 17 00:00:00 2001 From: Luis Miguel Merino Date: Tue, 23 Aug 2022 10:17:18 +0200 Subject: [PATCH 82/98] Fix IPTC tags written on jpg files that contains non-English characters can't be correctly displayed on external apps #2212 --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 74 +++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 72c00302dd..870c4c8ceb 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -20,6 +20,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc private const uint MaxStandardDataTagSize = 0x7FFF; + /// + /// 1:90 Coded Character Set + /// + private const byte IptcEnvelopeCodedCharacterSet = 0x5A; + + /// + /// This value marks that UTF-8 encoding is used in application records + /// + private static readonly byte[] CodedCharacterSetUtf8Value = { 0x1B, 0x25, 0x47 }; + /// /// Initializes a new instance of the class. /// @@ -194,6 +204,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc this.values.Add(new IptcValue(tag, encoding, value, strict)); } + /// + /// Sets the value of the specified tag. + /// + /// The tag of the iptc value. + /// The value. + /// + /// Indicates if length restrictions from the specification should be followed strictly. + /// Defaults to true. + /// + public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict); + /// /// Makes sure the datetime is formatted according to the iptc specification. /// @@ -219,17 +240,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc this.SetValue(tag, Encoding.UTF8, formattedDate); } - /// - /// Sets the value of the specified tag. - /// - /// The tag of the iptc value. - /// The value. - /// - /// Indicates if length restrictions from the specification should be followed strictly. - /// Defaults to true. - /// - public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict); - /// /// Updates the data of the profile. /// @@ -241,9 +251,28 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc length += value.Length + 5; } - this.Data = new byte[length]; + bool hasValuesInUtf8 = this.HasValuesInUtf8(); + + if (hasValuesInUtf8) + { + length += 5 + CodedCharacterSetUtf8Value.Length; // additional length for UTF-8 Tag + } + this.Data = new byte[length]; int i = 0; + + if (hasValuesInUtf8) + { + // Standard DataSet Tag + this.Data[i++] = IptcTagMarkerByte; + this.Data[i++] = 1; // Envelope + this.Data[i++] = IptcEnvelopeCodedCharacterSet; + this.Data[i++] = (byte)(CodedCharacterSetUtf8Value.Length >> 8); + this.Data[i++] = (byte)CodedCharacterSetUtf8Value.Length; + Buffer.BlockCopy(CodedCharacterSetUtf8Value, 0, this.Data, i, CodedCharacterSetUtf8Value.Length); + i += CodedCharacterSetUtf8Value.Length; + } + foreach (IptcValue value in this.Values) { // Standard DataSet Tag @@ -264,7 +293,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc // | | | octet 4(most significant bit) always will be 0. | // +-----------+----------------+---------------------------------------------------------------------------------+ this.Data[i++] = IptcTagMarkerByte; - this.Data[i++] = 2; + this.Data[i++] = 2; // Application this.Data[i++] = (byte)value.Tag; this.Data[i++] = (byte)(value.Length >> 8); this.Data[i++] = (byte)value.Length; @@ -309,7 +338,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount)) { - var iptcData = new byte[byteCount]; + byte[] iptcData = new byte[byteCount]; Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); this.values.Add(new IptcValue(tag, iptcData, false)); } @@ -317,5 +346,22 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc offset += (int)byteCount; } } + + /// + /// Gets if any value has UTF-8 encoding + /// + /// true if any value has UTF-8 encoding + private bool HasValuesInUtf8() + { + foreach (IptcValue value in this.values) + { + if (value.Encoding == Encoding.UTF8) + { + return true; + } + } + + return false; + } } } From a58d8d5e25a34eeb53e0080f85ab324854b17b38 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 00:57:08 +0200 Subject: [PATCH 83/98] Ignore iptc records, which are not application records when reading iptc data --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 870c4c8ceb..c2a94e5b0a 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -21,12 +21,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc private const uint MaxStandardDataTagSize = 0x7FFF; /// - /// 1:90 Coded Character Set + /// 1:90 Coded Character Set. /// private const byte IptcEnvelopeCodedCharacterSet = 0x5A; /// - /// This value marks that UTF-8 encoding is used in application records + /// This value marks that UTF-8 encoding is used in application records. /// private static readonly byte[] CodedCharacterSetUtf8Value = { 0x1B, 0x25, 0x47 }; @@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc if (hasValuesInUtf8) { - length += 5 + CodedCharacterSetUtf8Value.Length; // additional length for UTF-8 Tag + length += 5 + CodedCharacterSetUtf8Value.Length; // Additional length for UTF-8 Tag. } this.Data = new byte[length]; @@ -263,7 +263,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc if (hasValuesInUtf8) { - // Standard DataSet Tag + // Envelope Record. this.Data[i++] = IptcTagMarkerByte; this.Data[i++] = 1; // Envelope this.Data[i++] = IptcEnvelopeCodedCharacterSet; @@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc foreach (IptcValue value in this.Values) { - // Standard DataSet Tag + // Application Record. // +-----------+----------------+---------------------------------------------------------------------------------+ // | Octet Pos | Name | Description | // +==========-+================+=================================================================================+ @@ -327,6 +327,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc bool isValidRecordNumber = recordNumber is >= 1 and <= 9; var tag = (IptcTag)this.Data[offset++]; bool isValidEntry = isValidTagMarker && isValidRecordNumber; + bool isApplicationRecord = recordNumber == 0x02; uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); offset += 2; @@ -336,7 +337,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc break; } - if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount)) + if (isValidEntry && isApplicationRecord && byteCount > 0 && (offset <= this.Data.Length - byteCount)) { byte[] iptcData = new byte[byteCount]; Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); @@ -348,9 +349,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc } /// - /// Gets if any value has UTF-8 encoding + /// Gets if any value has UTF-8 encoding. /// - /// true if any value has UTF-8 encoding + /// true if any value has UTF-8 encoding. private bool HasValuesInUtf8() { foreach (IptcValue value in this.values) From e314305d9646b05047a9e7f16965d9f04865e4b4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 05:30:43 +0200 Subject: [PATCH 84/98] Add enum for RecordNumber --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 7 ++++--- .../Profiles/IPTC/IptcRecordNumber.cs | 21 +++++++++++++++++++ .../Formats/Jpg/JpegEncoderTests.Metadata.cs | 8 ++++--- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index c2a94e5b0a..07b6b73606 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; +using SixLabors.ImageSharp.Metadata.Profiles.IPTC; namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc { @@ -265,7 +266,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc { // Envelope Record. this.Data[i++] = IptcTagMarkerByte; - this.Data[i++] = 1; // Envelope + this.Data[i++] = (byte)IptcRecordNumber.Envelope; this.Data[i++] = IptcEnvelopeCodedCharacterSet; this.Data[i++] = (byte)(CodedCharacterSetUtf8Value.Length >> 8); this.Data[i++] = (byte)CodedCharacterSetUtf8Value.Length; @@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc // | | | octet 4(most significant bit) always will be 0. | // +-----------+----------------+---------------------------------------------------------------------------------+ this.Data[i++] = IptcTagMarkerByte; - this.Data[i++] = 2; // Application + this.Data[i++] = (byte)IptcRecordNumber.Application; this.Data[i++] = (byte)value.Tag; this.Data[i++] = (byte)(value.Length >> 8); this.Data[i++] = (byte)value.Length; @@ -327,7 +328,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc bool isValidRecordNumber = recordNumber is >= 1 and <= 9; var tag = (IptcTag)this.Data[offset++]; bool isValidEntry = isValidTagMarker && isValidRecordNumber; - bool isApplicationRecord = recordNumber == 0x02; + bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application; uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); offset += 2; diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs new file mode 100644 index 0000000000..52e4c47a45 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC +{ + /// + /// Enum for the different record types of a IPTC value. + /// + internal enum IptcRecordNumber : byte + { + /// + /// A Envelope Record. + /// + Envelope = 0x01, + + /// + /// A Application Record. + /// + Application = 0x02 + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 60f45664d3..23b2f749d8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -38,8 +38,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // arrange using var input = new Image(1, 1); - input.Metadata.IptcProfile = new IptcProfile(); - input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); + var expectedProfile = new IptcProfile(); + expectedProfile.SetValue(IptcTag.Country, "ESPAÑA"); + expectedProfile.SetValue(IptcTag.City, "unit-test-city"); + input.Metadata.IptcProfile = expectedProfile; // act using var memStream = new MemoryStream(); @@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var output = Image.Load(memStream); IptcProfile actual = output.Metadata.IptcProfile; Assert.NotNull(actual); - IEnumerable values = input.Metadata.IptcProfile.Values; + IEnumerable values = expectedProfile.Values; Assert.Equal(values, actual.Values); } From 65c3c44ab58086a0b5737277a7747b342bb3747c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 06:17:12 +0200 Subject: [PATCH 85/98] Avoid code duplication --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 07b6b73606..0a6831e9b4 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -256,27 +256,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc if (hasValuesInUtf8) { - length += 5 + CodedCharacterSetUtf8Value.Length; // Additional length for UTF-8 Tag. + // Additional length for UTF-8 Tag. + length += 5 + CodedCharacterSetUtf8Value.Length; } this.Data = new byte[length]; - int i = 0; - + int offset = 0; if (hasValuesInUtf8) { - // Envelope Record. - this.Data[i++] = IptcTagMarkerByte; - this.Data[i++] = (byte)IptcRecordNumber.Envelope; - this.Data[i++] = IptcEnvelopeCodedCharacterSet; - this.Data[i++] = (byte)(CodedCharacterSetUtf8Value.Length >> 8); - this.Data[i++] = (byte)CodedCharacterSetUtf8Value.Length; - Buffer.BlockCopy(CodedCharacterSetUtf8Value, 0, this.Data, i, CodedCharacterSetUtf8Value.Length); - i += CodedCharacterSetUtf8Value.Length; + // Write Envelope Record. + offset = this.WriteRecord(offset, CodedCharacterSetUtf8Value, IptcRecordNumber.Envelope, IptcEnvelopeCodedCharacterSet); } foreach (IptcValue value in this.Values) { - // Application Record. + // Write Application Record. // +-----------+----------------+---------------------------------------------------------------------------------+ // | Octet Pos | Name | Description | // +==========-+================+=================================================================================+ @@ -293,17 +287,24 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| // | | | octet 4(most significant bit) always will be 0. | // +-----------+----------------+---------------------------------------------------------------------------------+ - this.Data[i++] = IptcTagMarkerByte; - this.Data[i++] = (byte)IptcRecordNumber.Application; - this.Data[i++] = (byte)value.Tag; - this.Data[i++] = (byte)(value.Length >> 8); - this.Data[i++] = (byte)value.Length; - if (value.Length > 0) - { - Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length); - i += value.Length; - } + offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag); + } + } + + private int WriteRecord(int offset, byte[] recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation) + { + this.Data[offset++] = IptcTagMarkerByte; + this.Data[offset++] = (byte)recordNumber; + this.Data[offset++] = recordBinaryRepresentation; + this.Data[offset++] = (byte)(recordData.Length >> 8); + this.Data[offset++] = (byte)recordData.Length; + if (recordData.Length > 0) + { + Buffer.BlockCopy(recordData, 0, this.Data, offset, recordData.Length); + offset += recordData.Length; } + + return offset; } private void Initialize() From 288fb0ad514b87254b87fa72802fa67c9d4440c7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 06:30:23 +0200 Subject: [PATCH 86/98] Add test making sure envelope data is written, when UTF-8 data is present --- .../Profiles/IPTC/IptcProfileTests.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index c9972aa25d..70b08b9ec8 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { public class IptcProfileTests { - private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + private static JpegDecoder JpegDecoder => new() { IgnoreMetadata = false }; - private static TiffDecoder TiffDecoder => new TiffDecoder() { IgnoreMetadata = false }; + private static TiffDecoder TiffDecoder => new() { IgnoreMetadata = false }; public static IEnumerable AllIptcTags() { @@ -27,6 +27,22 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC } } + [Fact] + public void IptcProfile_WithUtf8Data_WritesEnvelopeRecord_Works() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.City, "ESPAÑA"); + profile.UpdateData(); + byte[] expectedEnvelopeData = { 28, 1, 90, 0, 3, 27, 37, 71 }; + + // act + byte[] profileBytes = profile.Data; + + // assert + Assert.True(profileBytes.AsSpan(0, 8).SequenceEqual(expectedEnvelopeData)); + } + [Theory] [MemberData(nameof(AllIptcTags))] public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag) From 41bef5bdf779e418655faf921e07fff96d6774d8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 15:19:22 +0200 Subject: [PATCH 87/98] Review suggestions --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 0a6831e9b4..ec3eed2650 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc /// private const byte IptcEnvelopeCodedCharacterSet = 0x5A; - /// - /// This value marks that UTF-8 encoding is used in application records. - /// - private static readonly byte[] CodedCharacterSetUtf8Value = { 0x1B, 0x25, 0x47 }; - /// /// Initializes a new instance of the class. /// @@ -75,6 +70,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc } } + /// + /// Gets a byte array marking that UTF-8 encoding is used in application records. + /// + private static ReadOnlySpan CodedCharacterSetUtf8Value => new byte[] { 0x1B, 0x25, 0x47 }; // Uses C#'s optimization to refer to the data segment in the assembly directly, no allocation occurs. + /// /// Gets the byte data of the IPTC profile. /// @@ -291,16 +291,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc } } - private int WriteRecord(int offset, byte[] recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation) + private int WriteRecord(int offset, ReadOnlySpan recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation) { - this.Data[offset++] = IptcTagMarkerByte; - this.Data[offset++] = (byte)recordNumber; - this.Data[offset++] = recordBinaryRepresentation; - this.Data[offset++] = (byte)(recordData.Length >> 8); - this.Data[offset++] = (byte)recordData.Length; + Span data = this.Data.AsSpan(offset, 5); + data[0] = IptcTagMarkerByte; + data[1] = (byte)recordNumber; + data[2] = recordBinaryRepresentation; + data[3] = (byte)(recordData.Length >> 8); + data[4] = (byte)recordData.Length; + offset += 5; if (recordData.Length > 0) { - Buffer.BlockCopy(recordData, 0, this.Data, offset, recordData.Length); + recordData.CopyTo(this.Data.AsSpan(offset)); offset += recordData.Length; } From 0ee5e4382e00593ca7fb01f80f7ed643e1f5198d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 22:28:04 +0200 Subject: [PATCH 88/98] Set HasTransparency to true, if tRns chunk is found. Fixes #2209 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 784d9aa11f..8e6789132b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Runtime.CompilerServices; @@ -188,6 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); + pngMetadata.HasTransparency = true; break; case PngChunkType.Text: this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); @@ -295,6 +295,7 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); + pngMetadata.HasTransparency = true; if (this.colorMetadataOnly) { @@ -380,8 +381,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Reads the least significant bits from the byte pair with the others set to 0. /// - /// The source buffer - /// THe offset + /// The source buffer. + /// THe offset. /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) @@ -392,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// specified number of bits. /// /// The bytes to convert from. Cannot be empty. - /// The number of bytes per scanline + /// The number of bytes per scanline. /// The number of bits per value. /// The new array. /// The resulting array. From 0e9317153324cd8aecd8d6887d3eb136bc451413 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Aug 2022 22:40:56 +0200 Subject: [PATCH 89/98] Add tests for #2209 --- .../Formats/Png/PngDecoderTests.cs | 24 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 +++ tests/Images/Input/Png/issues/Issue_2209.png | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 tests/Images/Input/Png/issues/Issue_2209.png diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 29e0af9c68..22454d2573 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -470,6 +470,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Assert.Null(ex); } + // https://github.com/SixLabors/ImageSharp/issues/2209 + [Theory] + [WithFile(TestImages.Png.Issue2209IndexedWithTransparency, PixelTypes.Rgba32)] + public void Issue2209_Decode_HasTransparencyIsTrue(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + PngMetadata metadata = image.Metadata.GetPngMetadata(); + Assert.True(metadata.HasTransparency); + } + + // https://github.com/SixLabors/ImageSharp/issues/2209 + [Theory] + [InlineData(TestImages.Png.Issue2209IndexedWithTransparency)] + public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); + Assert.True(metadata.HasTransparency); + } + // https://github.com/SixLabors/ImageSharp/issues/410 [Theory] [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 306a28dae9..ddaf672b43 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -125,6 +125,9 @@ namespace SixLabors.ImageSharp.Tests // Discussion 1875: https://github.com/SixLabors/ImageSharp/discussions/1875 public const string Issue1875 = "Png/raw-profile-type-exif.png"; + // Issue 2209: https://github.com/SixLabors/ImageSharp/issues/2209 + public const string Issue2209IndexedWithTransparency = "Png/issues/Issue_2209.png"; + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; diff --git a/tests/Images/Input/Png/issues/Issue_2209.png b/tests/Images/Input/Png/issues/Issue_2209.png new file mode 100644 index 0000000000..6cc26bce57 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2209.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58eae3863a2107bf3359a87492bc95524b54dd091683e333ac73d76f229a1f71 +size 621275 From e69658ea7340440a97c8fdde84a8079928b5c11e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 29 Aug 2022 10:45:20 +1000 Subject: [PATCH 90/98] Only assign when the relevant detail is there. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 8e6789132b..29b28c9180 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -187,7 +187,6 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); - pngMetadata.HasTransparency = true; break; case PngChunkType.Text: this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); @@ -295,7 +294,6 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); - pngMetadata.HasTransparency = true; if (this.colorMetadataOnly) { @@ -974,6 +972,10 @@ namespace SixLabors.ImageSharp.Formats.Png pngMetadata.HasTransparency = true; } } + else if (this.pngColorType == PngColorType.Palette && alpha.Length > 0) + { + pngMetadata.HasTransparency = true; + } } /// From 7356edd4ea71468d49e4ee933659c95904f805fa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 31 Aug 2022 15:38:13 +0200 Subject: [PATCH 91/98] Add option to write 2 bit bitmap's --- src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs | 5 + src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 125 +++++++++++++----- .../Formats/Bmp/BmpEncoderTests.cs | 52 +++++++- tests/ImageSharp.Tests/TestImages.cs | 2 + .../ImageComparison/ImageComparer.cs | 11 +- tests/Images/Input/Bmp/pal2.bmp | 3 + tests/Images/Input/Bmp/pal2color.bmp | 3 + 8 files changed, 158 insertions(+), 45 deletions(-) create mode 100644 tests/Images/Input/Bmp/pal2.bmp create mode 100644 tests/Images/Input/Bmp/pal2color.bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index 1b73d8b189..f66883c203 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Pixel1 = 1, + /// + /// 2 bits per pixel. + /// + Pixel2 = 2, + /// /// 4 bits per pixel. /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 3a96c40223..517a3b8cfd 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1387,7 +1387,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int colorMapSizeBytes = -1; if (this.infoHeader.ClrUsed == 0) { - if (this.infoHeader.BitsPerPixel is 1 or 4 or 8) + if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8) { switch (this.fileMarkerType) { diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index f71275b7cc..257159bd23 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -57,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private const int ColorPaletteSize4Bit = 64; + /// + /// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize2Bit = 16; + /// /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry. /// @@ -125,19 +130,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); - int colorPaletteSize = 0; - if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8) - { - colorPaletteSize = ColorPaletteSize8Bit; - } - else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4) - { - colorPaletteSize = ColorPaletteSize4Bit; - } - else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1) + int colorPaletteSize = this.bitsPerPixel switch { - colorPaletteSize = ColorPaletteSize1Bit; - } + BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit, + BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit, + BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit, + BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit, + _ => 0 + }; byte[] iccProfileData = null; int iccProfileSize = 0; @@ -322,27 +322,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp switch (this.bitsPerPixel) { case BmpBitsPerPixel.Pixel32: - this.Write32Bit(stream, pixels); + this.Write32BitPixelData(stream, pixels); break; case BmpBitsPerPixel.Pixel24: - this.Write24Bit(stream, pixels); + this.Write24BitPixelData(stream, pixels); break; case BmpBitsPerPixel.Pixel16: - this.Write16Bit(stream, pixels); + this.Write16BitPixelData(stream, pixels); break; case BmpBitsPerPixel.Pixel8: - this.Write8Bit(stream, image); + this.Write8BitPixelData(stream, image); break; case BmpBitsPerPixel.Pixel4: - this.Write4BitColor(stream, image); + this.Write4BitPixelData(stream, image); + break; + + case BmpBitsPerPixel.Pixel2: + this.Write2BitPixelData(stream, image); break; case BmpBitsPerPixel.Pixel1: - this.Write1BitColor(stream, image); + this.Write1BitPixelData(stream, image); break; } } @@ -351,12 +355,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); /// - /// Writes the 32bit color palette to the stream. + /// Writes 32-bit data with a color palette to the stream. /// /// The pixel format. /// The to write to. /// The containing pixel data. - private void Write32Bit(Stream stream, Buffer2D pixels) + private void Write32BitPixelData(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); @@ -375,12 +379,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes the 24bit color palette to the stream. + /// Writes 24-bit pixel data with a color palette to the stream. /// /// The pixel format. /// The to write to. /// The containing pixel data. - private void Write24Bit(Stream stream, Buffer2D pixels) + private void Write24BitPixelData(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { int width = pixels.Width; @@ -401,12 +405,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes the 16bit color palette to the stream. + /// Writes 16-bit pixel data with a color palette to the stream. /// /// The type of the pixel. /// The to write to. /// The containing pixel data. - private void Write16Bit(Stream stream, Buffer2D pixels) + private void Write16BitPixelData(Stream stream, Buffer2D pixels) where TPixel : unmanaged, IPixel { int width = pixels.Width; @@ -429,12 +433,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// /// The type of the pixel. /// The to write to. /// The containing pixel data. - private void Write8Bit(Stream stream, ImageFrame image) + private void Write8BitPixelData(Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { bool isL8 = typeof(TPixel) == typeof(L8); @@ -443,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp if (isL8) { - this.Write8BitGray(stream, image, colorPalette); + this.Write8BitPixelData(stream, image, colorPalette); } else { @@ -480,13 +484,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// /// The type of the pixel. /// The to write to. /// The containing pixel data. /// A byte span of size 1024 for the color palette. - private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette) + private void Write8BitPixelData(Stream stream, ImageFrame image, Span colorPalette) where TPixel : unmanaged, IPixel { // Create a color palette with 256 different gray values. @@ -518,12 +522,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry. + /// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry. /// /// The type of the pixel. /// The to write to. /// The containing pixel data. - private void Write4BitColor(Stream stream, ImageFrame image) + private void Write4BitPixelData(Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() @@ -562,12 +566,65 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry. + /// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write2BitPixelData(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() + { + MaxColors = 4 + }); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize2Bit, AllocationOptions.Clean); + + Span colorPalette = colorPaletteBuffer.GetSpan(); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); + int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding; + for (int y = image.Height - 1; y >= 0; y--) + { + pixelRowSpan = quantized.DangerousGetRowSpan(y); + + int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4; + int i = 0; + for (i = 0; i < endIdx; i += 4) + { + stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3])); + } + + if (pixelRowSpan.Length % 4 != 0) + { + int shift = 6; + byte pixelData = 0; + for (; i < pixelRowSpan.Length; i++) + { + pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift)); + shift -= 2; + } + + stream.WriteByte(pixelData); + } + + for (i = 0; i < rowPadding; i++) + { + stream.WriteByte(0); + } + } + } + + /// + /// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry. /// /// The type of the pixel. /// The to write to. /// The containing pixel data. - private void Write1BitColor(Stream stream, ImageFrame image) + private void Write1BitPixelData(Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() @@ -622,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette); for (int i = 0; i < colorPaletteAsUInt.Length; i++) { - colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0. + colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0. } stream.Write(colorPalette); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index dd59fb2795..fc9554f6a1 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Trait("Format", "Bmp")] public class BmpEncoderTests { + private static BmpDecoder BmpDecoder => new(); + public static readonly TheoryData BitsPerPixel = new() { @@ -39,6 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp new() { { Bit1, BmpBitsPerPixel.Pixel1 }, + { Bit2, BmpBitsPerPixel.Pixel2 }, { Bit4, BmpBitsPerPixel.Pixel4 }, { Bit8, BmpBitsPerPixel.Pixel8 }, { Rgb16, BmpBitsPerPixel.Pixel16 }, @@ -204,6 +207,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer); } + [Theory] + [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)] + public void Encode_2Bit_WithV3Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel }; + using var memoryStream = new MemoryStream(); + using Image input = provider.GetImage(BmpDecoder); + + // act + encoder.Encode(input, memoryStream); + memoryStream.Position = 0; + + // assert + using var actual = Image.Load(memoryStream); + ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); + Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); + } + + [Theory] + [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)] + public void Encode_2Bit_WithV4Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel }; + using var memoryStream = new MemoryStream(); + using Image input = provider.GetImage(BmpDecoder); + + // act + encoder.Encode(input, memoryStream); + memoryStream.Position = 0; + + // assert + using var actual = Image.Load(memoryStream); + ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); + Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); + } + [Theory] [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] public void Encode_1Bit_WithV3Header_Works( @@ -343,7 +390,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header. IQuantizer quantizer = null, - ImageComparer customComparer = null) + ImageComparer customComparer = null, + IImageDecoder referenceDecoder = null) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) @@ -362,7 +410,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer, referenceDecoder: referenceDecoder); } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 306a28dae9..27df82b5f5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -344,6 +344,8 @@ namespace SixLabors.ImageSharp.Tests public const string RLE4Delta = "Bmp/pal4rletrns.bmp"; public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp"; public const string Bit1 = "Bmp/pal1.bmp"; + public const string Bit2 = "Bmp/pal2.bmp"; + public const string Bit2Color = "Bmp/pal2Color.bmp"; public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; public const string Bit4 = "Bmp/pal4.bmp"; public const string Bit8 = "Bmp/test8.bmp"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index cea2784b67..d2750c31c5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -19,10 +19,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// A ImageComparer instance. public static ImageComparer Tolerant( float imageThreshold = TolerantImageComparer.DefaultImageThreshold, - int perPixelManhattanThreshold = 0) - { - return new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); - } + int perPixelManhattanThreshold = 0) => + new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); /// /// Returns Tolerant(imageThresholdInPercents/100) @@ -45,10 +43,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison Image expected, Image actual) where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel - { - return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); - } + where TPixelB : unmanaged, IPixel => comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); public static IEnumerable> CompareImages( this ImageComparer comparer, diff --git a/tests/Images/Input/Bmp/pal2.bmp b/tests/Images/Input/Bmp/pal2.bmp new file mode 100644 index 0000000000..ac351d5fb6 --- /dev/null +++ b/tests/Images/Input/Bmp/pal2.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bac6eec4100831e635fcd34a9e0e34a8a9082abdec132ac327aa1bfc7137d40f +size 2118 diff --git a/tests/Images/Input/Bmp/pal2color.bmp b/tests/Images/Input/Bmp/pal2color.bmp new file mode 100644 index 0000000000..dd7c31bf67 --- /dev/null +++ b/tests/Images/Input/Bmp/pal2color.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ac541592afb207524091aa19d59614851c293193600eacb1170b4854d351dae +size 2118 From 26700fe6b0eb250faaed263b9ad4ef339408e5ff Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 31 Aug 2022 17:07:12 +0200 Subject: [PATCH 92/98] Add decoder tests for 2 bit bitmap's --- .../Formats/Bmp/BmpDecoderTests.cs | 15 +++++++++++++++ .../Formats/Bmp/BmpEncoderTests.cs | 10 ++++------ .../BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png | 3 +++ ...BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png | 3 +++ 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 239e209765..f85219757b 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -131,6 +131,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } } + [Theory] + [WithFile(Bit2, PixelTypes.Rgba32)] + [WithFile(Bit2Color, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_2Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(BmpDecoder)) + { + image.DebugSave(provider); + + // Reference decoder cant decode 2-bit, compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); + } + } + [Theory] [WithFile(Bit4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index fc9554f6a1..665bdd0da8 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { private static BmpDecoder BmpDecoder => new(); + private static BmpEncoder BmpEncoder => new(); + public static readonly TheoryData BitsPerPixel = new() { @@ -53,14 +55,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [MemberData(nameof(RatioFiles))] public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - var options = new BmpEncoder(); - var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, BmpEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) @@ -78,14 +78,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [MemberData(nameof(BmpBitsPerPixelFiles))] public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) { - var options = new BmpEncoder(); - var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, BmpEncoder); memStream.Position = 0; using (var output = Image.Load(memStream)) diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png new file mode 100644 index 0000000000..4a1ac40887 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c3e8b87af737c40d7be02e55a2aec93bb0e7bd123cd1f3e3b74482a0c7d18bd +size 2376 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png new file mode 100644 index 0000000000..6f7bd68696 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3da68e15f4edf6ce5da76360f3704d52baff5292ee12efe5415540b5788dda5 +size 2578 From 3db96c989e70aff06f046d17f5703fcaa35fa31e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 31 Aug 2022 17:21:40 +0200 Subject: [PATCH 93/98] Compare to reference output, when reference decoder is not an option --- .../Formats/Bmp/BmpDecoderTests.cs | 27 ++++++++++--------- ...nDecodeAlphaBitfields_Rgba32_rgba32abf.png | 3 +++ ...der_CanDecode_Os2BitmapArray_Rgba32_9S.png | 3 +++ ...anDecode_Os2BitmapArray_Rgba32_DIAMOND.png | 3 +++ ...anDecode_Os2BitmapArray_Rgba32_GMARBLE.png | 3 +++ ..._CanDecode_Os2BitmapArray_Rgba32_PINES.png | 3 +++ ...CanDecode_Os2BitmapArray_Rgba32_SKATER.png | 3 +++ ..._CanDecode_Os2BitmapArray_Rgba32_SPADE.png | 3 +++ ...anDecode_Os2BitmapArray_Rgba32_SUNFLOW.png | 3 +++ ..._CanDecode_Os2BitmapArray_Rgba32_WARPD.png | 3 +++ ..._CanDecode_Os2BitmapArray_Rgba32_ba-bm.png | 3 +++ ...CanDecode_Os2v2Header_Rgba32_pal8os2v2.png | 3 +++ ..._Os2v2XShortHeader_Rgba32_pal8os2v2-16.png | 3 +++ ...nLengthEncoded_24Bit_Rgba32_rgb24rle24.png | 3 +++ ...LengthEncoded_24Bit_Rgba32_rle24rlecut.png | 3 +++ 15 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png create mode 100644 tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index f85219757b..23a849790d 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -304,8 +304,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { image.DebugSave(provider); - // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. - // image.CompareToOriginal(provider); + // Neither System.Drawing nor MagickReferenceDecoder decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); } } @@ -318,8 +319,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { image.DebugSave(provider); - // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. - // image.CompareToOriginal(provider); + // Neither System.Drawing nor MagickReferenceDecoder decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); } } @@ -592,8 +594,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { image.DebugSave(provider); - // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // image.CompareToOriginal(provider); + // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); } } @@ -606,10 +609,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { image.DebugSave(provider); - // TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it, - // but i think incorrectly. I have loaded the image with GIMP and exported as PNG. - // The results are the same as the image sharp implementation. - // image.CompareToOriginal(provider, new MagickReferenceDecoder()); + // System.Drawing can not decode this image. MagickReferenceDecoder can decode it, + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); } } @@ -630,8 +632,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { image.DebugSave(provider); - // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // image.CompareToOriginal(provider); + // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); } } } diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png new file mode 100644 index 0000000000..40613ca7e5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c7c5d24cf8ba473a22d1c12dcd196f626d2ef056a35bb3ff54b5c84516544bf +size 14547 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png new file mode 100644 index 0000000000..0319b97182 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8597af653507fb625a8f387ce01ab900603086892f046b7b92e6fcf60a636295 +size 884 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png new file mode 100644 index 0000000000..630b44ecd2 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a6989978a0fe36399a774000ee04336d090a4e6a2b63bcbfcd45312ccac4dab +size 648 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png new file mode 100644 index 0000000000..ff484218d3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:951d9d48a5b5df5b70a8c217e2a3d94f4b2c8e8cc63d70cb807627b8e98b8b1d +size 20567 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png new file mode 100644 index 0000000000..78ffbe76c5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35dc46a1f19f3f0a91948bee9b173f6ce264ade69754c01b688e2a878f1374a9 +size 21406 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png new file mode 100644 index 0000000000..7b6ec01dcf --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3191c0ac33c1749f770f96814c0585715aa1c0b085f02256317cedeabc531c12 +size 636 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png new file mode 100644 index 0000000000..89bf24a228 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c5f1adb8b9f0f9a111fdd4b04df023d4239d409f93e2ab5823352c02761118 +size 802 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png new file mode 100644 index 0000000000..5e7a2c071a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f0f9b6a5f1a36596fbe8ac1416e69af82e24c5892a8012a6b68206b6e467bec +size 14190 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png new file mode 100644 index 0000000000..6a62cc9c71 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:452e8aeca41c0899f4e7a4f0458f7cf2dd8002e42a752708d7dd308e040641a0 +size 103892 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png new file mode 100644 index 0000000000..2c9fab29fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e +size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png new file mode 100644 index 0000000000..2c9fab29fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e +size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png new file mode 100644 index 0000000000..2c9fab29fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e +size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png new file mode 100644 index 0000000000..2c9fab29fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e +size 5081 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png new file mode 100644 index 0000000000..8311bc95be --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13d9b630227069f3fd744ef486d64d3f997ee0a9844824e9986c55d754bf413c +size 4379 From 9aa991884935e69c5acef696d6167ac86dc905d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 31 Aug 2022 17:30:01 +0200 Subject: [PATCH 94/98] Throw exception, when not enough data could be read --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 51 ++++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 517a3b8cfd..74b41bb04b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -832,7 +832,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - this.stream.Read(rowSpan); + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + int offset = 0; Span pixelRow = pixels.DangerousGetRowSpan(newY); @@ -884,7 +888,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { - this.stream.Read(bufferSpan); + if (this.stream.Read(bufferSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + int newY = Invert(y, height, inverted); Span pixelRow = pixels.DangerousGetRowSpan(newY); @@ -939,7 +947,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { - this.stream.Read(rowSpan); + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( @@ -967,7 +979,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { - this.stream.Read(rowSpan); + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( @@ -1003,7 +1019,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp // actually a BGRA image, and change tactics accordingly. for (int y = 0; y < height; y++) { - this.stream.Read(rowSpan); + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } PixelOperations.Instance.FromBgra32Bytes( this.Configuration, @@ -1036,7 +1055,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = 0; y < height; y++) { - this.stream.Read(rowSpan); + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); @@ -1054,7 +1076,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp // Slow path. We need to set each alpha component value to fully opaque. for (int y = 0; y < height; y++) { - this.stream.Read(rowSpan); + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + PixelOperations.Instance.FromBgra32Bytes( this.Configuration, rowSpan, @@ -1115,7 +1141,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { - this.stream.Read(bufferSpan); + if (this.stream.Read(bufferSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + int newY = Invert(y, height, inverted); Span pixelRow = pixels.DangerousGetRowSpan(newY); @@ -1431,7 +1461,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp palette = new byte[colorMapSizeBytes]; - this.stream.Read(palette, 0, colorMapSizeBytes); + if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!"); + } } this.infoHeader.VerifyDimensions(); From ee62b83ad937614652c613ee0d50b8f3f1edac51 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 31 Aug 2022 18:03:17 +0200 Subject: [PATCH 95/98] Fix test file name --- tests/ImageSharp.Tests/TestImages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c51ec4acb6..0f9479a764 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp"; public const string Bit1 = "Bmp/pal1.bmp"; public const string Bit2 = "Bmp/pal2.bmp"; - public const string Bit2Color = "Bmp/pal2Color.bmp"; + public const string Bit2Color = "Bmp/pal2color.bmp"; public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; public const string Bit4 = "Bmp/pal4.bmp"; public const string Bit8 = "Bmp/test8.bmp"; From 9d699c34c85e198e01788d3456ba7b6fbd317007 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 31 Aug 2022 19:21:35 +0200 Subject: [PATCH 96/98] Rename test file --- ...l2Color.png => BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/Images/External/ReferenceOutput/BmpDecoderTests/{BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png => BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png} (100%) diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png similarity index 100% rename from tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2Color.png rename to tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png From 976f2a8eacfc7c811c3bc329deaca0d85362c864 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Sep 2022 13:27:12 +1000 Subject: [PATCH 97/98] Bypass constantly failing NET7 Win tests --- .github/workflows/build-and-test.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1a57ec1ed9..440131362f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,19 +20,19 @@ jobs: sdk-preview: true runtime: -x64 codecov: false + - os: macos-latest + framework: net7.0 + sdk: 7.0.x + sdk-preview: true + runtime: -x64 + codecov: false # Temp disabled due to runtime preview issues. - #- os: macos-latest + #- os: windows-latest # framework: net7.0 # sdk: 7.0.x # sdk-preview: true # runtime: -x64 # codecov: false - - os: windows-latest - framework: net7.0 - sdk: 7.0.x - sdk-preview: true - runtime: -x64 - codecov: false - os: ubuntu-latest framework: net6.0 sdk: 6.0.x From af9bfe516eeded3e6a52a7e74cf531af86d00f56 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 1 Sep 2022 13:44:08 +1000 Subject: [PATCH 98/98] Mac fix is not released yet --- .github/workflows/build-and-test.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 440131362f..cd59dca803 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -20,13 +20,14 @@ jobs: sdk-preview: true runtime: -x64 codecov: false - - os: macos-latest - framework: net7.0 - sdk: 7.0.x - sdk-preview: true - runtime: -x64 - codecov: false # Temp disabled due to runtime preview issues. + # https://github.com/SixLabors/ImageSharp/issues/2117 + #- os: macos-latest + # framework: net7.0 + # sdk: 7.0.x + # sdk-preview: true + # runtime: -x64 + # codecov: false #- os: windows-latest # framework: net7.0 # sdk: 7.0.x