Browse Source

Implemented quantization and coding tables selectors

pull/2120/head
Dmitry Pentin 4 years ago
parent
commit
fdb8b97f6f
  1. 9
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  2. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  3. 4
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  4. 135
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

9
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs

@ -31,6 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
public static readonly HuffmanLut[] TheHuffmanLut = new HuffmanLut[4];
public static readonly HuffmanLut[] DcHuffmanLut = new HuffmanLut[2];
public static readonly HuffmanLut[] AcHuffmanLut = new HuffmanLut[2];
/// <summary>
/// Initializes static members of the <see cref="HuffmanLut"/> struct.
/// </summary>
@ -41,6 +44,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
TheHuffmanLut[i] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[i]);
}
// TODO: REWRITE THIS
DcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[0]);
DcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[2]);
AcHuffmanLut[0] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[1]);
AcHuffmanLut[1] = new HuffmanLut(HuffmanSpec.TheHuffmanSpecs[3]);
}
/// <summary>

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

@ -132,8 +132,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
where TPixel : unmanaged, IPixel<TPixel>
{
// DEBUG INITIALIZATION SETUP
this.huffmanTables = HuffmanLut.TheHuffmanLut;
frame.Init(1, 1);
frame.AllocateComponents(fullScan: false);
@ -161,8 +159,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
JpegComponent component = frame.Components[k];
ref HuffmanLut dcHuffmanTable = ref this.huffmanTables[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref this.huffmanTables[component.AcTableId];
ref HuffmanLut dcHuffmanTable = ref HuffmanLut.DcHuffmanLut[component.DcTableId];
ref HuffmanLut acHuffmanTable = ref HuffmanLut.AcHuffmanLut[component.AcTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;

4
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegComponentConfig[] Components { get; }
public JpegFrameConfig PopulateComponent(int index, int id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex)
public JpegFrameConfig PopulateComponent(int index, byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex)
{
this.Components[index] = new JpegComponentConfig
{
@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public class JpegComponentConfig
{
public int Id { get; set; }
public byte Id { get; set; }
public int HorizontalSampleFactor { get; set; }

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

@ -89,7 +89,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Compute number of components based on color type in options.
int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
ReadOnlySpan<byte> componentIds = this.GetComponentIds();
// TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
// Initialize the quantization tables.
@ -117,13 +116,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds);
this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig.Components);
// Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount);
// Write the scan header.
this.WriteStartOfScan(componentCount, componentIds);
this.WriteStartOfScan(componentCount, this.frameConfig.Components);
var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType));
var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable };
@ -200,15 +199,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
|| colorType == JpegColorType.Luminance
|| colorType == JpegColorType.Rgb;
/// <summary>
/// Gets the component ids.
/// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3.
/// </summary>
/// <returns>The component Ids.</returns>
private ReadOnlySpan<byte> GetComponentIds() => this.colorType == JpegColorType.Rgb
? new ReadOnlySpan<byte>(new byte[] { 82, 71, 66 })
: new ReadOnlySpan<byte>(new byte[] { 1, 2, 3 });
/// <summary>
/// Writes data to "Define Quantization Tables" block for QuantIndex.
/// </summary>
@ -646,91 +636,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="height">The height of the image.</param>
/// <param name="componentCount">The number of components in a pixel.</param>
/// <param name="componentIds">The component Id's.</param>
private void WriteStartOfFrame(int width, int height, int componentCount, ReadOnlySpan<byte> componentIds)
private void WriteStartOfFrame(int width, int height, JpegComponentConfig[] components)
{
// This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
// and doesn't incur any allocation at all.
// "default" to 4:2:0
ReadOnlySpan<byte> subsamples = new byte[]
{
0x22,
0x11,
0x11
};
ReadOnlySpan<byte> chroma = new byte[]
{
0x00,
0x01,
0x01
};
if (this.colorType == JpegColorType.Luminance)
{
subsamples = new byte[]
{
0x11,
0x00,
0x00
};
}
else
{
switch (this.colorType)
{
case JpegColorType.YCbCrRatio444:
case JpegColorType.Rgb:
subsamples = new byte[]
{
0x11,
0x11,
0x11
};
if (this.colorType == JpegColorType.Rgb)
{
chroma = new byte[]
{
0x00,
0x00,
0x00
};
}
break;
case JpegColorType.YCbCrRatio420:
subsamples = new byte[]
{
0x22,
0x11,
0x11
};
break;
}
}
// Length (high byte, low byte), 8 + components * 3.
int markerlen = 8 + (3 * componentCount);
int markerlen = 8 + (3 * components.Length);
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen);
this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
this.buffer[1] = (byte)(height >> 8);
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[3] = (byte)(width >> 8);
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[5] = (byte)componentCount;
this.buffer[5] = (byte)components.Length;
for (int i = 0; i < componentCount; i++)
// Components data
for (int i = 0; i < components.Length; i++)
{
int i3 = 3 * i;
// Component ID.
Span<byte> bufferSpan = this.buffer.AsSpan(i3 + 6, 3);
bufferSpan[2] = chroma[i];
bufferSpan[1] = subsamples[i];
bufferSpan[0] = componentIds[i];
// Quantization table selector
bufferSpan[2] = (byte)components[i].QuantizatioTableIndex;
// Sampling factors
// 4 bits
int samplingFactors = components[i].HorizontalSampleFactor | (components[i].VerticalSampleFactor << 4);
bufferSpan[1] = (byte)samplingFactors;
// Id
bufferSpan[0] = components[i].Id;
}
this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9);
this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9);
}
/// <summary>
@ -738,28 +674,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="componentCount">The number of components in a pixel.</param>
/// <param name="componentIds">The componentId's.</param>
private void WriteStartOfScan(int componentCount, ReadOnlySpan<byte> componentIds)
private void WriteStartOfScan(int componentCount, JpegComponentConfig[] components)
{
// This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
// and doesn't incur any allocation at all.
ReadOnlySpan<byte> huffmanId = new byte[]
{
0x00,
0x11,
0x11
};
// Use the same DC/AC tables for all channels for RGB.
if (this.colorType == JpegColorType.Rgb)
{
huffmanId = new byte[]
{
0x00,
0x00,
0x00
};
}
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c",
// - the number of components "\x03",
@ -777,11 +693,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[2] = 0x00;
this.buffer[3] = (byte)sosSize;
this.buffer[4] = (byte)componentCount; // Number of components in a scan
// Components data
for (int i = 0; i < componentCount; i++)
{
int i2 = 2 * i;
this.buffer[i2 + 5] = componentIds[i]; // Component Id
this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table
// Id
this.buffer[i2 + 5] = components[i].Id;
// Table selectors
int tableSelectors = (components[i].dcTableSelector << 4) | (components[i].acTableSelector);
this.buffer[i2 + 6] = (byte)tableSelectors;
}
this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.

Loading…
Cancel
Save