Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JpegDecoder: post-process baseline spectral data per MCU-row #1694

Merged
merged 78 commits into from
Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
6f6ee73
Renamed pixel dimensions for JpegFrame
Jul 7, 2021
925b3ad
Added debug code to the sandbox
Jul 7, 2021
2f8d3c9
Injected progressive scan parameters
Jul 7, 2021
336c64a
Injected scan selectors count
Jul 7, 2021
3b2d2d8
Injected frame & reset interval
Jul 7, 2021
5d44503
Injected huffman tables
Jul 7, 2021
442af2c
Scan decoder is not a persistent state of the decoder core
Jul 7, 2021
b7d54b1
Added comments for future refactoring
Jul 7, 2021
7044741
Added extra comment
Jul 7, 2021
9b81724
Jpeg frame is now injected to the scan decoder at the SOF marker
Jul 8, 2021
7e1bd59
Slight change to image post processor for better understanding
Jul 8, 2021
5ba8763
Replaced hardcoded values with actual calculated ones in postprocessor
Jul 8, 2021
22af241
WIP spectral converter
Jul 9, 2021
dbe4c4e
Fixed iteration variables
Jul 9, 2021
887c0ba
Added todo(s)
Jul 9, 2021
6b2f189
Decoupled image processor from component processor
Jul 9, 2021
dc4bc6e
Fixed converter frame injection
Jul 9, 2021
d178c8c
Added getter which converts pixels to PixelBuffer property
Jul 9, 2021
c86d029
Fixed sandbox code
Jul 9, 2021
1dbb16a
Wired up converter & scan decoder
Jul 9, 2021
1c10ec6
Added Buffer2D Image ctor, wired new post processor with decoder core
Jul 9, 2021
1348ecf
Implemented disposable pattern for spectral converter
Jul 9, 2021
1d4dd08
Implemented step-based iteration for spectralconverter
Jul 9, 2021
fa0aaec
Added separate step parameter for spectral data enumeration
Jul 10, 2021
c017357
Added external way to mark convesion finished
Jul 10, 2021
3c59cd9
Added initial support for the baseline interleaved stride conversion
Jul 10, 2021
460b02c
Sandbox changes
Jul 10, 2021
e39adf8
Fixed invalid baseline jpeg decoding
Jul 10, 2021
7afca19
Rolled back to counter enumeration for spectral converter
Jul 11, 2021
4b5f0f6
Refactored scan converter
Jul 11, 2021
74a7e90
Refactores post processor buffer clear
Jul 11, 2021
4af7fd1
Refactored spectral converter
Jul 11, 2021
fae763e
Final refactor of the converter
Jul 11, 2021
243e2bd
Removed todo
Jul 11, 2021
639ed62
Implemented new spectral buffers allocation
Jul 11, 2021
27d7c3a
Rolled back to initial sandbox code
Jul 11, 2021
da7bca3
Moved SpectralConverter to the separate file
Jul 11, 2021
86a7b46
Added docs
Jul 11, 2021
d325d06
Fixed styling issues
Jul 11, 2021
3fb7105
Fixed docs
Jul 11, 2021
73d35b7
Fixed no color deduction for metadata only pass
Jul 11, 2021
ccd6601
Marked ParseStream private as it now can't be called outside of Decod…
Jul 11, 2021
b9f12a6
Removed unsupported benchmark
Jul 11, 2021
ef80d98
Tests no longer use ParseStream method
Jul 11, 2021
8078688
Fixed null reference in spectral converter
Jul 11, 2021
7c63fb4
Fixed out of range exception at component postprocessor
Jul 11, 2021
ae1b40d
Skipped old post processing pipeline tests
Jul 11, 2021
3c4d0fe
Fixed invalid frame mcu size calculation
Jul 11, 2021
7fbc33c
Fixed invalid internal deconding mode selection for grayscale jpegs
Jul 11, 2021
e478795
Cosmetic fixes
Jul 11, 2021
b8e13e7
Disabled spectral tests due to new architecture incompatibility
Jul 11, 2021
519c6b2
Baseline jpegs now clear allocated buffers in the decoding loop
Jul 12, 2021
daccbfb
Basement for spectral tests
Jul 12, 2021
bbbfb50
Additional fixes for spectral tests
Jul 12, 2021
19e2e3d
Fixed new spectral tests for progressive and multi-scan images
Jul 12, 2021
39dd5bc
Fixed out of range exception for baseline tests
Jul 12, 2021
0c78c67
Clarified diff logs
Jul 12, 2021
4c97fcc
Rolled back spectral buffer cleaning logic
Jul 12, 2021
024be3b
Spectral converter base class no longer implements IDisposable interface
Jul 12, 2021
194f6e0
Debug converter no longer use actual converter
Jul 12, 2021
7540fd9
Fixed baseline images tsting code
Jul 12, 2021
0261ea9
Fixed bad EOI image
Jul 12, 2021
79eb6c4
Fixed metadata only pass for a test
Jul 12, 2021
d7084eb
Style fixes
Jul 13, 2021
2e5b0ad
Fixed baseline image invalid reference output png image
Jul 13, 2021
005fff7
Removed post processor tests
Jul 13, 2021
c6a2c6b
Removed post processor from jpeg decoder
Jul 13, 2021
865c706
Added new tolerance to the Jpeg420Small test image
Jul 13, 2021
4ff282e
Merge branch 'master' into jpeg-decoder-memory
Jul 13, 2021
8b6ad9c
Fixed docs
Jul 13, 2021
19b65ab
Merge branch 'jpeg-decoder-memory' of https://github.com/br3aker/Imag…
Jul 13, 2021
84900dc
Restored memory stress test to the sandbox
Jul 13, 2021
82e22c3
Restored decoder parse stream only benchmark
Jul 13, 2021
0c27adc
Updated StreamParseOnly benchmark
Jul 13, 2021
2eaa2d5
Added docs
Jul 13, 2021
13c3a45
Added DivideCeil
Jul 13, 2021
269c073
Fixed spectral data as image saving test
Jul 13, 2021
190964c
Disabled image saving test
Jul 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 98 additions & 90 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

