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

Jpeg downscaling decoding #2076

Merged
merged 34 commits into from
Jul 17, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
36180c6
playground
Mar 19, 2022
11220a3
Replaced absurdly complicated math from JpegComponentPostProcessor
Mar 19, 2022
afbf44b
Infrastructure
Mar 21, 2022
017919a
First working prototype, not optimized
Mar 22, 2022
5cca314
Added playground benchmarks
Mar 22, 2022
a10be09
Resizing converter no longer depends on avx converter only
Mar 23, 2022
1ce994a
Unified spectral conversion for direct and downscaled routines
Mar 25, 2022
4e17b69
Added second stage to the resizing decoding
Mar 26, 2022
f4d5f1a
Chroma subsampling for downscaling decoder
Mar 26, 2022
282e593
Update playground
Mar 26, 2022
8192ff2
Initial processor implementation, code base for tests
Mar 29, 2022
52f507d
Separated scaled IDCT methods
Apr 1, 2022
6eceb6c
Moved quantization table initialization to component post processors
Apr 1, 2022
03407f1
4x4 implementation, tests
Apr 2, 2022
1050cf2
Fixed bug leading to gray images after decoding
Apr 12, 2022
9575a24
Fix compilation error
Apr 12, 2022
c57ca1b
Merge branch 'main' into dp/jpeg-downscaling-decode
Apr 12, 2022
12776f0
IDCT resizing modes
Apr 23, 2022
7057245
Code cleanup, removed invalid second pass logic, marked scaled decodi…
Apr 26, 2022
bfbfdfa
Added tests for out jpeg image size getter method
Apr 30, 2022
b943f80
Restored Program.cs
Apr 30, 2022
bb82e27
Merge branch 'main' into dp/jpeg-downscaling-decode
May 1, 2022
6747339
Docs & review fixes
May 1, 2022
1aff245
Merge branch 'dp/jpeg-downscaling-decode' of https://github.com/br3ak…
May 1, 2022
f011dcc
Unsafe.Add fix
May 1, 2022
3feb7f6
Merge branch 'main' of https://github.com/SixLabors/ImageSharp into d…
May 1, 2022
95c56b0
Small bug fixes, ready for merging
May 1, 2022
ed86426
Updated load-resize-save benchmark, deleted obsolete benchmarks
May 1, 2022
9f35b78
Merge branch 'main' into dp/jpeg-downscaling-decode
May 2, 2022
2896faf
Merge branch 'main' into dp/jpeg-downscaling-decode
brianpopow May 17, 2022
3f16a68
Merge branch 'main' into dp/jpeg-downscaling-decode
Jun 26, 2022
9851315
Added resizing benchmark results
Jun 26, 2022
d0de191
Merge remote-tracking branch 'upstream/main' into dp/jpeg-downscaling…
JimBobSquarePants Jul 16, 2022
7a9cf87
Fix headers
JimBobSquarePants Jul 16, 2022
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

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

public override bool IsAvailable => Avx.IsSupported;
public sealed override bool IsAvailable => Avx.IsSupported;

public sealed override int ElementsPerBatch => Vector256<float>.Count;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision)
/// </summary>
public abstract bool IsAvailable { get; }

/// <summary>
/// Gets a value indicating how many pixels are processed in a single batch.
/// </summary>
/// <remarks>
/// This generaly should be equal to register size,
br3aker marked this conversation as resolved.
Show resolved Hide resolved
/// e.g. 1 for scalar implementation, 8 for AVX implementation and so on.
/// </remarks>
public abstract int ElementsPerBatch { get; }

/// <summary>
/// Gets the <see cref="JpegColorSpace"/> of this converter.
/// </summary>
Expand Down Expand Up @@ -219,7 +228,7 @@ public ComponentValues(IReadOnlyList<Buffer2D<float>> componentBuffers, int row)
/// </summary>
/// <param name="processors">List of component color processors.</param>
/// <param name="row">Row to convert</param>
public ComponentValues(IReadOnlyList<JpegComponentPostProcessor> processors, int row)
public ComponentValues(IReadOnlyList<ComponentProcessor> processors, int row)
{
DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision)
{
}

public override bool IsAvailable => true;
public sealed override bool IsAvailable => true;

public sealed override int ElementsPerBatch => 1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal abstract partial class JpegColorConverterBase
/// Even though real life data is guaranteed to be of size
/// divisible by 8 newer SIMD instructions like AVX512 won't work with
/// such data out of the box. These converters have fallback code
/// for 'remainder' data.
/// for remainder data.
/// </remarks>
internal abstract class JpegColorConverterVector : JpegColorConverterBase
{
Expand All @@ -28,7 +28,9 @@ protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision)

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

public override void ConvertToRgbInplace(in ComponentValues values)
public sealed override int ElementsPerBatch => Vector<float>.Count;

