mirror of https://github.com/SixLabors/ImageSharp
17 changed files with 355 additions and 633 deletions
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
// 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 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; |
||||
|
|
||||
|
int blocksRowsPerStep = this.Component.SamplingFactors.Height; |
||||
|
|
||||
|
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 + MathF.Ceiling(maximumValue / 2), 0, maximumValue)); |
||||
|
|
||||
|
// Save to the intermediate buffer
|
||||
|
int xColorBufferStart = xBlock * this.BlockAreaSize.Width; |
||||
|
colorBufferRow[xColorBufferStart] = dc; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,124 +0,0 @@ |
|||||
// 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 |
|
||||
{ |
|
||||
/// <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 <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); |
|
||||
} |
|
||||
|
|
||||
/// <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 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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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); |
|
||||
} |
|
||||
} |
|
||||
@ -1,139 +0,0 @@ |
|||||
// 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 |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Encapsulates spectral data to rgba32 processing for one component.
|
|
||||
/// </summary>
|
|
||||
internal class JpegComponentPostProcessor8 : 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 <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; |
|
||||
|
|
||||
private readonly float dcDequantizer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor8"/> class.
|
|
||||
/// </summary>
|
|
||||
public JpegComponentPostProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) |
|
||||
{ |
|
||||
// TODO: this must be a variable depending on dct scale factor
|
|
||||
const int blockSize = 1; |
|
||||
|
|
||||
this.frame = frame; |
|
||||
|
|
||||
this.component = component; |
|
||||
|
|
||||
this.rawJpeg = rawJpeg; |
|
||||
|
|
||||
this.dcDequantizer = rawJpeg.QuantizationTables[this.component.QuantizationTableIndex][0]; |
|
||||
|
|
||||
this.blockAreaSize = this.component.SubSamplingDivisors * blockSize; |
|
||||
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>( |
|
||||
postProcessorBufferSize.Width, |
|
||||
postProcessorBufferSize.Height, |
|
||||
this.blockAreaSize.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 blocksRowsPerStep = this.component.SamplingFactors.Height; |
|
||||
|
|
||||
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 DC - averaged 8x8 pixel value
|
|
||||
float DC = blockRow[xBlock][0]; |
|
||||
|
|
||||
// dequantization
|
|
||||
DC *= this.dcDequantizer; |
|
||||
|
|
||||
// Normalize & round
|
|
||||
DC = (float)Math.Round(Numerics.Clamp(DC + MathF.Ceiling(maximumValue / 2), 0, maximumValue)); |
|
||||
|
|
||||
// Save to the intermediate buffer
|
|
||||
int xColorBufferStart = xBlock * this.blockAreaSize.Width; |
|
||||
colorBufferRow[xColorBufferStart] = DC; |
|
||||
|
|
||||
//// 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);
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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); |
|
||||
} |
|
||||
} |
|
||||
@ -1,261 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Buffers; |
|
||||
using System.Linq; |
|
||||
using System.Threading; |
|
||||
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|
||||
{ |
|
||||
// TODO: docs
|
|
||||
internal class ResizingSpectralConverter<TPixel> : SpectralConverter<TPixel>, IDisposable |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// <see cref="Configuration"/> instance associated with current
|
|
||||
/// decoding routine.
|
|
||||
/// </summary>
|
|
||||
private readonly Configuration configuration; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Target image size after scaled decoding.
|
|
||||
/// </summary>
|
|
||||
private readonly Size targetSize; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Jpeg component converters from decompressed spectral to color data.
|
|
||||
/// </summary>
|
|
||||
private JpegComponentPostProcessor8[] componentProcessors; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Resulting 2D pixel buffer.
|
|
||||
/// </summary>
|
|
||||
private Buffer2D<TPixel> pixelBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// How many pixel rows are processed in one 'stride'.
|
|
||||
/// </summary>
|
|
||||
private int pixelRowsPerStep; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// How many pixel rows were processed.
|
|
||||
/// </summary>
|
|
||||
private int pixelRowCounter; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Intermediate buffer of RGB components used in color conversion.
|
|
||||
/// </summary>
|
|
||||
private IMemoryOwner<byte> rgbBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Proxy buffer used in packing from RGB to target TPixel pixels.
|
|
||||
/// </summary>
|
|
||||
private IMemoryOwner<TPixel> paddedProxyPixelRow; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Color converter from jpeg color space to target pixel color space.
|
|
||||
/// </summary>
|
|
||||
private JpegColorConverterBase colorConverter; |
|
||||
|
|
||||
public ResizingSpectralConverter(Configuration configuration, Size targetSize) |
|
||||
{ |
|
||||
this.configuration = configuration; |
|
||||
this.targetSize = targetSize; |
|
||||
} |
|
||||
|
|
||||
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) |
|
||||
{ |
|
||||
MemoryAllocator allocator = this.configuration.MemoryAllocator; |
|
||||
|
|
||||
(int width, int height, int scaleDenominator) = GetScaledImageDimensions(frame.PixelWidth, frame.PixelHeight, this.targetSize.Width, this.targetSize.Height); |
|
||||
|
|
||||
// iteration data
|
|
||||
int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); |
|
||||
int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); |
|
||||
|
|
||||
this.pixelBuffer = allocator.Allocate2D<TPixel>( |
|
||||
width, |
|
||||
height, |
|
||||
this.configuration.PreferContiguousImageBuffers); |
|
||||
|
|
||||
this.paddedProxyPixelRow = allocator.Allocate<TPixel>(width + 3); |
|
||||
|
|
||||
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
|
|
||||
this.rgbBuffer = allocator.Allocate<byte>(width * 3); |
|
||||
|
|
||||
// component processors from spectral to Rgba32
|
|
||||
int blockPixelSize = 8 / scaleDenominator; |
|
||||
this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize; |
|
||||
|
|
||||
// color converter
|
|
||||
JpegColorConverterBase converter = this.GetColorConverter(frame, jpegData); |
|
||||
this.colorConverter = converter; |
|
||||
|
|
||||
int bufferWidth = majorBlockWidth * blockPixelSize; |
|
||||
int batchSize = converter.ElementsPerBatch; |
|
||||
int correctedBufferWidth = bufferWidth + (batchSize - (bufferWidth % batchSize)); |
|
||||
var postProcessorBufferSize = new Size(correctedBufferWidth, this.pixelRowsPerStep); |
|
||||
|
|
||||
this.componentProcessors = new JpegComponentPostProcessor8[frame.Components.Length]; |
|
||||
for (int i = 0; i < this.componentProcessors.Length; i++) |
|
||||
{ |
|
||||
this.componentProcessors[i] = new JpegComponentPostProcessor8(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public override void ConvertStrideBaseline() |
|
||||
{ |
|
||||
// Convert next pixel stride using single spectral `stride'
|
|
||||
// Note that zero passing eliminates the need of virtual call
|
|
||||
// from JpegComponentPostProcessor
|
|
||||
this.ConvertStride(spectralStep: 0); |
|
||||
|
|
||||
foreach (JpegComponentPostProcessor8 cpp in this.componentProcessors) |
|
||||
{ |
|
||||
cpp.ClearSpectralBuffers(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts single spectral jpeg stride to color stride.
|
|
||||
/// </summary>
|
|
||||
/// <param name="spectralStep">Spectral stride index.</param>
|
|
||||
private void ConvertStride(int spectralStep) |
|
||||
{ |
|
||||
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); |
|
||||
|
|
||||
for (int i = 0; i < this.componentProcessors.Length; i++) |
|
||||
{ |
|
||||
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); |
|
||||
} |
|
||||
|
|
||||
int width = this.pixelBuffer.Width; |
|
||||
|
|
||||
for (int yy = this.pixelRowCounter; yy < maxY; yy++) |
|
||||
{ |
|
||||
int y = yy - this.pixelRowCounter; |
|
||||
|
|
||||
var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); |
|
||||
|
|
||||
this.colorConverter.ConvertToRgbInplace(values); |
|
||||
values = values.Slice(0, width); // slice away Jpeg padding
|
|
||||
|
|
||||
Span<byte> r = this.rgbBuffer.Slice(0, width); |
|
||||
Span<byte> g = this.rgbBuffer.Slice(width, width); |
|
||||
Span<byte> b = this.rgbBuffer.Slice(width * 2, width); |
|
||||
|
|
||||
SimdUtils.NormalizedFloatToByteSaturate(values.Component0.Slice(0, width), r); |
|
||||
SimdUtils.NormalizedFloatToByteSaturate(values.Component1.Slice(0, width), g); |
|
||||
SimdUtils.NormalizedFloatToByteSaturate(values.Component2.Slice(0, width), b); |
|
||||
|
|
||||
// PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
|
|
||||
// If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
|
|
||||
// pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
|
|
||||
if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow)) |
|
||||
{ |
|
||||
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan(); |
|
||||
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); |
|
||||
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.pixelRowCounter += this.pixelRowsPerStep; |
|
||||
} |
|
||||
|
|
||||
public override Buffer2D<TPixel> GetPixelBuffer(CancellationToken cancellationToken) |
|
||||
{ |
|
||||
if (!this.Converted) |
|
||||
{ |
|
||||
int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); |
|
||||
|
|
||||
for (int step = 0; step < steps; step++) |
|
||||
{ |
|
||||
cancellationToken.ThrowIfCancellationRequested(); |
|
||||
this.ConvertStride(step); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return this.pixelBuffer; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
if (this.componentProcessors != null) |
|
||||
{ |
|
||||
foreach (JpegComponentPostProcessor8 cpp in this.componentProcessors) |
|
||||
{ |
|
||||
cpp.Dispose(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.rgbBuffer?.Dispose(); |
|
||||
this.paddedProxyPixelRow?.Dispose(); |
|
||||
} |
|
||||
|
|
||||
// TODO: docs, code formatting
|
|
||||
private static readonly (int Num, int Denom)[] ScalingFactors = new (int, int)[] |
|
||||
{ |
|
||||
/* upscaling factors */ |
|
||||
// (16, 8),
|
|
||||
// (15, 8),
|
|
||||
// (14, 8),
|
|
||||
// (13, 8),
|
|
||||
// (12, 8),
|
|
||||
// (11, 8),
|
|
||||
// (10, 8),
|
|
||||
// (9, 8),
|
|
||||
|
|
||||
/* no scaling */ |
|
||||
(8, 8), |
|
||||
|
|
||||
/* downscaling factors */ |
|
||||
// (7, 8), // 8 => 7
|
|
||||
// (6, 8), // 8 => 6
|
|
||||
// (5, 8), // 8 => 5
|
|
||||
// (4, 8), // 1/2 dct scaling - currently not supported
|
|
||||
// (3, 8), // 8 => 3
|
|
||||
// (2, 8), // 1/4 dct scaling - currently not supported
|
|
||||
(1, 8), // 1/8 dct scaling
|
|
||||
}; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// TODO: docs, code formatting
|
|
||||
/// </summary>
|
|
||||
/// <param name="iWidth">Initial image width.</param>
|
|
||||
/// <param name="iHeight">Initial image height.</param>
|
|
||||
/// <param name="tWidth">Target image width.</param>
|
|
||||
/// <param name="tHeight">Target image height.</param>
|
|
||||
private static (int Width, int Height, int ScaleDenominator) GetScaledImageDimensions(int iWidth, int iHeight, int tWidth, int tHeight) |
|
||||
{ |
|
||||
int output_width = iWidth; |
|
||||
int output_height = iHeight; |
|
||||
int dct_scale = 8; |
|
||||
|
|
||||
for (int i = 1; i < ScalingFactors.Length; i++) |
|
||||
{ |
|
||||
(int num, int denom) = ScalingFactors[i]; |
|
||||
int scaledw = (int)Numerics.DivideCeil((uint)(iWidth * num), (uint)denom); |
|
||||
int scaledh = (int)Numerics.DivideCeil((uint)(iHeight * num), (uint)denom); |
|
||||
|
|
||||
if (scaledw < tWidth || scaledh < tHeight) |
|
||||
{ |
|
||||
dct_scale = 8 / ScalingFactors[i - 1].Num; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
output_width = scaledw; |
|
||||
output_height = scaledh; |
|
||||
} |
|
||||
|
|
||||
return (output_width, output_height, dct_scale); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System.Threading; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder |
|
||||
{ |
|
||||
// TODO: docs
|
|
||||
internal abstract class SpectralConverter<TPixel> : SpectralConverter |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Gets converted pixel buffer.
|
|
||||
/// </summary>
|
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
|
||||
/// <returns>Pixel buffer.</returns>
|
|
||||
public abstract Buffer2D<TPixel> GetPixelBuffer(CancellationToken cancellationToken); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue