mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
60 changed files with 1728 additions and 602 deletions
@ -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(); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 4-to-1 scale.
|
|||
/// </summary>
|
|||
internal sealed class DownScalingComponentProcessor4 : ComponentProcessor |
|||
{ |
|||
private Block8x8F dequantizationTable; |
|||
|
|||
public DownScalingComponentProcessor4(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) |
|||
: base(memoryAllocator, frame, postProcessorBufferSize, component, 2) |
|||
{ |
|||
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_2x2(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 < 2; y++) |
|||
{ |
|||
int yy = y * verticalScale; |
|||
int y8 = y * 8; |
|||
|
|||
for (int x = 0; x < 2; 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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
// 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 8-to-1 scale.
|
|||
/// </summary>
|
|||
internal sealed class DownScalingComponentProcessor8 : ComponentProcessor |
|||
{ |
|||
private readonly float dcDequantizatizer; |
|||
|
|||
public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) |
|||
: base(memoryAllocator, frame, postProcessorBufferSize, component, 1) |
|||
=> this.dcDequantizatizer = 0.125f * 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++) |
|||
{ |
|||
float dc = ScaledFloatingPointDCT.TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizatizer, normalizationValue, 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) |
|||
{ |
|||
destRef = value; |
|||
return; |
|||
} |
|||
|
|||
if (horizontalScale == 2 && verticalScale == 2) |
|||
{ |
|||
destRef = value; |
|||
Unsafe.Add(ref destRef, 1) = value; |
|||
Unsafe.Add(ref destRef, 0 + (nint)(uint)destStrideWidth) = value; |
|||
Unsafe.Add(ref destRef, 1 + (nint)(uint)destStrideWidth) = value; |
|||
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, (nint)(uint)x) = value; |
|||
} |
|||
|
|||
destRef = ref Unsafe.Add(ref destRef, (nint)(uint)destStrideWidth); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,129 +0,0 @@ |
|||
// 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>
|
|||
/// Encapsulates spectral data to rgba32 processing for one component.
|
|||
/// </summary>
|
|||
internal class JpegComponentPostProcessor : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The size of the area in <see cref="ColorBuffer"/> corresponding to one 8x8 Jpeg block
|
|||
/// </summary>
|
|||
private readonly Size blockAreaSize; |
|||
|
|||
/// <summary>
|
|||
/// Jpeg frame instance containing required decoding metadata.
|
|||
/// </summary>
|
|||
private readonly JpegFrame frame; |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximal number of block rows being processed in one step.
|
|||
/// </summary>
|
|||
private readonly int blockRowsPerStep; |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IJpegComponent"/> component containing decoding meta information.
|
|||
/// </summary>
|
|||
private readonly IJpegComponent component; |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IRawJpegData"/> instance containing decoding meta information.
|
|||
/// </summary>
|
|||
private readonly IRawJpegData rawJpeg; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
|
|||
/// </summary>
|
|||
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) |
|||
{ |
|||
this.frame = frame; |
|||
|
|||
this.component = component; |
|||
this.rawJpeg = rawJpeg; |
|||
this.blockAreaSize = this.component.SubSamplingDivisors * 8; |
|||
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>( |
|||
postProcessorBufferSize.Width, |
|||
postProcessorBufferSize.Height, |
|||
this.blockAreaSize.Height); |
|||
|
|||
this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporary working buffer of color values.
|
|||
/// </summary>
|
|||
public Buffer2D<float> ColorBuffer { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() => this.ColorBuffer.Dispose(); |
|||
|
|||
/// <summary>
|
|||
/// Convert raw spectral DCT data to color data and copy it to the color buffer <see cref="ColorBuffer"/>.
|
|||
/// </summary>
|
|||
public void CopyBlocksToColorBuffer(int spectralStep) |
|||
{ |
|||
Buffer2D<Block8x8> spectralBuffer = this.component.SpectralBlocks; |
|||
|
|||
float maximumValue = this.frame.MaxColorChannelValue; |
|||
|
|||
int destAreaStride = this.ColorBuffer.Width; |
|||
|
|||
int yBlockStart = spectralStep * this.blockRowsPerStep; |
|||
|
|||
Size subSamplingDivisors = this.component.SubSamplingDivisors; |
|||
|
|||
Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex]; |
|||
Block8x8F workspaceBlock = default; |
|||
|
|||
for (int y = 0; y < this.blockRowsPerStep; 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 according to sampling factors
|
|||
int xColorBufferStart = xBlock * this.blockAreaSize.Width; |
|||
workspaceBlock.ScaledCopyTo( |
|||
ref colorBufferRow[xColorBufferStart], |
|||
destAreaStride, |
|||
subSamplingDivisors.Width, |
|||
subSamplingDivisors.Height); |
|||
} |
|||
} |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,220 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
#pragma warning disable IDE0078
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components |
|||
{ |
|||
/// <summary>
|
|||
/// Contains floating point forward DCT implementations with built-in scaling.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Based on "Loeffler, Ligtenberg, and Moschytz" algorithm.
|
|||
/// </remarks>
|
|||
internal static class ScaledFloatingPointDCT |
|||
{ |
|||
#pragma warning disable SA1310
|
|||
private const float FP32_0_541196100 = 0.541196100f; |
|||
private const float FP32_0_765366865 = 0.765366865f; |
|||
private const float FP32_1_847759065 = 1.847759065f; |
|||
private const float FP32_0_211164243 = 0.211164243f; |
|||
private const float FP32_1_451774981 = 1.451774981f; |
|||
private const float FP32_2_172734803 = 2.172734803f; |
|||
private const float FP32_1_061594337 = 1.061594337f; |
|||
private const float FP32_0_509795579 = 0.509795579f; |
|||
private const float FP32_0_601344887 = 0.601344887f; |
|||
private const float FP32_0_899976223 = 0.899976223f; |
|||
private const float FP32_2_562915447 = 2.562915447f; |
|||
private const float FP32_0_720959822 = 0.720959822f; |
|||
private const float FP32_0_850430095 = 0.850430095f; |
|||
private const float FP32_1_272758580 = 1.272758580f; |
|||
private const float FP32_3_624509785 = 3.624509785f; |
|||
#pragma warning restore SA1310
|
|||
|
|||
/// <summary>
|
|||
/// Adjusts given quantization table for usage with IDCT algorithms
|
|||
/// from <see cref="ScaledFloatingPointDCT"/>.
|
|||
/// </summary>
|
|||
/// <param name="quantTable">Quantization table to adjust.</param>
|
|||
public static void AdjustToIDCT(ref Block8x8F quantTable) |
|||
{ |
|||
ref float tableRef = ref Unsafe.As<Block8x8F, float>(ref quantTable); |
|||
for (nint i = 0; i < Block8x8F.Size; i++) |
|||
{ |
|||
ref float elemRef = ref Unsafe.Add(ref tableRef, i); |
|||
elemRef = 0.125f * elemRef; |
|||
} |
|||
|
|||
// Spectral macroblocks are transposed before quantization
|
|||
// so we must transpose quantization table
|
|||
quantTable.TransposeInplace(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Apply 2D floating point 'donwscaling' IDCT inplace producing
|
|||
/// 8x8 -> 4x4 result.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Resulting matrix is stored in the top left 4x4 part of the
|
|||
/// <paramref name="block"/>.
|
|||
/// </remarks>
|
|||
/// <param name="block">Input block.</param>
|
|||
/// <param name="dequantTable">Dequantization table adjusted by <see cref="AdjustToIDCT(ref Block8x8F)"/>.</param>
|
|||
/// <param name="normalizationValue">Output range normalization value, 1/2 of the <paramref name="maxValue"/>.</param>
|
|||
/// <param name="maxValue">Maximum value of the output range.</param>
|
|||
public static void TransformIDCT_4x4(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) |
|||
{ |
|||
for (int ctr = 0; ctr < 8; ctr++) |
|||
{ |
|||
// Don't process row 4, second pass doesn't use it
|
|||
if (ctr == 4) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Even part
|
|||
float tmp0 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0] * 2; |
|||
|
|||
float z2 = block[(ctr * 8) + 2] * dequantTable[(ctr * 8) + 2]; |
|||
float z3 = block[(ctr * 8) + 6] * dequantTable[(ctr * 8) + 6]; |
|||
|
|||
float tmp2 = (z2 * FP32_1_847759065) + (z3 * -FP32_0_765366865); |
|||
|
|||
float tmp10 = tmp0 + tmp2; |
|||
float tmp12 = tmp0 - tmp2; |
|||
|
|||
// Odd part
|
|||
float z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; |
|||
z2 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; |
|||
z3 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; |
|||
float z4 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; |
|||
|
|||
tmp0 = (z1 * -FP32_0_211164243) + |
|||
(z2 * FP32_1_451774981) + |
|||
(z3 * -FP32_2_172734803) + |
|||
(z4 * FP32_1_061594337); |
|||
|
|||
tmp2 = (z1 * -FP32_0_509795579) + |
|||
(z2 * -FP32_0_601344887) + |
|||
(z3 * FP32_0_899976223) + |
|||
(z4 * FP32_2_562915447); |
|||
|
|||
// temporal result is saved to +4 shifted indices
|
|||
// because result is saved into the top left 2x2 region of the
|
|||
// input block
|
|||
block[(ctr * 8) + 0 + 4] = (tmp10 + tmp2) / 2; |
|||
block[(ctr * 8) + 3 + 4] = (tmp10 - tmp2) / 2; |
|||
block[(ctr * 8) + 1 + 4] = (tmp12 + tmp0) / 2; |
|||
block[(ctr * 8) + 2 + 4] = (tmp12 - tmp0) / 2; |
|||
} |
|||
|
|||
for (int ctr = 0; ctr < 4; ctr++) |
|||
{ |
|||
// Even part
|
|||
float tmp0 = block[ctr + (8 * 0) + 4] * 2; |
|||
|
|||
float tmp2 = (block[ctr + (8 * 2) + 4] * FP32_1_847759065) + (block[ctr + (8 * 6) + 4] * -FP32_0_765366865); |
|||
|
|||
float tmp10 = tmp0 + tmp2; |
|||
float tmp12 = tmp0 - tmp2; |
|||
|
|||
// Odd part
|
|||
float z1 = block[ctr + (8 * 7) + 4]; |
|||
float z2 = block[ctr + (8 * 5) + 4]; |
|||
float z3 = block[ctr + (8 * 3) + 4]; |
|||
float z4 = block[ctr + (8 * 1) + 4]; |
|||
|
|||
tmp0 = (z1 * -FP32_0_211164243) + |
|||
(z2 * FP32_1_451774981) + |
|||
(z3 * -FP32_2_172734803) + |
|||
(z4 * FP32_1_061594337); |
|||
|
|||
tmp2 = (z1 * -FP32_0_509795579) + |
|||
(z2 * -FP32_0_601344887) + |
|||
(z3 * FP32_0_899976223) + |
|||
(z4 * FP32_2_562915447); |
|||
|
|||
// Save results to the top left 4x4 subregion
|
|||
block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp2) / 2) + normalizationValue, 0, maxValue)); |
|||
block[(ctr * 8) + 3] = MathF.Round(Numerics.Clamp(((tmp10 - tmp2) / 2) + normalizationValue, 0, maxValue)); |
|||
block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp12 + tmp0) / 2) + normalizationValue, 0, maxValue)); |
|||
block[(ctr * 8) + 2] = MathF.Round(Numerics.Clamp(((tmp12 - tmp0) / 2) + normalizationValue, 0, maxValue)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Apply 2D floating point 'donwscaling' IDCT inplace producing
|
|||
/// 8x8 -> 2x2 result.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Resulting matrix is stored in the top left 2x2 part of the
|
|||
/// <paramref name="block"/>.
|
|||
/// </remarks>
|
|||
/// <param name="block">Input block.</param>
|
|||
/// <param name="dequantTable">Dequantization table adjusted by <see cref="AdjustToIDCT(ref Block8x8F)"/>.</param>
|
|||
/// <param name="normalizationValue">Output range normalization value, 1/2 of the <paramref name="maxValue"/>.</param>
|
|||
/// <param name="maxValue">Maximum value of the output range.</param>
|
|||
public static void TransformIDCT_2x2(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) |
|||
{ |
|||
for (int ctr = 0; ctr < 8; ctr++) |
|||
{ |
|||
// Don't process rows 2/4/6, second pass doesn't use it
|
|||
if (ctr == 2 || ctr == 4 || ctr == 6) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Even part
|
|||
float tmp0; |
|||
float z1 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0]; |
|||
float tmp10 = z1 * 4; |
|||
|
|||
// Odd part
|
|||
z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; |
|||
tmp0 = z1 * -FP32_0_720959822; |
|||
z1 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; |
|||
tmp0 += z1 * FP32_0_850430095; |
|||
z1 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; |
|||
tmp0 += z1 * -FP32_1_272758580; |
|||
z1 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; |
|||
tmp0 += z1 * FP32_3_624509785; |
|||
|
|||
// temporal result is saved to +2 shifted indices
|
|||
// because result is saved into the top left 2x2 region of the
|
|||
// input block
|
|||
block[(ctr * 8) + 2] = (tmp10 + tmp0) / 4; |
|||
block[(ctr * 8) + 3] = (tmp10 - tmp0) / 4; |
|||
} |
|||
|
|||
for (int ctr = 0; ctr < 2; ctr++) |
|||
{ |
|||
// Even part
|
|||
float tmp10 = block[ctr + (8 * 0) + 2] * 4; |
|||
|
|||
// Odd part
|
|||
float tmp0 = (block[ctr + (8 * 7) + 2] * -FP32_0_720959822) + |
|||
(block[ctr + (8 * 5) + 2] * FP32_0_850430095) + |
|||
(block[ctr + (8 * 3) + 2] * -FP32_1_272758580) + |
|||
(block[ctr + (8 * 1) + 2] * FP32_3_624509785); |
|||
|
|||
// Save results to the top left 2x2 subregion
|
|||
block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp0) / 4) + normalizationValue, 0, maxValue)); |
|||
block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp10 - tmp0) / 4) + normalizationValue, 0, maxValue)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Apply 2D floating point 'donwscaling' IDCT inplace producing
|
|||
/// 8x8 -> 1x1 result.
|
|||
/// </summary>
|
|||
/// <param name="dc">Direct current term value from input block.</param>
|
|||
/// <param name="dequantizer">Dequantization value.</param>
|
|||
/// <param name="normalizationValue">Output range normalization value, 1/2 of the <paramref name="maxValue"/>.</param>
|
|||
/// <param name="maxValue">Maximum value of the output range.</param>
|
|||
public static float TransformIDCT_1x1(float dc, float dequantizer, float normalizationValue, float maxValue) |
|||
=> MathF.Round(Numerics.Clamp((dc * dequantizer) + normalizationValue, 0, maxValue)); |
|||
} |
|||
} |
|||
#pragma warning restore IDE0078
|
|||
@ -1,31 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
|||
{ |
|||
/// <summary>
|
|||
/// Spectral converter for YCbCr TIFF's which use the JPEG compression.
|
|||
/// The jpeg data should be always treated as RGB color space.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|||
internal sealed class RgbJpegSpectralConverter<TPixel> : SpectralConverter<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RgbJpegSpectralConverter{TPixel}"/> class.
|
|||
/// This Spectral converter will always convert the pixel data to RGB color.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
public RgbJpegSpectralConverter(Configuration configuration) |
|||
: base(configuration) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
|||
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
|||
{ |
|||
/// <summary>
|
|||
/// Spectral converter for YCbCr TIFF's which use the JPEG compression.
|
|||
/// The jpeg data should be always treated as RGB color space.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
|||
internal sealed class TiffJpegSpectralConverter<TPixel> : SpectralConverter<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly TiffPhotometricInterpretation photometricInterpretation; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TiffJpegSpectralConverter{TPixel}"/> class.
|
|||
/// This Spectral converter will always convert the pixel data to RGB color.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="photometricInterpretation">Tiff photometric interpretation.</param>
|
|||
public TiffJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation) |
|||
: base(configuration) |
|||
=> this.photometricInterpretation = photometricInterpretation; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) |
|||
{ |
|||
JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation); |
|||
return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); |
|||
} |
|||
|
|||
/// <remarks>
|
|||
/// This converter must be used only for RGB and YCbCr color spaces for performance reasons.
|
|||
/// For grayscale images <see cref="GrayJpegSpectralConverter{TPixel}"/> must be used.
|
|||
/// </remarks>
|
|||
private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation) |
|||
=> interpretation switch |
|||
{ |
|||
TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB, |
|||
TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB, |
|||
_ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"), |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Formats.Tiff.Constants; |
|||
using SixLabors.ImageSharp.Formats.Webp; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors |
|||
{ |
|||
/// <summary>
|
|||
/// Class to handle cases where TIFF image data is compressed as a webp stream.
|
|||
/// </summary>
|
|||
internal class WebpTiffCompression : TiffBaseDecompressor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WebpTiffCompression"/> class.
|
|||
/// </summary>
|
|||
/// <param name="memoryAllocator">The memory allocator.</param>
|
|||
/// <param name="width">The width of the image.</param>
|
|||
/// <param name="bitsPerPixel">The bits per pixel.</param>
|
|||
/// <param name="predictor">The predictor.</param>
|
|||
public WebpTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) |
|||
: base(memoryAllocator, width, bitsPerPixel, predictor) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer) |
|||
{ |
|||
using var image = Image.Load<Rgb24>(stream, new WebpDecoder()); |
|||
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); |
|||
} |
|||
|
|||
private static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<Rgb24> pixelBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int y = 0; y < pixelBuffer.Height; y++) |
|||
{ |
|||
Span<Rgb24> pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); |
|||
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); |
|||
rgbBytes.CopyTo(buffer.Slice(offset)); |
|||
offset += rgbBytes.Length; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorSpaces; |
|||
using SixLabors.ImageSharp.ColorSpaces.Conversion; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
|||
{ |
|||
/// <summary>
|
|||
/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
|
|||
/// </summary>
|
|||
internal class CieLabPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private static readonly ColorSpaceConverter ColorSpaceConverter = new(); |
|||
|
|||
private const float Inv255 = 1.0f / 255.0f; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
Span<byte> l = data[0].GetSpan(); |
|||
Span<byte> a = data[1].GetSpan(); |
|||
Span<byte> b = data[2].GetSpan(); |
|||
|
|||
var color = default(TPixel); |
|||
int offset = 0; |
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); |
|||
for (int x = 0; x < pixelRow.Length; x++) |
|||
{ |
|||
var lab = new CieLab((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]); |
|||
var rgb = ColorSpaceConverter.ToRgb(lab); |
|||
|
|||
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); |
|||
pixelRow[x] = color; |
|||
|
|||
offset++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorSpaces; |
|||
using SixLabors.ImageSharp.ColorSpaces.Conversion; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation |
|||
{ |
|||
/// <summary>
|
|||
/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
|
|||
/// </summary>
|
|||
internal class CieLabTiffColor<TPixel> : TiffBaseColorDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private static readonly ColorSpaceConverter ColorSpaceConverter = new(); |
|||
|
|||
private const float Inv255 = 1.0f / 255.0f; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) |
|||
{ |
|||
var color = default(TPixel); |
|||
int offset = 0; |
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); |
|||
|
|||
for (int x = 0; x < pixelRow.Length; x++) |
|||
{ |
|||
float l = (data[offset] & 0xFF) * 100f * Inv255; |
|||
var lab = new CieLab(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); |
|||
var rgb = ColorSpaceConverter.ToRgb(lab); |
|||
|
|||
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); |
|||
pixelRow[x] = color; |
|||
|
|||
offset += 3; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.Drawing.Imaging; |
|||
using System.IO; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg |
|||
{ |
|||
[Config(typeof(Config.ShortMultiFramework))] |
|||
public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase |
|||
{ |
|||
protected override IEnumerable<string> InputImageSubfoldersOrFiles => |
|||
new[] |
|||
{ |
|||
TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, |
|||
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, |
|||
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, |
|||
TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, |
|||
TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, |
|||
}; |
|||
|
|||
[Params(InputImageCategory.AllImages)] |
|||
public override InputImageCategory InputCategory { get; set; } |
|||
|
|||
private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); |
|||
|
|||
private byte[] destBytes; |
|||
|
|||
public override void Setup() |
|||
{ |
|||
base.Setup(); |
|||
|
|||
this.configuration.MaxDegreeOfParallelism = 1; |
|||
const int MaxOutputSizeInBytes = 2 * 1024 * 1024; // ~2 MB
|
|||
this.destBytes = new byte[MaxOutputSizeInBytes]; |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void SystemDrawing() |
|||
=> this.ForEachStream( |
|||
sourceStream => |
|||
{ |
|||
using (var destStream = new MemoryStream(this.destBytes)) |
|||
using (var source = System.Drawing.Image.FromStream(sourceStream)) |
|||
using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) |
|||
{ |
|||
using (var g = Graphics.FromImage(destination)) |
|||
{ |
|||
g.InterpolationMode = InterpolationMode.HighQualityBicubic; |
|||
g.PixelOffsetMode = PixelOffsetMode.HighQuality; |
|||
g.CompositingQuality = CompositingQuality.HighQuality; |
|||
g.DrawImage(source, 0, 0, 400, 400); |
|||
} |
|||
|
|||
destination.Save(destStream, ImageFormat.Jpeg); |
|||
} |
|||
|
|||
return null; |
|||
}); |
|||
|
|||
[Benchmark] |
|||
public void ImageSharp() |
|||
=> this.ForEachStream( |
|||
sourceStream => |
|||
{ |
|||
using (var source = Image.Load<Rgba32>( |
|||
this.configuration, |
|||
sourceStream, |
|||
new JpegDecoder { IgnoreMetadata = true })) |
|||
{ |
|||
using var destStream = new MemoryStream(this.destBytes); |
|||
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); |
|||
source.SaveAsJpeg(destStream); |
|||
} |
|||
|
|||
return null; |
|||
}); |
|||
} |
|||
} |
|||
@ -1,103 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System; |
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.Drawing.Imaging; |
|||
using System.IO; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests; |
|||
using SDImage = System.Drawing.Image; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg |
|||
{ |
|||
[Config(typeof(Config.ShortMultiFramework))] |
|||
public class LoadResizeSave_ImageSpecific |
|||
{ |
|||
private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); |
|||
|
|||
private byte[] sourceBytes; |
|||
|
|||
private byte[] destBytes; |
|||
|
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params( |
|||
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, |
|||
TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, |
|||
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] |
|||
|
|||
public string TestImage { get; set; } |
|||
|
|||
[Params(false, true)] |
|||
public bool ParallelExec { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.configuration.MaxDegreeOfParallelism = |
|||
this.ParallelExec ? Environment.ProcessorCount : 1; |
|||
|
|||
this.sourceBytes = File.ReadAllBytes(this.TestImageFullPath); |
|||
|
|||
this.destBytes = new byte[this.sourceBytes.Length * 2]; |
|||
} |
|||
|
|||
[Benchmark(Baseline = true)] |
|||
public void SystemDrawing() |
|||
{ |
|||
using var sourceStream = new MemoryStream(this.sourceBytes); |
|||
using var destStream = new MemoryStream(this.destBytes); |
|||
using var source = SDImage.FromStream(sourceStream); |
|||
using var destination = new Bitmap(source.Width / 4, source.Height / 4); |
|||
using (var g = Graphics.FromImage(destination)) |
|||
{ |
|||
g.InterpolationMode = InterpolationMode.HighQualityBicubic; |
|||
g.PixelOffsetMode = PixelOffsetMode.HighQuality; |
|||
g.CompositingQuality = CompositingQuality.HighQuality; |
|||
g.DrawImage(source, 0, 0, 400, 400); |
|||
} |
|||
|
|||
destination.Save(destStream, ImageFormat.Jpeg); |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void ImageSharp() |
|||
{ |
|||
using (var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true })) |
|||
using (var destStream = new MemoryStream(this.destBytes)) |
|||
{ |
|||
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); |
|||
source.SaveAsJpeg(destStream); |
|||
} |
|||
} |
|||
|
|||
// RESULTS (2018 October 31):
|
|||
//
|
|||
// BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
|
|||
// Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
|
|||
// Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC
|
|||
// .NET Core SDK=2.1.403
|
|||
// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
|
|||
// Job-ZPEZGV : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
|
|||
// Job-SGOCJT : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
|
|||
//
|
|||
// Method | Runtime | TestImage | ParallelExec | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|
|||
// -------------- |-------- |----------------------------- |------------- |----------:|----------:|----------:|-------:|---------:|---------:|----------:|
|
|||
// SystemDrawing | Clr | Jpg/baseline/jpeg420exif.jpg | False | 64.88 ms | 3.735 ms | 0.2110 ms | 1.00 | 0.00 | 250.0000 | 791.07 KB |
|
|||
// ImageSharp | Clr | Jpg/baseline/jpeg420exif.jpg | False | 129.53 ms | 23.423 ms | 1.3234 ms | 2.00 | 0.02 | - | 50.09 KB |
|
|||
// | | | | | | | | | | |
|
|||
// SystemDrawing | Core | Jpg/baseline/jpeg420exif.jpg | False | 65.87 ms | 10.488 ms | 0.5926 ms | 1.00 | 0.00 | 250.0000 | 789.79 KB |
|
|||
// ImageSharp | Core | Jpg/baseline/jpeg420exif.jpg | False | 92.00 ms | 7.241 ms | 0.4091 ms | 1.40 | 0.01 | - | 46.36 KB |
|
|||
// | | | | | | | | | | |
|
|||
// SystemDrawing | Clr | Jpg/baseline/jpeg420exif.jpg | True | 64.23 ms | 5.998 ms | 0.3389 ms | 1.00 | 0.00 | 250.0000 | 791.07 KB |
|
|||
// ImageSharp | Clr | Jpg/baseline/jpeg420exif.jpg | True | 82.63 ms | 29.320 ms | 1.6566 ms | 1.29 | 0.02 | - | 57.59 KB |
|
|||
// | | | | | | | | | | |
|
|||
// SystemDrawing | Core | Jpg/baseline/jpeg420exif.jpg | True | 64.20 ms | 6.560 ms | 0.3707 ms | 1.00 | 0.00 | 250.0000 | 789.79 KB |
|
|||
// ImageSharp | Core | Jpg/baseline/jpeg420exif.jpg | True | 68.08 ms | 18.376 ms | 1.0383 ms | 1.06 | 0.01 | - | 50.49 KB |
|
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Formats.Jpeg; |
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; |
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
[Trait("Format", "Jpg")] |
|||
public partial class JpegDecoderTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(1, 0, JpegColorSpace.Grayscale)] |
|||
[InlineData(3, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.RGB)] |
|||
[InlineData(3, JpegConstants.Adobe.ColorTransformYCbCr, JpegColorSpace.YCbCr)] |
|||
[InlineData(4, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.Cmyk)] |
|||
[InlineData(4, JpegConstants.Adobe.ColorTransformYcck, JpegColorSpace.Ycck)] |
|||
internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte componentCount, byte adobeFlag, JpegColorSpace expectedColorSpace) |
|||
{ |
|||
byte[] adobeMarkerPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, adobeFlag }; |
|||
ProfileResolver.AdobeMarker.CopyTo(adobeMarkerPayload); |
|||
_ = AdobeMarker.TryParse(adobeMarkerPayload, out AdobeMarker adobeMarker); |
|||
|
|||
JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker); |
|||
|
|||
Assert.Equal(expectedColorSpace, actualColorSpace); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2)] |
|||
[InlineData(5)] |
|||
public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) |
|||
{ |
|||
AdobeMarker adobeMarker = default; |
|||
|
|||
Assert.Throws<NotSupportedException>(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1, JpegColorSpace.Grayscale)] |
|||
[InlineData(3, JpegColorSpace.YCbCr)] |
|||
[InlineData(4, JpegColorSpace.Cmyk)] |
|||
internal void DeduceJpegColorSpace_ShouldReturnValidColorSpace(byte componentCount, JpegColorSpace expectedColorSpace) |
|||
{ |
|||
JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount); |
|||
|
|||
Assert.Equal(expectedColorSpace, actualColorSpace); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2)] |
|||
[InlineData(5)] |
|||
public void DeduceJpegColorSpace_ShouldThrowOnUnsupportedComponentCount(byte componentCount) |
|||
=> Assert.Throws<NotSupportedException>(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount)); |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
|||
{ |
|||
[Trait("Format", "Jpg")] |
|||
public class SpectralConverterTests |
|||
{ |
|||
// Test for null target size, i.e. when no scaling is needed
|
|||
[Theory] |
|||
[InlineData(1, 1)] |
|||
[InlineData(800, 400)] |
|||
[InlineData(2354, 4847)] |
|||
public void CalculateResultingImageSize_Null_TargetSize(int width, int height) |
|||
{ |
|||
Size inputSize = new(width, height); |
|||
|
|||
Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, null, out int blockPixelSize); |
|||
|
|||
Assert.Equal(expected: 8, blockPixelSize); |
|||
Assert.Equal(inputSize, outputSize); |
|||
} |
|||
|
|||
// Test for 'perfect' dimensions, i.e. dimensions divisible by 8, with exact scaled size match
|
|||
[Theory] |
|||
[InlineData(800, 400, 800, 400, 8)] |
|||
[InlineData(800, 400, 400, 200, 4)] |
|||
[InlineData(800, 400, 200, 100, 2)] |
|||
[InlineData(800, 400, 100, 50, 1)] |
|||
public void CalculateResultingImageSize_Perfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) |
|||
{ |
|||
Size inputSize = new(inW, inH); |
|||
Size targetSize = new(tW, tH); |
|||
|
|||
Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); |
|||
|
|||
Assert.Equal(expectedBlockSize, blockPixelSize); |
|||
Assert.Equal(outputSize, targetSize); |
|||
} |
|||
|
|||
// Test for 'imperfect' dimensions, i.e. dimensions NOT divisible by 8, with exact scaled size match
|
|||
[Theory] |
|||
[InlineData(7, 14, 7, 14, 8)] |
|||
[InlineData(7, 14, 4, 7, 4)] |
|||
[InlineData(7, 14, 2, 4, 2)] |
|||
[InlineData(7, 14, 1, 2, 1)] |
|||
public void CalculateResultingImageSize_Imperfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) |
|||
{ |
|||
Size inputSize = new(inW, inH); |
|||
Size targetSize = new(tW, tH); |
|||
|
|||
Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); |
|||
|
|||
Assert.Equal(expectedBlockSize, blockPixelSize); |
|||
Assert.Equal(outputSize, targetSize); |
|||
} |
|||
|
|||
// Test for inexact target and output sizes match
|
|||
[Theory] |
|||
[InlineData(7, 14, 4, 6, 4, 7, 4)] |
|||
[InlineData(7, 14, 1, 1, 1, 2, 1)] |
|||
[InlineData(800, 400, 999, 600, 800, 400, 8)] |
|||
[InlineData(800, 400, 390, 150, 400, 200, 4)] |
|||
[InlineData(804, 1198, 500, 800, 804, 1198, 8)] |
|||
public void CalculateResultingImageSize_Inexact_Target_Size(int inW, int inH, int tW, int tH, int exW, int exH, int expectedBlockSize) |
|||
{ |
|||
Size inputSize = new(inW, inH); |
|||
Size targetSize = new(tW, tH); |
|||
Size expectedSize = new(exW, exH); |
|||
|
|||
Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); |
|||
|
|||
Assert.Equal(expectedBlockSize, blockPixelSize); |
|||
Assert.Equal(expectedSize, outputSize); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:13bc9da102f85124855217fad757ca907f5d68442e54e3b7039ac048d7b2ad3f |
|||
size 25791 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:13bc9da102f85124855217fad757ca907f5d68442e54e3b7039ac048d7b2ad3f |
|||
size 25791 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6f9481c91c58ca7bbab9de4b9ae95fe4a2197ae4b6ef6b15b72d4858aba3a1a4 |
|||
size 25782 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:7542b5b3abe049614f2ddaf78ffe995edac13e768f0b2fc9f324c6ef43b379eb |
|||
size 1312046 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:28592d9da8d51f60700b7136369d2d6bd40550d5f8c7758e570b5e624c71a3e4 |
|||
size 1307488 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:6affced5550e51441c4cde7f1770d4e57cfa594bd271a12f9571359733c2185d |
|||
size 55346 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:72fd7fa941aa6201faa5368349764b4c17b582bee9be65861bad6308a8c5e4fe |
|||
size 4898 |
|||
Loading…
Reference in new issue