Skip to content

Commit

Permalink
Merge pull request #1694 from br3aker/jpeg-decoder-memory
Browse files Browse the repository at this point in the history
JpegDecoder: post-process baseline spectral data per MCU-row
  • Loading branch information
JimBobSquarePants authored Jul 15, 2021
2 parents c7f6115 + 190964c commit 61b137d
Show file tree
Hide file tree
Showing 23 changed files with 713 additions and 525 deletions.
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

0 comments on commit 61b137d

Please sign in to comment.