public sealed override void ConvertToRgbInplace(in ComponentValues values)
{
DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware.");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal abstract class ComponentProcessor : IDisposable
{
public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size postProcessorBufferSize, IJpegComponent component, int blockSize)
{
this.Frame = frame;
this.Component = component;

this.BlockAreaSize = component.SubSamplingDivisors * blockSize;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
postProcessorBufferSize.Width,
postProcessorBufferSize.Height,
this.BlockAreaSize.Height);
}

protected JpegFrame Frame { get; }

protected IJpegComponent Component { get; }

protected Buffer2D<float> ColorBuffer { get; }

protected Size BlockAreaSize { get; }

public abstract void CopyBlocksToColorBuffer(int spectralStep);

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

public Span<float> GetColorBufferRowSpan(int row) =>
this.ColorBuffer.DangerousGetRowSpan(row);

public void Dispose() => this.ColorBuffer.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal sealed class DirectComponentProcessor : ComponentProcessor
{
private readonly IRawJpegData rawJpeg;

public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
: base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8)
=> this.rawJpeg = rawJpeg;

public override void CopyBlocksToColorBuffer(int spectralStep)
{
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;

float maximumValue = this.Frame.MaxColorChannelValue;

int destAreaStride = this.ColorBuffer.Width;

int blocksRowsPerStep = this.Component.SamplingFactors.Height;

int yBlockStart = spectralStep * blocksRowsPerStep;

Size subSamplingDivisors = this.Component.SubSamplingDivisors;

Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.Component.QuantizationTableIndex];
Block8x8F workspaceBlock = default;

for (int y = 0; y < blocksRowsPerStep; y++)
{
int yBuffer = y * this.BlockAreaSize.Height;

Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);

for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
// Integer to float
workspaceBlock.LoadFrom(ref blockRow[xBlock]);

// Dequantize
workspaceBlock.MultiplyInPlace(ref dequantTable);

// Convert from spectral to color
FastFloatingPointDCT.TransformIDCT(ref workspaceBlock);

// To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding!
workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);

// Write to color buffer acording to sampling factors
int xColorBufferStart = xBlock * this.BlockAreaSize.Width;
workspaceBlock.ScaledCopyTo(
ref colorBufferRow[xColorBufferStart],
destAreaStride,
subSamplingDivisors.Width,
subSamplingDivisors.Height);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal sealed class DownScalingComponentProcessor8 : ComponentProcessor
{
private readonly float dcDequantizer;

public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
: base(memoryAllocator, frame, postProcessorBufferSize, component, 1)
=> this.dcDequantizer = rawJpeg.QuantizationTables[component.QuantizationTableIndex][0];

public override void CopyBlocksToColorBuffer(int spectralStep)
{
Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;

float maximumValue = this.Frame.MaxColorChannelValue;
float normalizationValue = MathF.Ceiling(maximumValue / 2);

int destAreaStride = this.ColorBuffer.Width;

int blocksRowsPerStep = this.Component.SamplingFactors.Height;
Size subSamplingDivisors = this.Component.SubSamplingDivisors;

int yBlockStart = spectralStep * blocksRowsPerStep;

for (int y = 0; y < blocksRowsPerStep; y++)
{
int yBuffer = y * this.BlockAreaSize.Height;

Span<float> colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer);
Span<Block8x8> blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y);

for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++)
{
// get direct current term - averaged 8x8 pixel value
float dc = blockRow[xBlock][0];

// dequantization
dc *= this.dcDequantizer;

// Normalize & round
dc = (float)Math.Round(Numerics.Clamp(dc + normalizationValue, 0, maximumValue));

// Save to the intermediate buffer
int xColorBufferStart = xBlock * this.BlockAreaSize.Width;
ScaledCopyTo(
dc,
ref colorBufferRow[xColorBufferStart],
destAreaStride,
subSamplingDivisors.Width,
subSamplingDivisors.Height);
}
}
}

[MethodImpl(InliningOptions.ShortMethod)]
public static void ScaledCopyTo(float value, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
Unsafe.Add(ref destRef, 0) = value;
br3aker marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (horizontalScale == 2 && verticalScale == 2)
{
Unsafe.Add(ref destRef, 0) = value;
Unsafe.Add(ref destRef, 1) = value;
Unsafe.Add(ref destRef, 0 + destStrideWidth) = value;
Unsafe.Add(ref destRef, 1 + destStrideWidth) = value;
br3aker marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// TODO: Optimize: implement all cases with scale-specific, loopless code!
for (int y = 0; y < verticalScale; y++)
{
for (int x = 0; x < horizontalScale; x++)
{
Unsafe.Add(ref destRef, x) = value;
br3aker marked this conversation as resolved.
Show resolved Hide resolved
}

destRef = ref Unsafe.Add(ref destRef, destStrideWidth);
br3aker marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
Loading