Browse Source

Setup

pull/2120/head
Dmitry Pentin 4 years ago
parent
commit
96a3c1903f
  1. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  2. 116
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  3. 117
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs
  4. 25
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs
  5. 97
      src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs
  6. 12
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs
  7. 128
      src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs
  8. 39
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Converter used to convert jpeg spectral data to color pixels.
/// Converter used to convert jpeg spectral data to pixels.
/// </summary>
internal abstract class SpectralConverter
{

116
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -128,6 +128,72 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2;
}
public void Encode<TPixel>(Image<TPixel> image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// DEBUG INITIALIZATION SETUP
var frame = new JpegFrame(configuration.MemoryAllocator, image, componentCount: 3);
frame.Init(1, 1);
frame.AllocateComponents(fullScan: false);
var spectralConverter = new SpectralConverter<TPixel>(configuration);
spectralConverter.InjectFrameData(frame, image, quantTables);
// DEBUG ENCODING SETUP
int mcu = 0;
int mcusPerColumn = frame.McusPerColumn;
int mcusPerLine = frame.McusPerLine;
for (int j = 0; j < mcusPerColumn; j++)
{
cancellationToken.ThrowIfCancellationRequested();
// Convert from pixels to spectral via given converter
spectralConverter.ConvertStrideBaseline();
// decode from binary to spectral
for (int i = 0; i < mcusPerLine; i++)
{
// Scan an interleaved mcu... process components in order
int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < frame.ComponentCount; k++)
{
JpegComponent component = frame.Components[k];
ref HuffmanLut dcHuffmanTable = ref this.huffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.huffmanTables[component.AcTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
// Scan out an mcu's worth of this component; that's just determined
// by the basic H and V specified for the component
for (int y = 0; y < v; y++)
{
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++)
{
int blockCol = (mcuCol * h) + x;
this.WriteBlock(
component,
ref Unsafe.Add(ref blockRef, blockCol),
ref dcHuffmanTable,
ref acHuffmanTable);
}
}
}
// After all interleaved components, that's an interleaved MCU
mcu++;
}
}
this.FlushRemainingBytes();
}
/// <summary>
/// Encodes the image with no subsampling.
/// </summary>
@ -441,6 +507,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
return dc;
}
private void WriteBlock(
JpegComponent component,
ref Block8x8 block,
ref HuffmanLut dcTable,
ref HuffmanLut acTable)
{
// Emit the DC delta.
int dc = block[0];
this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor);
component.DcPredictor = dc;
// Emit the AC components.
int[] acHuffTable = acTable.Values;
nint lastValuableIndex = block.GetLastNonZeroIndex();
int runLength = 0;
ref short blockRef = ref Unsafe.As<Block8x8, short>(ref block);
for (nint zig = 1; zig <= lastValuableIndex; zig++)
{
const int zeroRun1 = 1 << 4;
const int zeroRun16 = 16 << 4;
int ac = Unsafe.Add(ref blockRef, zig);
if (ac == 0)
{
runLength += zeroRun1;
}
else
{
while (runLength >= zeroRun16)
{
this.EmitHuff(acHuffTable, 0xf0);
runLength -= zeroRun16;
}
this.EmitHuffRLE(acHuffTable, runLength, ac);
runLength = 0;
}
}
// if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over
// this can be done for any number of trailing zeros, even when all 63 ac values are zero
// (Block8x8F.Size - 1) == 63 - last index of the mcu elements
if (lastValuableIndex != Block8x8F.Size - 1)
{
this.EmitHuff(acHuffTable, 0x00);
}
}
/// <summary>
/// Emits the most significant count of bits to the buffer.
/// </summary>

117
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponent.cs

@ -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);
}
}
}

25
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegComponentPostProcessor.cs

@ -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()
{
}
}
}

97
src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs

@ -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);
}
}
}
}

12
src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs

@ -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
{
}
}

128
src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs

@ -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;
}
}
}

39
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -131,25 +131,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the scan header.
this.WriteStartOfScan(componentCount, componentIds);
var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable };
new HuffmanScanEncoder(3, stream).Encode(image, quantTables, Configuration.Default, cancellationToken);
// Write the scan compressed data.
switch (this.colorType)
{
case JpegColorType.YCbCrRatio444:
new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegColorType.YCbCrRatio420:
new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegColorType.Luminance:
new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
break;
case JpegColorType.Rgb:
new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
break;
default:
// all other non-supported color types are checked at the start of this method
break;
}
//switch (this.colorType)
//{
// case JpegColorType.YCbCrRatio444:
// new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
// break;
// case JpegColorType.YCbCrRatio420:
// new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
// break;
// case JpegColorType.Luminance:
// new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
// break;
// case JpegColorType.Rgb:
// new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
// break;
// default:
// // all other non-supported color types are checked at the start of this method
// break;
//}
// Write the End Of Image marker.
this.WriteEndOfImageMarker();

Loading…
Cancel
Save