diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 808ca687b4..d2fb89caf6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -233,6 +233,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } + // TODO: experimental + 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/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/DirectSpectralConverter{TPixel}.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/DirectSpectralConverter{TPixel}.cs index 430adeb21d..21ce7a16d3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/DirectSpectralConverter{TPixel}.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// /// - internal class SpectralConverter : SpectralConverter, IDisposable + internal class DirectSpectralConverter : SpectralConverter, IDisposable where TPixel : unmanaged, IPixel { /// @@ -67,22 +67,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int pixelRowCounter; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. - public SpectralConverter(Configuration configuration) => + public DirectSpectralConverter(Configuration configuration) => this.configuration = configuration; - /// - /// Gets converted pixel buffer. - /// + /// /// /// For non-baseline interleaved jpeg this method does a 'lazy' spectral /// conversion from spectral to color. /// - /// Cancellation token. - /// Pixel buffer. - public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) + public override Buffer2D GetPixelBuffer(CancellationToken cancellationToken) { if (!this.Converted) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor.cs similarity index 100% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor8.cs new file mode 100644 index 0000000000..47a93c0a1f --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/JpegComponentPostProcessor8.cs @@ -0,0 +1,129 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Encapsulates spectral data to rgba32 processing for one component. + /// + internal class JpegComponentPostProcessor8 : IDisposable + { + /// + /// The size of the area in corresponding to one 8x8 Jpeg block + /// + private readonly Size blockAreaSize; + + /// + /// Jpeg frame instance containing required decoding metadata. + /// + private readonly JpegFrame frame; + + /// + /// Gets the component containing decoding meta information. + /// + private readonly IJpegComponent component; + + /// + /// Gets the instance containing decoding meta information. + /// + private readonly IRawJpegData rawJpeg; + + /// + /// Initializes a new instance of the class. + /// + public JpegComponentPostProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + { + // TODO: this must be a variable depending on dct scale factor + const int blockSize = 1; + + this.frame = frame; + + this.component = component; + this.rawJpeg = rawJpeg; + this.blockAreaSize = this.component.SubSamplingDivisors * blockSize; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, + this.blockAreaSize.Height); + } + + /// + /// Gets the temporary working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } + + /// + public void Dispose() => this.ColorBuffer.Dispose(); + + /// + /// Convert raw spectral DCT data to color data and copy it to the color buffer . + /// + public void CopyBlocksToColorBuffer(int spectralStep) + { + Buffer2D spectralBuffer = this.component.SpectralBlocks; + + float maximumValue = this.frame.MaxColorChannelValue; + + int destAreaStride = this.ColorBuffer.Width; + + int blocksRowsPerStep = this.component.SamplingFactors.Height; + + int yBlockStart = spectralStep * blocksRowsPerStep; + + Size subSamplingDivisors = this.component.SubSamplingDivisors; + + Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex]; + Block8x8F workspaceBlock = default; + + return; + + 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++) + { + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // Dequantize + workspaceBlock.MultiplyInPlace(ref dequantTable); + + // Convert from spectral to color + FastFloatingPointDCT.TransformIDCT(ref workspaceBlock); + + // To conform better to libjpeg we actually NEED TO loose precision here. + // This is because they store blocks as Int16 between all the operations. + // To be "more accurate", we need to emulate this by rounding! + workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); + + // Write to color buffer acording to sampling factors + int xColorBufferStart = xBlock * this.blockAreaSize.Width; + workspaceBlock.ScaledCopyTo( + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); + } + } + } + + public void ClearSpectralBuffers() + { + Buffer2D spectralBlocks = this.component.SpectralBlocks; + for (int i = 0; i < spectralBlocks.Height; i++) + { + spectralBlocks.DangerousGetRowSpan(i).Clear(); + } + } + + public Span GetColorBufferRowSpan(int row) => + this.ColorBuffer.DangerousGetRowSpan(row); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/ResizingSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/ResizingSpectralConverter{TPixel}.cs new file mode 100644 index 0000000000..5089449ef1 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/ResizingSpectralConverter{TPixel}.cs @@ -0,0 +1,258 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + // TODO: docs + internal class ResizingSpectralConverter : SpectralConverter, IDisposable + where TPixel : unmanaged, IPixel + { + /// + /// instance associated with current + /// decoding routine. + /// + private readonly Configuration configuration; + + /// + /// Target image size after scaled decoding. + /// + private readonly Size targetSize; + + /// + /// Jpeg component converters from decompressed spectral to color data. + /// + private JpegComponentPostProcessor8[] componentProcessors; + + /// + /// Resulting 2D pixel buffer. + /// + private Buffer2D pixelBuffer; + + /// + /// How many pixel rows are processed in one 'stride'. + /// + private int pixelRowsPerStep; + + /// + /// How many pixel rows were processed. + /// + private int pixelRowCounter; + + /// + /// Intermediate buffer of RGB components used in color conversion. + /// + private IMemoryOwner rgbBuffer; + + /// + /// Proxy buffer used in packing from RGB to target TPixel pixels. + /// + private IMemoryOwner paddedProxyPixelRow; + + /// + /// Color converter from jpeg color space to target pixel color space. + /// + private JpegColorConverterBase colorConverter; + + public ResizingSpectralConverter(Configuration configuration, Size targetSize) + { + this.configuration = configuration; + this.targetSize = targetSize; + } + + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + MemoryAllocator allocator = this.configuration.MemoryAllocator; + + (int width, int height, int scaleDenominator) = GetScaledImageDimensions(frame.PixelWidth, frame.PixelHeight, this.targetSize.Width, this.targetSize.Height); + + // iteration data + int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); + + this.pixelBuffer = allocator.Allocate2D( + width, + height, + this.configuration.PreferContiguousImageBuffers); + + this.paddedProxyPixelRow = allocator.Allocate(width + 3); + + // single 'stride' rgba32 buffer for conversion between spectral and TPixel + this.rgbBuffer = allocator.Allocate(width * 3); + + // component processors from spectral to Rgba32 + int blockPixelSize = 8 / scaleDenominator; + this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize; + + // BUFFER SIZE MUST BE DIVISIBLE BY 8 ATM + // TODO: fix this mess + int bufferWidth = majorBlockWidth * blockPixelSize; + int correctedBufferWidth = bufferWidth + (8 - (bufferWidth % 8)); + var postProcessorBufferSize = new Size(correctedBufferWidth, this.pixelRowsPerStep); + + this.componentProcessors = new JpegComponentPostProcessor8[frame.Components.Length]; + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i] = new JpegComponentPostProcessor8(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); + } + + // color converter + this.colorConverter = this.GetColorConverter(frame, jpegData); + } + + public override 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); + + foreach (JpegComponentPostProcessor8 cpp in this.componentProcessors) + { + cpp.ClearSpectralBuffers(); + } + } + + /// + /// Converts single spectral jpeg stride to color stride. + /// + /// Spectral stride index. + private void ConvertStride(int spectralStep) + { + int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); + + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); + } + + int width = this.pixelBuffer.Width; + + for (int yy = this.pixelRowCounter; yy < maxY; yy++) + { + int y = yy - this.pixelRowCounter; + + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + + this.colorConverter.ConvertToRgbInplace(values); + values = values.Slice(0, width); // slice away Jpeg padding + + Span r = this.rgbBuffer.Slice(0, width); + Span g = this.rgbBuffer.Slice(width, width); + Span b = this.rgbBuffer.Slice(width * 2, width); + + SimdUtils.NormalizedFloatToByteSaturate(values.Component0.Slice(0, width), r); + SimdUtils.NormalizedFloatToByteSaturate(values.Component1.Slice(0, width), g); + SimdUtils.NormalizedFloatToByteSaturate(values.Component2.Slice(0, width), 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; + } + + public override Buffer2D GetPixelBuffer(CancellationToken cancellationToken) + { + if (!this.Converted) + { + int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); + + for (int step = 0; step < steps; step++) + { + cancellationToken.ThrowIfCancellationRequested(); + this.ConvertStride(step); + } + } + + return this.pixelBuffer; + } + + public void Dispose() + { + if (this.componentProcessors != null) + { + foreach (JpegComponentPostProcessor8 cpp in this.componentProcessors) + { + cpp.Dispose(); + } + } + } + + // TODO: docs, code formatting + private static readonly (int Num, int Denom)[] ScalingFactors = new (int, int)[] + { + /* upscaling factors */ + // (16, 8), + // (15, 8), + // (14, 8), + // (13, 8), + // (12, 8), + // (11, 8), + // (10, 8), + // (9, 8), + + /* no scaling */ + (8, 8), + + /* downscaling factors */ + // (7, 8), // 8 => 7 + // (6, 8), // 8 => 6 + // (5, 8), // 8 => 5 + // (4, 8), // 1/2 dct scaling - currently not supported + // (3, 8), // 8 => 3 + // (2, 8), // 1/4 dct scaling - currently not supported + (1, 8), // 1/8 dct scaling + }; + + /// + /// TODO: docs, code formatting + /// + /// Initial image width. + /// Initial image height. + /// Target image width. + /// Target image height. + private static (int Width, int Height, int ScaleDenominator) GetScaledImageDimensions(int iWidth, int iHeight, int tWidth, int tHeight) + { + int output_width = iWidth; + int output_height = iHeight; + int dct_scale = 8; + + for (int i = 1; i < ScalingFactors.Length; i++) + { + (int num, int denom) = ScalingFactors[i]; + int scaledw = (int)Numerics.DivideCeil((uint)(iWidth * num), (uint)denom); + int scaledh = (int)Numerics.DivideCeil((uint)(iHeight * num), (uint)denom); + + if (scaledw < tWidth || scaledh < tHeight) + { + dct_scale = 8 / ScalingFactors[i - 1].Num; + break; + } + + output_width = scaledw; + output_height = scaledh; + } + + return (output_width, output_height, dct_scale); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter.cs similarity index 100% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter{TPixel}.cs new file mode 100644 index 0000000000..c0ed4c9d84 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConversion/SpectralConverter{TPixel}.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + // TODO: docs + internal abstract class SpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + /// + /// Gets converted pixel buffer. + /// + /// Cancellation token. + /// Pixel buffer. + public abstract Buffer2D GetPixelBuffer(CancellationToken cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 22a9801b8f..295569c980 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -3,6 +3,9 @@ using System.IO; using System.Threading; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -29,6 +32,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(Configuration configuration, Stream stream, CancellationToken cancellationToken) => this.Decode(configuration, stream, cancellationToken); + // TODO: this implementation is experimental + public Image experimental__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); + + // Copied from ImageDecoderUtilities.cs + // TODO: interface cast druing exception handling and code duplication is not okay + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + try + { + return decoder.experimental__DecodeInto(bufferedReadStream, targetSize, cancellationToken); + } + catch (InvalidMemoryOperationException ex) + { + throw new InvalidImageContentException(((IImageDecoderInternals)decoder).Dimensions, ex); + } + } + /// public IImageInfo Identify(Configuration configuration, Stream stream, CancellationToken cancellationToken) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ef4e3ffac2..7709a58be6 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -182,13 +182,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return new JpegFileMarker(marker[1], stream.Position - 2, true); } - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + // TODO: docs + private Image Decode(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - using var spectralConverter = new SpectralConverter(this.Configuration); - - var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); + var scanDecoder = new HuffmanScanDecoder(stream, converter, cancellationToken); this.ParseStream(stream, scanDecoder, cancellationToken); this.InitExifProfile(); @@ -199,10 +197,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return new Image( this.Configuration, - spectralConverter.GetPixelBuffer(cancellationToken), + converter.GetPixelBuffer(cancellationToken), this.Metadata); } + // TODO: docs + public Image experimental__DecodeInto(BufferedReadStream stream, Size targetSize, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using var converter = new ResizingSpectralConverter(this.Configuration, targetSize); + return this.Decode(stream, converter, cancellationToken); + } + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using var converter = new DirectSpectralConverter(this.Configuration); + return this.Decode(stream, converter, cancellationToken); + } + /// public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs index 5b793c35de..e084fdf16d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// Spectral converter for gray TIFF's which use the JPEG compression. /// /// The type of the pixel. - internal sealed class GrayJpegSpectralConverter : SpectralConverter + internal sealed class GrayJpegSpectralConverter : DirectSpectralConverter where TPixel : unmanaged, IPixel { /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index cfbc32f4f6..87c34a8402 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.WhiteIsZero: { - using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration); + using DirectSpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(this.configuration); var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None); @@ -74,8 +74,8 @@ 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 DirectSpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? + new RgbJpegSpectralConverter(this.configuration) : new DirectSpectralConverter(this.configuration); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index a83518064d..3e06152462 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// The jpeg data should be always treated as RGB color space. /// /// The type of the pixel. - internal sealed class RgbJpegSpectralConverter : SpectralConverter + internal sealed class RgbJpegSpectralConverter : DirectSpectralConverter where TPixel : unmanaged, IPixel { /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index 27240831c3..322c044bb6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); // Decoding - using var converter = new SpectralConverter(Configuration.Default); + using var converter = new DirectSpectralConverter(Configuration.Default); using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);