Large diffs are not rendered by default.

20 changes: 14 additions & 6 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ public void Dispose()
public void Init()
{
this.WidthInBlocks = (int)MathF.Ceiling(
MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor);
MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor);

this.HeightInBlocks = (int)MathF.Ceiling(
MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor);
MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor);

int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor;
int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor;
Expand All @@ -125,12 +125,20 @@ public void Init()
{
JpegThrowHelper.ThrowBadSampling();
}
}

public void AllocateSpectral(bool fullScan)
{
if (this.SpectralBlocks != null)
{
// this method will be called each scan marker so we need to allocate only once
return;
}

int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
int width = this.WidthInBlocks + 1;
int height = totalNumberOfBlocks / width;
int spectralAllocWidth = this.SizeInBlocks.Width;
int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor;

this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(width, height, AllocationOptions.Clean);
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
Expand All @@ -27,23 +24,20 @@ internal class JpegComponentPostProcessor : IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary>
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePostProcessor imagePostProcessor, IJpegComponent component)
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
{
this.Component = component;
this.ImagePostProcessor = imagePostProcessor;
this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
imagePostProcessor.PostProcessorBufferSize.Width,
imagePostProcessor.PostProcessorBufferSize.Height,
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
this.blockAreaSize.Height);

this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height;
}

/// <summary>
/// Gets the <see cref="JpegImagePostProcessor"/>
/// </summary>
public JpegImagePostProcessor ImagePostProcessor { get; }
public IRawJpegData RawJpeg { get; }

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

/// <inheritdoc />
public void Dispose()
{
this.ColorBuffer.Dispose();
}
public void Dispose() => this.ColorBuffer.Dispose();

/// <summary>
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
/// </summary>
public void CopyBlocksToColorBuffer()
public void CopyBlocksToColorBuffer(int step)
{
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component);
float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1;
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;

var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
float maximumValue = MathF.Pow(2, this.RawJpeg.Precision) - 1;

int destAreaStride = this.ColorBuffer.Width;

int yBlockStart = step * this.BlockRowsPerStep;

for (int y = 0; y < this.BlockRowsPerStep; y++)
{
int yBlock = this.currentComponentRowInBlocks + y;
int yBlock = yBlockStart + y;

if (yBlock >= this.SizeInBlocks.Height)
if (yBlock >= spectralBuffer.Height)
{
break;
}

int yBuffer = y * this.blockAreaSize.Height;

Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock);
Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlock);

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

for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
{
Expand All @@ -107,7 +102,20 @@ public void CopyBlocksToColorBuffer()
blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
}
}
}

public void ClearSpectralBuffers()
{
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
spectralBlocks.GetRowSpan(i).Clear();
}
}

