Browse Source

Implemented quantization tables

pull/2120/head
Dmitry Pentin 4 years ago
parent
commit
ea81abc4f0
  1. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs
  2. 17
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs
  5. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  6. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  7. 13
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  8. 14
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  9. 12
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  10. 84
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs

@ -7,7 +7,7 @@ using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal unsafe partial struct Block8x8 public unsafe partial struct Block8x8
{ {
[FieldOffset(0)] [FieldOffset(0)]
public Vector128<short> V0; public Vector128<short> V0;

17
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
internal unsafe partial struct Block8x8 public unsafe partial struct Block8x8
{ {
/// <summary> /// <summary>
/// A number of scalar coefficients in a <see cref="Block8x8F"/> /// A number of scalar coefficients in a <see cref="Block8x8F"/>
@ -122,6 +122,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
} }
public static Block8x8 Load(ReadOnlySpan<byte> data)
{
Unsafe.SkipInit(out Block8x8 result);
result.LoadFrom(data);
return result;
}
public void LoadFrom(ReadOnlySpan<byte> source)
{
for (int i = 0; i < Size; i++)
{
this[i] = source[i];
}
}
/// <summary> /// <summary>
/// Load raw 16bit integers from source. /// Load raw 16bit integers from source.
/// </summary> /// </summary>

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
// <auto-generated /> // <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal partial struct Block8x8F public partial struct Block8x8F
{ {
/// <summary> /// <summary>
/// Level shift by +maximum/2, clip to [0, maximum] /// Level shift by +maximum/2, clip to [0, maximum]

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs

@ -11,7 +11,7 @@ using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal partial struct Block8x8F public partial struct Block8x8F
{ {
/// <summary> /// <summary>
/// A number of rows of 8 scalar coefficients each in <see cref="Block8x8F"/> /// A number of rows of 8 scalar coefficients each in <see cref="Block8x8F"/>

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs

@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Memory;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
internal partial struct Block8x8F public partial struct Block8x8F
{ {
/// <summary> /// <summary>
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors.

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// 8x8 matrix of <see cref="float"/> coefficients. /// 8x8 matrix of <see cref="float"/> coefficients.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
internal partial struct Block8x8F : IEquatable<Block8x8F> public partial struct Block8x8F : IEquatable<Block8x8F>
{ {
/// <summary> /// <summary>
/// A number of scalar coefficients in a <see cref="Block8x8F"/> /// A number of scalar coefficients in a <see cref="Block8x8F"/>

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

@ -62,12 +62,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// The DC Huffman tables. /// The DC Huffman tables.
/// </summary> /// </summary>
private readonly HuffmanLut[] dcHuffmanTables; private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4];
/// <summary> /// <summary>
/// The AC Huffman tables. /// The AC Huffman tables.
/// </summary> /// </summary>
private readonly HuffmanLut[] acHuffmanTables; private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4];
/// <summary> /// <summary>
/// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>. /// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>.
@ -119,9 +119,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier];
this.target = outputStream; this.target = outputStream;
this.dcHuffmanTables = new HuffmanLut[4];
this.acHuffmanTables = new HuffmanLut[4];
} }
/// <summary> /// <summary>
@ -135,10 +132,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2;
} }
public void BuildHuffmanTable(JpegHuffmanTableConfig table) public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig)
{ {
HuffmanLut[] tables = table.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[table.DestinationIndex] = new HuffmanLut(table.TableSpec); tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table);
} }
public void EncodeInterleavedScan<TPixel>(JpegFrame frame, Image<TPixel> image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken) public void EncodeInterleavedScan<TPixel>(JpegFrame frame, Image<TPixel> image, Block8x8F[] quantTables, Configuration configuration, CancellationToken cancellationToken)

14
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -176,7 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return quality < 50 ? (5000 / quality) : (200 - (quality * 2)); return quality < 50 ? (5000 / quality) : (200 - (quality * 2));
} }
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable) public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = ((unscaledTable[j] * scale) + 50) / 100;
table[j] = Numerics.Clamp(x, 1, 255);
}
return table;
}
public static Block8x8F ScaleQuantizationTable(int scale, Block8x8 unscaledTable)
{ {
Block8x8F table = default; Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++) for (int j = 0; j < Block8x8F.Size; j++)

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

@ -5,6 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -125,11 +126,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public int DestinationIndex { get; set; } public int DestinationIndex { get; set; }
public HuffmanSpec TableSpec { get; set; } public HuffmanSpec Table { get; set; }
}
public class JpegQuantizationTableConfig
{
public int DestinationIndex { get; set; }
public Block8x8 Table { get; set; }
} }
public class JpegScanConfig public class JpegScanConfig
{ {
public JpegHuffmanTableConfig[] HuffmanTables { get; set; } public JpegHuffmanTableConfig[] HuffmanTables { get; set; }
public JpegQuantizationTableConfig[] QuantizationTables { get; set; }
} }
} }

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

