Skip to content

Commit e29a9e8

Browse files
Merge pull request #2076 from br3aker/dp/jpeg-downscaling-decode
Jpeg downscaling decoding
2 parents 2c42e41 + 7a9cf87 commit e29a9e8

32 files changed

+1273
-489
lines changed

src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ public void ParseEntropyCodedData(int scanComponentCount)
247247

248248
this.scanBuffer = new JpegBitReader(this.stream);
249249

250-
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
251-
this.frame.AllocateComponents(fullScan);
250+
this.frame.AllocateComponents();
252251

253252
if (this.frame.Progressive)
254253
{
@@ -326,11 +325,13 @@ private void ParseBaselineData()
326325

327326
if (this.scanComponentCount != 1)
328327
{
328+
this.spectralConverter.PrepareForDecoding();
329329
this.ParseBaselineDataInterleaved();
330330
this.spectralConverter.CommitConversion();
331331
}
332332
else if (this.frame.ComponentCount == 1)
333333
{
334+
this.spectralConverter.PrepareForDecoding();
334335
this.ParseBaselineDataSingleComponent();
335336
this.spectralConverter.CommitConversion();
336337
}

src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33
#if SUPPORTS_RUNTIME_INTRINSICS
4+
using System.Runtime.Intrinsics;
45
using System.Runtime.Intrinsics.X86;
56

67
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
@@ -25,7 +26,9 @@ protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision)
2526
{
2627
}
2728

28-
public override bool IsAvailable => Avx.IsSupported;
29+
public sealed override bool IsAvailable => Avx.IsSupported;
30+
31+
public sealed override int ElementsPerBatch => Vector256<float>.Count;
2932
}
3033
}
3134
}

src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision)
3535
/// </summary>
3636
public abstract bool IsAvailable { get; }
3737

38+
/// <summary>
39+
/// Gets a value indicating how many pixels are processed in a single batch.
40+
/// </summary>
41+
/// <remarks>
42+
/// This generally should be equal to register size,
43+
/// e.g. 1 for scalar implementation, 8 for AVX implementation and so on.
44+
/// </remarks>
45+
public abstract int ElementsPerBatch { get; }
46+
3847
/// <summary>
3948
/// Gets the <see cref="JpegColorSpace"/> of this converter.
4049
/// </summary>
@@ -219,7 +228,7 @@ public ComponentValues(IReadOnlyList<Buffer2D<float>> componentBuffers, int row)
219228
/// </summary>
220229
/// <param name="processors">List of component color processors.</param>
221230
/// <param name="row">Row to convert</param>
222-
public ComponentValues(IReadOnlyList<JpegComponentPostProcessor> processors, int row)
231+
public ComponentValues(IReadOnlyList<ComponentProcessor> processors, int row)
223232
{
224233
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));
225234

src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision)
1616
{
1717
}
1818

19-
public override bool IsAvailable => true;
19+
public sealed override bool IsAvailable => true;
20+
21+
public sealed override int ElementsPerBatch => 1;
2022
}
2123
}
2224
}

src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal abstract partial class JpegColorConverterBase
1717
/// Even though real life data is guaranteed to be of size
1818
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
1919
/// such data out of the box. These converters have fallback code
20-
/// for 'remainder' data.
20+
/// for remainder data.
2121
/// </remarks>
2222
internal abstract class JpegColorConverterVector : JpegColorConverterBase
2323
{
@@ -28,7 +28,9 @@ protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision)
2828

2929
public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector<float>.Count % 4 == 0;
3030

