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
{
internal unsafe partial struct Block8x8
public unsafe partial struct Block8x8
{
[FieldOffset(0)]
public Vector128<short> V0;

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

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
// ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Explicit)]
internal unsafe partial struct Block8x8
public unsafe partial struct Block8x8
{
/// <summary>
/// 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>
/// Load raw 16bit integers from source.
/// </summary>

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

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
public partial struct Block8x8F
{
/// <summary>
/// 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
{
internal partial struct Block8x8F
public partial struct Block8x8F
{
/// <summary>
/// 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
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
public partial struct Block8x8F
{
/// <summary>
/// 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.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
internal partial struct Block8x8F : IEquatable<Block8x8F>
public partial struct Block8x8F : IEquatable<Block8x8F>
{
/// <summary>
/// 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>
/// The DC Huffman tables.
/// </summary>
private readonly HuffmanLut[] dcHuffmanTables;
private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4];
/// <summary>
/// The AC Huffman tables.
/// </summary>
private readonly HuffmanLut[] acHuffmanTables;
private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4];
/// <summary>
/// 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.target = outputStream;
this.dcHuffmanTables = new HuffmanLut[4];
this.acHuffmanTables = new HuffmanLut[4];
}
/// <summary>
@ -135,10 +132,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
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;
tables[table.DestinationIndex] = new HuffmanLut(table.TableSpec);
HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table);
}
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));
}
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;
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.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats;
@ -125,11 +126,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 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;
public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4];
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
/// </summary>
@ -95,10 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ImageMetadata metadata = image.Metadata;
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.
this.WriteStartOfImage();
@ -117,21 +115,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteApp14Marker();
}
// Write the quantization tables.
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, this.frameConfig);
// Write the Huffman tables.
this.WriteDefineHuffmanTables(this.scanConfig.HuffmanTables);
// Write the quantization tables.
this.InitQuantizationTables(this.scanConfig.QuantizationTables, jpegMetadata);
// Write the scan header.
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 quantTables = new Block8x8F[] { luminanceQuantTable, chrominanceQuantTable };
this.scanEncoder.EncodeInterleavedScan(frame, image, quantTables, Configuration.Default, cancellationToken);
this.scanEncoder.EncodeInterleavedScan(frame, image, this.QuantizationTables, Configuration.Default, cancellationToken);
// Write the End Of Image marker.
this.WriteEndOfImageMarker();
@ -281,26 +278,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Writes the Define Huffman Table marker and tables.
/// </summary>
/// <param name="componentCount">The number of components to write.</param>
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tables)
private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs)
{
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);
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.Write(table.TableSpec.Count);
this.outputStream.Write(table.TableSpec.Values);
this.outputStream.Write(tableConfig.Table.Count);
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="luminanceQuantTable">Output luminance 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 chromaQuality;
if (this.quality.HasValue)
{
lumaQuality = this.quality.Value;
chromaQuality = this.quality.Value;
}
else
{
lumaQuality = metadata.LuminanceQuality;
chromaQuality = metadata.ChrominanceQuality;
}
int dataLen = configs.Length * (1 + Block8x8F.Size);
// Luminance
lumaQuality = Numerics.Clamp(lumaQuality, 1, 100);
luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);
// Marker + quantization table lengths.
int markerlen = 2 + dataLen;
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
byte[] buffer = new byte[dataLen];
int offset = 0;
// Chrominance
chrominanceQuantTable = default;
if (componentCount > 1)
for (int i = 0; i < configs.Length; i++)
{
chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
JpegQuantizationTableConfig config = configs[i];
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