@ -50,6 +50,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private HuffmanScanEncoder scanEncoder; private HuffmanScanEncoder scanEncoder;
public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4];
/// <summary> /// <summary>
/// The output stream. All attempted writes after the first error become no-ops. /// The output stream. All attempted writes after the first error become no-ops.
/// </summary> /// </summary>
@ -95,10 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
// TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
// Initialize the quantization tables.
this.InitQuantizationTables(this.frameConfig.Components.Length, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteStartOfImage(); this.WriteStartOfImage();
@ -117,21 +115,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteApp14Marker(); this.WriteApp14Marker();
} }
// Write the quantization tables.
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions. // Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig); this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig);
// Write the Huffman tables. // Write the Huffman tables.
this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables); this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables);
// Write the quantization tables.
this.InitQuantizationTables(this.scanConfig.QuantizationTables, jpegMetadata);
// Write the scan header. // Write the scan header.
this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components); this.WriteStartOfScan(this.frameConfig.Components.Length, this.frameConfig.Components);
var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType)); var frame = new Components.Encoder.JpegFrame(this.frameConfig, Configuration.Default.MemoryAllocator, image, GetTargetColorSpace(this.frameConfig.ColorType));
var quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable }; this.scanEncoder.EncodeInterleavedScan(frame, image, this.QuantizationTables, Configuration.Default, cancellationToken);
this.scanEncoder.EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken);
// Write the End Of Image marker. // Write the End Of Image marker.
this.WriteEndOfImageMarker(); this.WriteEndOfImageMarker();
@ -281,26 +278,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Writes the Define Huffman Table marker and tables. /// Writes the Define Huffman Table marker and tables.
/// </summary> /// </summary>
/// <param name="componentCount">The number of components to write.</param> /// <param name="componentCount">The number of components to write.</param>
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tables) private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs)
{ {
int markerlen = 2; int markerlen = 2;
for (int i = 0; i < tables.Length; i++) for (int i = 0; i < tableConfigs.Length; i++)
{ {
markerlen += 1 + 16 + tables[i].TableSpec.Values.Length; markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length;
} }
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
for (int i = 0; i < tables.Length; i++) for (int i = 0; i < tableConfigs.Length; i++)
{ {
JpegHuffmanTableConfig table = tables[i]; JpegHuffmanTableConfig tableConfig = tableConfigs[i];
int header = (table.Class << 4) | table.DestinationIndex; int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex;
this.outputStream.WriteByte((byte)header); this.outputStream.WriteByte((byte)header);
this.outputStream.Write(table.TableSpec.Count); this.outputStream.Write(tableConfig.Table.Count);
this.outputStream.Write(table.TableSpec.Values); this.outputStream.Write(tableConfig.Table.Values);
this.scanEncoder.BuildHuffmanTable(table); this.scanEncoder.BuildHuffmanTable(tableConfig);
} }
} }
@ -749,37 +746,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="metadata">Jpeg metadata instance.</param> /// <param name="metadata">Jpeg metadata instance.</param>
/// <param name="luminanceQuantTable">Output luminance quantization table.</param> /// <param name="luminanceQuantTable">Output luminance quantization table.</param>
/// <param name="chrominanceQuantTable">Output chrominance quantization table.</param> /// <param name="chrominanceQuantTable">Output chrominance quantization table.</param>
private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable) private void InitQuantizationTables(JpegQuantizationTableConfig[] configs, JpegMetadata metadata)
{ {
int lumaQuality; int dataLen = configs.Length * (1 + Block8x8F.Size);
int chromaQuality;
if (this.quality.HasValue)
{
lumaQuality = this.quality.Value;
chromaQuality = this.quality.Value;
}
else
{
lumaQuality = metadata.LuminanceQuality;
chromaQuality = metadata.ChrominanceQuality;
}
// Luminance // Marker + quantization table lengths.
lumaQuality = Numerics.Clamp(lumaQuality, 1, 100); int markerlen = 2 + dataLen;
luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
byte[] buffer = new byte[dataLen];
int offset = 0;
// Chrominance for (int i = 0; i < configs.Length; i++)
chrominanceQuantTable = default;
if (componentCount > 1)
{ {
chromaQuality = Numerics.Clamp(chromaQuality, 1, 100); JpegQuantizationTableConfig config = configs[i];
chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
if (!this.colorType.HasValue) // write to the output stream
buffer[offset++] = (byte)config.DestinationIndex;
for (int j = 0; j < Block8x8F.Size; j++)
{ {
this.colorType = chromaQuality >= 91 ? JpegEncodingMode.YCbCrRatio444 : JpegEncodingMode.YCbCrRatio420; buffer[offset++] = (byte)(uint)config.Table[ZigZag.ZigZagOrder[j]];
} }
// apply scaling and save into buffer
int quality = GetQualityForTable(config.DestinationIndex, this.quality, metadata);
this.QuantizationTables[config.DestinationIndex] = Quantization.ScaleQuantizationTable(quality, config.Table);
} }
// write filled buffer to the stream
this.outputStream.Write(buffer);
static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch
{
0 => encoderQuality ?? metadata.LuminanceQuality,
1 => encoderQuality ?? metadata.ChrominanceQuality,
_ => encoderQuality ?? metadata.Quality,
};
} }
} }
} }

Loading…
Cancel
Save