mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 517 additions and 19 deletions
@ -0,0 +1,117 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a single frame component.
|
|||
/// </summary>
|
|||
internal class JpegComponent : IDisposable |
|||
{ |
|||
private readonly MemoryAllocator memoryAllocator; |
|||
|
|||
public JpegComponent(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, byte quantizationTableIndex) |
|||
{ |
|||
this.memoryAllocator = memoryAllocator; |
|||
|
|||
this.HorizontalSamplingFactor = horizontalFactor; |
|||
this.VerticalSamplingFactor = verticalFactor; |
|||
this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); |
|||
|
|||
this.QuantizationTableIndex = quantizationTableIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets DC coefficient predictor.
|
|||
/// </summary>
|
|||
public int DcPredictor { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal sampling factor.
|
|||
/// </summary>
|
|||
public int HorizontalSamplingFactor { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical sampling factor.
|
|||
/// </summary>
|
|||
public int VerticalSamplingFactor { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public Buffer2D<Block8x8> SpectralBlocks { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public Size SubSamplingDivisors { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public int QuantizationTableIndex { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public Size SizeInBlocks { get; private set; } |
|||
|
|||
/// <inheritdoc />
|
|||
public Size SamplingFactors { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of blocks per line.
|
|||
/// </summary>
|
|||
public int WidthInBlocks { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of blocks per column.
|
|||
/// </summary>
|
|||
public int HeightInBlocks { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index for the DC Huffman table.
|
|||
/// </summary>
|
|||
public int DcTableId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index for the AC Huffman table.
|
|||
/// </summary>
|
|||
public int AcTableId { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.SpectralBlocks?.Dispose(); |
|||
this.SpectralBlocks = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes component for future buffers initialization.
|
|||
/// </summary>
|
|||
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
|
|||
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
|
|||
public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) |
|||
{ |
|||
this.WidthInBlocks = (int)MathF.Ceiling( |
|||
MathF.Ceiling(frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); |
|||
|
|||
this.HeightInBlocks = (int)MathF.Ceiling( |
|||
MathF.Ceiling(frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); |
|||
|
|||
int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; |
|||
int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; |
|||
this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); |
|||
|
|||
this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); |
|||
|
|||
if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) |
|||
{ |
|||
JpegThrowHelper.ThrowBadSampling(); |
|||
} |
|||
} |
|||
|
|||
public void AllocateSpectral(bool fullScan) |
|||
{ |
|||
int spectralAllocWidth = this.SizeInBlocks.Width; |
|||
int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; |
|||
|
|||
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
internal class JpegComponentPostProcessor : IDisposable |
|||
{ |
|||
private readonly JpegComponent component; |
|||
|
|||
private readonly Block8x8F dequantTable; |
|||
|
|||
public JpegComponentPostProcessor(JpegComponent component, Block8x8F dequantTable) |
|||
{ |
|||
this.component = component; |
|||
this.dequantTable = dequantTable; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
/// <summary>
|
|||
/// Represent a single jpeg frame.
|
|||
/// </summary>
|
|||
internal sealed class JpegFrame : IDisposable |
|||
{ |
|||
public JpegFrame(MemoryAllocator allocator, Image image, byte componentCount) |
|||
{ |
|||
this.PixelWidth = image.Width; |
|||
this.PixelHeight = image.Height; |
|||
|
|||
if (componentCount != 3) |
|||
{ |
|||
throw new ArgumentException("This is YCbCr debug path only."); |
|||
} |
|||
|
|||
this.Components = new JpegComponent[] |
|||
{ |
|||
new JpegComponent(allocator, 1, 1, 0), |
|||
new JpegComponent(allocator, 1, 1, 1), |
|||
new JpegComponent(allocator, 1, 1, 1), |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of pixel per row.
|
|||
/// </summary>
|
|||
public int PixelHeight { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of pixels per line.
|
|||
/// </summary>
|
|||
public int PixelWidth { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of components within a frame.
|
|||
/// </summary>
|
|||
public int ComponentCount => this.Components.Length; |
|||
|
|||
/// <summary>
|
|||
/// Gets the frame component collection.
|
|||
/// </summary>
|
|||
public JpegComponent[] Components { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of MCU's per line.
|
|||
/// </summary>
|
|||
public int McusPerLine { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of MCU's per column.
|
|||
/// </summary>
|
|||
public int McusPerColumn { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
for (int i = 0; i < this.Components.Length; i++) |
|||
{ |
|||
this.Components[i]?.Dispose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Allocates the frame component blocks.
|
|||
/// </summary>
|
|||
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
|
|||
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
|
|||
public void Init(int maxSubFactorH, int maxSubFactorV) |
|||
{ |
|||
this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); |
|||
this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); |
|||
|
|||
for (int i = 0; i < this.ComponentCount; i++) |
|||
{ |
|||
JpegComponent component = this.Components[i]; |
|||
component.Init(this, maxSubFactorH, maxSubFactorV); |
|||
} |
|||
} |
|||
|
|||
public void AllocateComponents(bool fullScan) |
|||
{ |
|||
for (int i = 0; i < this.ComponentCount; i++) |
|||
{ |
|||
JpegComponent component = this.Components[i]; |
|||
component.AllocateSpectral(fullScan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
/// <summary>
|
|||
/// Converter used to convert pixel data to jpeg spectral data.
|
|||
/// </summary>
|
|||
internal abstract class SpectralConverter |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder |
|||
{ |
|||
|
|||
/// <inheritdoc/>
|
|||
internal class SpectralConverter<TPixel> : SpectralConverter |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private readonly Configuration configuration; |
|||
|
|||
private JpegComponentPostProcessor[] componentProcessors; |
|||
|
|||
private int pixelRowsPerStep; |
|||
|
|||
private int pixelRowCounter; |
|||
|
|||
private IMemoryOwner<byte> rgbBuffer; |
|||
|
|||
private IMemoryOwner<TPixel> paddedProxyPixelRow; |
|||
|
|||
private Buffer2D<TPixel> pixelBuffer; |
|||
|
|||
public SpectralConverter(Configuration configuration) => |
|||
this.configuration = configuration; |
|||
|
|||
public void InjectFrameData(JpegFrame frame, Image<TPixel> image, Block8x8F[] dequantTables) |
|||
{ |
|||
MemoryAllocator allocator = this.configuration.MemoryAllocator; |
|||
|
|||
// iteration data
|
|||
int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); |
|||
int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); |
|||
|
|||
const int blockPixelHeight = 8; |
|||
this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; |
|||
|
|||
// pixel buffer of the image
|
|||
// currently codec only supports encoding single frame jpegs
|
|||
this.pixelBuffer = image.GetRootFramePixelBuffer(); |
|||
|
|||
// ???
|
|||
//this.paddedProxyPixelRow = allocator.Allocate<TPixel>(frame.PixelWidth + 3);
|
|||
|
|||
// component processors from spectral to Rgba32
|
|||
const int blockPixelWidth = 8; |
|||
var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); |
|||
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; |
|||
for (int i = 0; i < this.componentProcessors.Length; i++) |
|||
{ |
|||
JpegComponent component = frame.Components[i]; |
|||
this.componentProcessors[i] = new JpegComponentPostProcessor(component, dequantTables[component.QuantizationTableIndex]); |
|||
} |
|||
|
|||
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
|
|||
//this.rgbBuffer = allocator.Allocate<byte>(frame.PixelWidth * 3);
|
|||
|
|||
// color converter from Rgba32 to TPixel
|
|||
//this.colorConverter = this.GetColorConverter(frame, jpegData);
|
|||
} |
|||
|
|||
public 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); |
|||
} |
|||
|
|||
private void ConvertStride(int spectralStep) |
|||
{ |
|||
// 1. Unpack from TPixel to r/g/b planes
|
|||
// 2. Byte r/g/b planes to normalized float r/g/b planes
|
|||
// 3. Convert from r/g/b planes to target pixel type with JpegColorConverter
|
|||
// 4. Convert color buffer to spectral blocks with component post processors
|
|||
|
|||
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, r);
|
|||
// SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g);
|
|||
// SimdUtils.NormalizedFloatToByteSaturate(values.Component2, 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; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue