Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions src/ImageSharp/Common/Helpers/Numerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -879,5 +879,13 @@ ref MemoryMarshal.GetReference(Log2DeBruijn),
(IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
}
#endif

/// <summary>
/// Fast division with ceiling for <see cref="uint"/> numbers.
/// </summary>
/// <param name="value">Divident value.</param>
/// <param name="divisor">Divisor value.</param>
/// <returns>Ceiled division result.</returns>
public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor;
}
}
190 changes: 99 additions & 91 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,15 +2,12 @@
// 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
{
/// <summary>
/// Encapsulates postprocessing data for one component for <see cref="JpegImagePostProcessor"/>.
/// Encapsulates spectral data to rgba32 processing for one component.
/// </summary>
internal class JpegComponentPostProcessor : IDisposable
{
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
25 changes: 21 additions & 4 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ internal sealed class JpegFrame : IDisposable
/// </summary>
public bool Progressive { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers).
/// </summary>
/// <remarks>
/// This is true for progressive and baseline non-interleaved images.
/// </remarks>
public bool MultiScan { get; set; }

/// <summary>
/// Gets or sets the precision.
/// </summary>
Expand All @@ -28,12 +36,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 +103,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)Numerics.DivideCeil((uint)this.PixelWidth, (uint)this.MaxHorizontalFactor * 8);
this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)this.MaxVerticalFactor * 8);

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);
}
}
}
}
Loading