31-
public override void ConvertToRgbInplace(in ComponentValues values)
31+
public sealed override int ElementsPerBatch => Vector<float>.Count;
32+
33+
public sealed override void ConvertToRgbInplace(in ComponentValues values)
3234
{
3335
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");
3436

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System;
5+
using SixLabors.ImageSharp.Memory;
6+
7+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
8+
{
9+
/// <summary>
10+
/// Base class for processing component spectral data and converting it to raw color data.
11+
/// </summary>
12+
internal abstract class ComponentProcessor : IDisposable
13+
{
14+
public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size postProcessorBufferSize, IJpegComponent component, int blockSize)
15+
{
16+
this.Frame = frame;
17+
this.Component = component;
18+
19+
this.BlockAreaSize = component.SubSamplingDivisors * blockSize;
20+
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
21+
postProcessorBufferSize.Width,
22+
postProcessorBufferSize.Height,
23+
this.BlockAreaSize.Height);
24+
}
25+
26+
protected JpegFrame Frame { get; }
27+
28+
protected IJpegComponent Component { get; }
29+
30+
protected Buffer2D<float> ColorBuffer { get; }
31+
32+
protected Size BlockAreaSize { get; }
33+
34+
/// <summary>
35+
/// Converts spectral data to color data accessible via <see cref="GetColorBufferRowSpan(int)"/>.
36+
/// </summary>
37+
/// <param name="row">Spectral row index to convert.</param>
38+
public abstract void CopyBlocksToColorBuffer(int row);
39+
40+
/// <summary>
41+
/// Clears spectral buffers.
42+
/// </summary>
43+
/// <remarks>
44+
/// Should only be called during baseline interleaved decoding.
45+
/// </remarks>
46+
public void ClearSpectralBuffers()
47+
{
48+
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
49+
for (int i = 0; i < spectralBlocks.Height; i++)
50+
{
51+
spectralBlocks.DangerousGetRowSpan(i).Clear();
52+
}
53+
}
54+
55+
/// <summary>
56+
/// Gets converted color buffer row.
57+
/// </summary>
58+
/// <param name="row">Row index.</param>
59+
/// <returns>Color buffer row.</returns>
60+
public Span<float> GetColorBufferRowSpan(int row) =>
61+
this.ColorBuffer.DangerousGetRowSpan(row);
62+
63+
public void Dispose() => this.ColorBuffer.Dispose();
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System;
5+
using SixLabors.ImageSharp.Memory;
6+
7+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
8+
{
9+
/// <summary>
10+
/// Processes component spectral data and converts it to color data in 1-to-1 scale.
11+
/// </summary>
12+
internal sealed class DirectComponentProcessor : ComponentProcessor
13+
{
14+
private Block8x8F dequantizationTable;
15+
16+
public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
17+
: base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8)
18+
{
19+
this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex];
20+
FloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable);
21+
}
22+
23+
public override void CopyBlocksToColorBuffer(int spectralStep)
24+
{
25+
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
26+
27+
float maximumValue = this.Frame.MaxColorChannelValue;
28+
29+
int destAreaStride = this.ColorBuffer.Width;
30+
31+
int blocksRowsPerStep = this.Component.SamplingFactors.Height;
32+
33+
int yBlockStart = spectralStep * blocksRowsPerStep;
34+
35+
Size subSamplingDivisors = this.Component.SubSamplingDivisors;
36+
37+
Block8x8F workspaceBlock = default;
38+
39+
for (int y = 0; y < blocksRowsPerStep; y++)
40+
{
41+
int yBuffer = y * this.BlockAreaSize.Height;
42+
43+
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
44+
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
45+
46+
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
47+
{
48+
// Integer to float
49+
workspaceBlock.LoadFrom(ref blockRow[xBlock]);
50+
51+
// Dequantize
52+
workspaceBlock.MultiplyInPlace(ref this.dequantizationTable);
53+
54+
// Convert from spectral to color
55+
FloatingPointDCT.TransformIDCT(ref workspaceBlock);
56+
57+
// To conform better to libjpeg we actually NEED TO loose precision here.
58+
// This is because they store blocks as Int16 between all the operations.
59+
// To be "more accurate", we need to emulate this by rounding!
60+
workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
61+
62+
// Write to color buffer acording to sampling factors
63+
int xColorBufferStart = xBlock * this.BlockAreaSize.Width;
64+
workspaceBlock.ScaledCopyTo(
65+
ref colorBufferRow[xColorBufferStart],
66+
destAreaStride,
67+
subSamplingDivisors.Width,
68+
subSamplingDivisors.Height);
69+
}
70+
}
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using SixLabors.ImageSharp.Memory;
7+
8+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
9+
{
10+
/// <summary>
11+
/// Processes component spectral data and converts it to color data in 2-to-1 scale.
12+
/// </summary>
13+
internal sealed class DownScalingComponentProcessor2 : ComponentProcessor
14+
{
15+
private Block8x8F dequantizationTable;
16+
17+
public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
18+
: base(memoryAllocator, frame, postProcessorBufferSize, component, 4)
19+
{
20+
this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex];
21+
ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable);
22+
}
23+
24+
public override void CopyBlocksToColorBuffer(int spectralStep)
25+
{
26+
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
27+
28+
float maximumValue = this.Frame.MaxColorChannelValue;
29+
float normalizationValue = MathF.Ceiling(maximumValue / 2);
30+
31+
int destAreaStride = this.ColorBuffer.Width;
32+
33+
int blocksRowsPerStep = this.Component.SamplingFactors.Height;
34+
Size subSamplingDivisors = this.Component.SubSamplingDivisors;
35+
36+
Block8x8F workspaceBlock = default;
37+
38+
int yBlockStart = spectralStep * blocksRowsPerStep;
39+
40+
for (int y = 0; y < blocksRowsPerStep; y++)
41+
{
42+
int yBuffer = y * this.BlockAreaSize.Height;
43+
44+
Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
45+
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);
46+
47+
for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
48+
{
49+
// Integer to float
50+
workspaceBlock.LoadFrom(ref blockRow[xBlock]);
51+
52+
// IDCT/Normalization/Range
53+
ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue);
54+
55+
// Save to the intermediate buffer
56+
int xColorBufferStart = xBlock * this.BlockAreaSize.Width;
57+
ScaledCopyTo(
58+
ref workspaceBlock,
59+
ref colorBufferRow[xColorBufferStart],
60+
destAreaStride,
61+
subSamplingDivisors.Width,
62+
subSamplingDivisors.Height);
63+
}
64+
}
65+
}
66+
67+
[MethodImpl(InliningOptions.ShortMethod)]
68+
public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale)
69+
{
70+
// TODO: Optimize: implement all cases with scale-specific, loopless code!
71+
CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale);
72+
73+
[MethodImpl(InliningOptions.ColdPath)]
74+
static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
75+
{
76+
for (int y = 0; y < 4; y++)
77+
{
78+
int yy = y * verticalScale;
79+
int y8 = y * 8;
80+
81+
for (int x = 0; x < 4; x++)
82+
{
83+
int xx = x * horizontalScale;
84+
85+
float value = block[y8 + x];
86+
87+
for (int i = 0; i < verticalScale; i++)
88+
{
89+
int baseIdx = ((yy + i) * areaStride) + xx;
90+
91+
for (int j = 0; j < horizontalScale; j++)
92+
{
93+
// area[xx + j, yy + i] = value;
94+
Unsafe.Add(ref areaOrigin, (nint)(uint)(baseIdx + j)) = value;
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)