-
-
Notifications
You must be signed in to change notification settings - Fork 851
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
JimBobSquarePants
merged 34 commits into
SixLabors:main
from
br3aker:dp/jpeg-downscaling-decode
Jul 17, 2022
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
36180c6
playground
11220a3
Replaced absurdly complicated math from JpegComponentPostProcessor
afbf44b
Infrastructure
017919a
First working prototype, not optimized
5cca314
Added playground benchmarks
a10be09
Resizing converter no longer depends on avx converter only
1ce994a
Unified spectral conversion for direct and downscaled routines
4e17b69
Added second stage to the resizing decoding
f4d5f1a
Chroma subsampling for downscaling decoder
282e593
Update playground
8192ff2
Initial processor implementation, code base for tests
52f507d
Separated scaled IDCT methods
6eceb6c
Moved quantization table initialization to component post processors
03407f1
4x4 implementation, tests
1050cf2
Fixed bug leading to gray images after decoding
9575a24
Fix compilation error
c57ca1b
Merge branch 'main' into dp/jpeg-downscaling-decode
12776f0
IDCT resizing modes
7057245
Code cleanup, removed invalid second pass logic, marked scaled decodi…
bfbfdfa
Added tests for out jpeg image size getter method
b943f80
Restored Program.cs
bb82e27
Merge branch 'main' into dp/jpeg-downscaling-decode
6747339
Docs & review fixes
1aff245
Merge branch 'dp/jpeg-downscaling-decode' of https://github.com/br3ak…
f011dcc
Unsafe.Add fix
3feb7f6
Merge branch 'main' of https://github.com/SixLabors/ImageSharp into d…
95c56b0
Small bug fixes, ready for merging
ed86426
Updated load-resize-save benchmark, deleted obsolete benchmarks
9f35b78
Merge branch 'main' into dp/jpeg-downscaling-decode
2896faf
Merge branch 'main' into dp/jpeg-downscaling-decode
brianpopow 3f16a68
Merge branch 'main' into dp/jpeg-downscaling-decode
9851315
Added resizing benchmark results
d0de191
Merge remote-tracking branch 'upstream/main' into dp/jpeg-downscaling…
JimBobSquarePants 7a9cf87
Fix headers
JimBobSquarePants File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Six Labors Split License. | ||
|
||
using System; | ||
using SixLabors.ImageSharp.Memory; | ||
|
||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder | ||
{ | ||
/// <summary> | ||
/// Base class for processing component spectral data and converting it to raw color data. | ||
/// </summary> | ||
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; } | ||
|
||
/// <summary> | ||
/// Converts spectral data to color data accessible via <see cref="GetColorBufferRowSpan(int)"/>. | ||
/// </summary> | ||
/// <param name="row">Spectral row index to convert.</param> | ||
public abstract void CopyBlocksToColorBuffer(int row); | ||
|
||
/// <summary> | ||
/// Clears spectral buffers. | ||
/// </summary> | ||
/// <remarks> | ||
/// Should only be called during baseline interleaved decoding. | ||
/// </remarks> | ||
public void ClearSpectralBuffers() | ||
{ | ||
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks; | ||
for (int i = 0; i < spectralBlocks.Height; i++) | ||
{ | ||
spectralBlocks.DangerousGetRowSpan(i).Clear(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets converted color buffer row. | ||
/// </summary> | ||
/// <param name="row">Row index.</param> | ||
/// <returns>Color buffer row.</returns> | ||
public Span<float> GetColorBufferRowSpan(int row) => | ||
this.ColorBuffer.DangerousGetRowSpan(row); | ||
|
||
public void Dispose() => this.ColorBuffer.Dispose(); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
...mageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Six Labors Split License. | ||
|
||
using System; | ||
using SixLabors.ImageSharp.Memory; | ||
|
||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder | ||
{ | ||
/// <summary> | ||
/// Processes component spectral data and converts it to color data in 1-to-1 scale. | ||
/// </summary> | ||
internal sealed class DirectComponentProcessor : ComponentProcessor | ||
{ | ||
private Block8x8F dequantizationTable; | ||
|
||
public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) | ||
: base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8) | ||
{ | ||
this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; | ||
FloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); | ||
} | ||
|
||
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 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 this.dequantizationTable); | ||
|
||
// Convert from spectral to color | ||
FloatingPointDCT.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); | ||
} | ||
} | ||
} | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
...arp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Six Labors Split License. | ||
|
||
using System; | ||
using System.Runtime.CompilerServices; | ||
using SixLabors.ImageSharp.Memory; | ||
|
||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder | ||
{ | ||
/// <summary> | ||
/// Processes component spectral data and converts it to color data in 2-to-1 scale. | ||
/// </summary> | ||
internal sealed class DownScalingComponentProcessor2 : ComponentProcessor | ||
{ | ||
private Block8x8F dequantizationTable; | ||
|
||
public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) | ||
: base(memoryAllocator, frame, postProcessorBufferSize, component, 4) | ||
{ | ||
this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; | ||
ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); | ||
} | ||
|
||
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; | ||
|
||
Block8x8F workspaceBlock = default; | ||
|
||
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++) | ||
{ | ||
// Integer to float | ||
workspaceBlock.LoadFrom(ref blockRow[xBlock]); | ||
|
||
// IDCT/Normalization/Range | ||
ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); | ||
|
||
// Save to the intermediate buffer | ||
int xColorBufferStart = xBlock * this.BlockAreaSize.Width; | ||
ScaledCopyTo( | ||
ref workspaceBlock, | ||
ref colorBufferRow[xColorBufferStart], | ||
destAreaStride, | ||
subSamplingDivisors.Width, | ||
subSamplingDivisors.Height); | ||
} | ||
} | ||
} | ||
|
||
[MethodImpl(InliningOptions.ShortMethod)] | ||
public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) | ||
{ | ||
// TODO: Optimize: implement all cases with scale-specific, loopless code! | ||
CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); | ||
|
||
[MethodImpl(InliningOptions.ColdPath)] | ||
static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) | ||
{ | ||
for (int y = 0; y < 4; y++) | ||
{ | ||
int yy = y * verticalScale; | ||
int y8 = y * 8; | ||
|
||
for (int x = 0; x < 4; x++) | ||
{ | ||
int xx = x * horizontalScale; | ||
|
||
float value = block[y8 + x]; | ||
|
||
for (int i = 0; i < verticalScale; i++) | ||
{ | ||
int baseIdx = ((yy + i) * areaStride) + xx; | ||
|
||
for (int j = 0; j < horizontalScale; j++) | ||
{ | ||
// area[xx + j, yy + i] = value; | ||
Unsafe.Add(ref areaOrigin, (nint)(uint)(baseIdx + j)) = value; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, we got a little problem here. ITU specs allows to place markers between frame and scan markers which can screw up current architecture.
I've decided to separate SOF scan callback which now simply saves references to frame and jpeg data variables and has a separate
PrepareForDecoding()
callback which is called explicitly for single scan images and implicitly called by multi-scan images bySpectralConverter
.HuffmanScanDecoder
andArithmeticScanDecoder
are affected by this change.I'm planning to do a refactoring PR for entire jpeg decoder after this is merged. Arithmetic decoding PR and this scaling decoding PR contains rather large changes so it's the perfect time to clean it up! :)