Skip to content

Commit 61b137d

Browse files
Merge pull request #1694 from br3aker/jpeg-decoder-memory
JpegDecoder: post-process baseline spectral data per MCU-row
2 parents c7f6115 + 190964c commit 61b137d

23 files changed

+713
-525
lines changed

src/ImageSharp/Common/Helpers/Numerics.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,5 +879,13 @@ ref MemoryMarshal.GetReference(Log2DeBruijn),
879879
(IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
880880
}
881881
#endif
882+
883+
/// <summary>
884+
/// Fast division with ceiling for <see cref="uint"/> numbers.
885+
/// </summary>
886+
/// <param name="value">Divident value.</param>
887+
/// <param name="divisor">Divisor value.</param>
888+
/// <returns>Ceiled division result.</returns>
889+
public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor;
882890
}
883891
}

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

Lines changed: 99 additions & 91 deletions
Large diffs are not rendered by default.

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ public void Dispose()
109109
public void Init()
110110
{
111111
this.WidthInBlocks = (int)MathF.Ceiling(
112-
MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor);
112+
MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor);
113113

114114
this.HeightInBlocks = (int)MathF.Ceiling(
115-
MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor);
115+
MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor);
116116

117117
int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor;
118118
int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor;
@@ -125,12 +125,20 @@ public void Init()
125125
{
126126
JpegThrowHelper.ThrowBadSampling();
127127
}
128+
}
129+
130+
public void AllocateSpectral(bool fullScan)
131+
{
132+
if (this.SpectralBlocks != null)
133+
{
134+
// this method will be called each scan marker so we need to allocate only once
135+
return;
136+
}
128137

129-
int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
130-
int width = this.WidthInBlocks + 1;
131-
int height = totalNumberOfBlocks / width;
138+
int spectralAllocWidth = this.SizeInBlocks.Width;
139+
int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor;
132140

133-
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(width, height, AllocationOptions.Clean);
141+
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean);
134142
}
135143
}
136144
}

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

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Runtime.CompilerServices;
6-
using System.Runtime.InteropServices;
7-
85
using SixLabors.ImageSharp.Memory;
96

107
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
118
{
129
/// <summary>
13-
/// Encapsulates postprocessing data for one component for <see cref="JpegImagePostProcessor"/>.
10+
/// Encapsulates spectral data to rgba32 processing for one component.
1411
/// </summary>
1512
internal class JpegComponentPostProcessor : IDisposable
1613
{
@@ -27,23 +24,20 @@ internal class JpegComponentPostProcessor : IDisposable
2724
/// <summary>
2825
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
2926
/// </summary>
30-
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePostProcessor imagePostProcessor, IJpegComponent component)
27+
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
3128
{
3229
this.Component = component;
33-
this.ImagePostProcessor = imagePostProcessor;
30+
this.RawJpeg = rawJpeg;
3431
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
3532
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
36-
imagePostProcessor.PostProcessorBufferSize.Width,
37-
imagePostProcessor.PostProcessorBufferSize.Height,
33+
postProcessorBufferSize.Width,
34+
postProcessorBufferSize.Height,
3835
this.blockAreaSize.Height);
3936

40-
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
37+
this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height;
4138
}
4239

43-
/// <summary>
44-
/// Gets the <see cref="JpegImagePostProcessor"/>
45-
/// </summary>
46-
public JpegImagePostProcessor ImagePostProcessor { get; }
40+
public IRawJpegData RawJpeg { get; }
4741

4842
/// <summary>
4943
/// Gets the <see cref="Component"/>
@@ -66,37 +60,38 @@ public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePost
6660
public int BlockRowsPerStep { get; }
6761

6862
/// <inheritdoc />
69-
public void Dispose()
70-
{
71-
this.ColorBuffer.Dispose();
72-
}
63+
public void Dispose() => this.ColorBuffer.Dispose();
7364

7465
/// <summary>
7566
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
7667
/// </summary>
77-
public void CopyBlocksToColorBuffer()
68+
public void CopyBlocksToColorBuffer(int step)
7869
{
79-
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component);
80-
float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1;
70+
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
71+
72+
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
73+
float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1;
8174

8275
int destAreaStride = this.ColorBuffer.Width;
8376

77+
int yBlockStart = step * this.BlockRowsPerStep;
78+
8479
for (int y = 0; y < this.BlockRowsPerStep; y++)
8580
{
86-
int yBlock = this.currentComponentRowInBlocks + y;
81+
int yBlock = yBlockStart + y;
8782

88-
if (yBlock >= this.SizeInBlocks.Height)
83+
if (yBlock >= spectralBuffer.Height)
8984
{
9085
break;
9186
}
9287

9388
int yBuffer = y * this.blockAreaSize.Height;
9489

9590
Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
96-
Span<Block8x8> blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock);
91+
Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlock);
9792

9893
// see: https://github.com/SixLabors/ImageSharp/issues/824
99-
int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width);
94+
int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width);
10095

10196
for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
10297
{
@@ -107,7 +102,20 @@ public void CopyBlocksToColorBuffer()
107102
blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
108103
}
109104
}
105+
}
106+
107+
public void ClearSpectralBuffers()
108+
{
109+
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
110+
for (int i = 0; i < spectralBlocks.Height; i++)
111+
{
112+
spectralBlocks.GetRowSpan(i).Clear();
113+
}
114+
}
110115

116+
public void CopyBlocksToColorBuffer()
117+
{
118+
this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks);
111119
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
112120
}
113121
}

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ internal sealed class JpegFrame : IDisposable
2020
/// </summary>
2121
public bool Progressive { get; set; }
2222

23+
/// <summary>
24+
/// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers).
25+
/// </summary>
26+
/// <remarks>
27+
/// This is true for progressive and baseline non-interleaved images.
28+
/// </remarks>
29+
public bool MultiScan { get; set; }
30+
2331
/// <summary>
2432
/// Gets or sets the precision.
2533
/// </summary>
@@ -28,12 +36,12 @@ internal sealed class JpegFrame : IDisposable
2836
/// <summary>
2937
/// Gets or sets the number of scanlines within the frame.
3038
/// </summary>
31-
public int Scanlines { get; set; }
39+
public int PixelHeight { get; set; }
3240

3341
/// <summary>
3442
/// Gets or sets the number of samples per scanline.
3543
/// </summary>
36-
public int SamplesPerLine { get; set; }
44+
public int PixelWidth { get; set; }
3745

3846
/// <summary>
3947
/// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4.
@@ -95,14 +103,23 @@ public void Dispose()
95103
/// </summary>
96104
public void InitComponents()
97105
{
98-
this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor);
99-
this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor);
106+
this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)this.MaxHorizontalFactor * 8);
107+
this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)this.MaxVerticalFactor * 8);
100108

101109
for (int i = 0; i < this.ComponentCount; i++)
102110
{
103111
JpegComponent component = this.Components[i];
104112
component.Init();
105113
}
106114
}
115+
116+
public void AllocateComponents(bool fullScan)
117+
{
118+
for (int i = 0; i < this.ComponentCount; i++)
119+
{
120+
JpegComponent component = this.Components[i];
121+
component.AllocateSpectral(fullScan);
122+
}
123+
}
107124
}
108125
}

0 commit comments

Comments
 (0)