public void CopyBlocksToColorBuffer()
{
this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks);
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
}
}
Expand Down
17 changes: 13 additions & 4 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ internal sealed class JpegFrame : IDisposable
/// <summary>
/// Gets or sets the number of scanlines within the frame.
/// </summary>
public int Scanlines { get; set; }
public int PixelHeight { get; set; }

/// <summary>
/// Gets or sets the number of samples per scanline.
/// </summary>
public int SamplesPerLine { get; set; }
public int PixelWidth { get; set; }

/// <summary>
/// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4.
Expand Down Expand Up @@ -95,14 +95,23 @@ public void Dispose()
/// </summary>
public void InitComponents()
{
this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor);
this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor);
this.McusPerLine = (int)MathF.Ceiling(this.PixelWidth / 8F / this.MaxHorizontalFactor);
br3aker marked this conversation as resolved.
Show resolved Hide resolved
this.McusPerColumn = (int)MathF.Ceiling(this.PixelHeight / 8F / this.MaxVerticalFactor);

for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
component.Init();
}
}

public void AllocateComponents(bool fullScan)
{
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Buffers;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter;
Expand All @@ -19,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
/// <see cref="pixelRowsPerStep"/> image rows are converted in one step,
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame.Height"/>).
/// </summary>
internal class JpegImagePostProcessor : IDisposable
Expand All @@ -29,12 +28,12 @@ internal class JpegImagePostProcessor : IDisposable
/// <summary>
/// The number of block rows to be processed in one Step.
/// </summary>
public const int BlockRowsPerStep = 4;
private readonly int blockRowsPerStep;

/// <summary>
/// The number of image pixel rows to be processed in one step.
/// </summary>
public const int PixelRowsPerStep = 4 * 8;
private readonly int pixelRowsPerStep;

/// <summary>
/// Temporal buffer to store a row of colors.
Expand All @@ -56,15 +55,18 @@ public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg)
this.configuration = configuration;
this.RawJpeg = rawJpeg;
IJpegComponent c0 = rawJpeg.Components[0];
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);

MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
this.blockRowsPerStep = c0.SamplingFactors.Height;
this.pixelRowsPerStep = this.blockRowsPerStep * 8;

this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / this.blockRowsPerStep;

var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep);
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length];
for (int i = 0; i < rawJpeg.Components.Length; i++)
{
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]);
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this.RawJpeg, postProcessorBufferSize, rawJpeg.Components[i]);
}

this.rgbaBuffer = memoryAllocator.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
Expand All @@ -82,17 +84,12 @@ public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg)
public IRawJpegData RawJpeg { get; }

/// <summary>
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="PixelRowsPerStep"/>.
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="pixelRowsPerStep"/>.
/// </summary>
public int NumberOfPostProcessorSteps { get; }

/// <summary>
/// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// </summary>
public Size PostProcessorBufferSize { get; }

/// <summary>
/// Gets the value of the counter that grows by each step by <see cref="PixelRowsPerStep"/>.
/// Gets the value of the counter that grows by each step by <see cref="pixelRowsPerStep"/>.
/// </summary>
public int PixelRowCounter { get; private set; }

Expand Down Expand Up @@ -131,36 +128,20 @@ public void PostProcess<TPixel>(ImageFrame<TPixel> destination, CancellationToke
}

/// <summary>
/// Execute one step processing <see cref="PixelRowsPerStep"/> pixel rows into 'destination'.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image.</param>
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
cpp.CopyBlocksToColorBuffer();
}

this.ConvertColorsInto(destination);

this.PixelRowCounter += PixelRowsPerStep;
}

/// <summary>
/// Convert and copy <see cref="PixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
/// Execute one step processing <see cref="pixelRowsPerStep"/> pixel rows into 'destination'.
/// Convert and copy <see cref="pixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination)
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);
int maxY = Math.Min(destination.Height, this.PixelRowCounter + this.pixelRowsPerStep);

var buffers = new Buffer2D<float>[this.ComponentProcessors.Length];
for (int i = 0; i < this.ComponentProcessors.Length; i++)
{
this.ComponentProcessors[i].CopyBlocksToColorBuffer();
buffers[i] = this.ComponentProcessors[i].ColorBuffer;
}

Expand All @@ -176,6 +157,8 @@ private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination)
// TODO: Investigate if slicing is actually necessary
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow);
}

this.PixelRowCounter += this.pixelRowsPerStep;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal abstract class SpectralConverter : IDisposable
{
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
br3aker marked this conversation as resolved.
Show resolved Hide resolved

public abstract void ConvertStrideBaseline();

public abstract void Dispose();
}
}
Loading