Browse Source

Add restart interval

pull/2740/head
ardabada 2 years ago
parent
commit
f131d60a6b
  1. 52
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  2. 27
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  3. 26
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

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

@ -241,14 +241,18 @@ internal class HuffmanScanEncoder
/// Encodes the DC coefficients for a given component's blocks in a scan.
/// </summary>
/// <param name="component">The component whose DC coefficients need to be encoded.</param>
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeDcScan(Component component, CancellationToken cancellationToken)
public void EncodeDcScan(Component component, int restartInterval, CancellationToken cancellationToken)
{
int h = component.HeightInBlocks;
int w = component.WidthInBlocks;
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
int restarts = 0;
int restartsToGo = restartInterval;
for (int i = 0; i < h; i++)
{
cancellationToken.ThrowIfCancellationRequested();
@ -258,6 +262,13 @@ internal class HuffmanScanEncoder
for (nuint k = 0; k < (uint)w; k++)
{
if (restartInterval > 0 && restartsToGo == 0)
{
this.FlushRemainingBytes();
this.WriteRestart(restarts % 8);
component.DcPredictor = 0;
}
this.WriteDc(
component,
ref Unsafe.Add(ref blockRef, k),
@ -267,6 +278,18 @@ internal class HuffmanScanEncoder
{
this.FlushToStream();
}
if (restartInterval > 0)
{
if (restartsToGo == 0)
{
restartsToGo = restartInterval;
restarts++;
restarts &= 7;
}
restartsToGo--;
}
}
}
@ -279,12 +302,16 @@ internal class HuffmanScanEncoder
/// <param name="component">The component whose AC coefficients need to be encoded.</param>
/// <param name="start">The starting index of the AC coefficient range to encode.</param>
/// <param name="end">The ending index of the AC coefficient range to encode.</param>
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void EncodeAcScan(Component component, nint start, nint end, CancellationToken cancellationToken)
public void EncodeAcScan(Component component, nint start, nint end, int restartInterval, CancellationToken cancellationToken)
{
int h = component.HeightInBlocks;
int w = component.WidthInBlocks;
int restarts = 0;
int restartsToGo = restartInterval;
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int i = 0; i < h; i++)
@ -296,6 +323,12 @@ internal class HuffmanScanEncoder
for (nuint k = 0; k < (uint)w; k++)
{
if (restartInterval > 0 && restartsToGo == 0)
{
this.FlushRemainingBytes();
this.WriteRestart(restarts % 8);
}
this.WriteAcBlock(
ref Unsafe.Add(ref blockRef, k),
start,
@ -306,6 +339,18 @@ internal class HuffmanScanEncoder
{
this.FlushToStream();
}
if (restartInterval > 0)
{
if (restartsToGo == 0)
{
restartsToGo = restartInterval;
restarts++;
restarts &= 7;
}
restartsToGo--;
}
}
}
@ -508,6 +553,9 @@ internal class HuffmanScanEncoder
this.WriteAcBlock(ref block, 1, 64, ref acTable);
}
private void WriteRestart(int restart) =>
this.target.Write([0xff, (byte)(JpegConstants.Markers.RST0 + restart)]);
/// <summary>
/// Emits the most significant count of bits to the buffer.
/// </summary>

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

@ -18,6 +18,11 @@ public sealed class JpegEncoder : ImageEncoder
/// </summary>
private int progressiveScans = 4;
/// <summary>
/// Backing field for <see cref="RestartInterval"/>
/// </summary>
private int restartInterval = 0;
/// <summary>
/// Gets the quality, that will be used to encode the image. Quality
/// index must be between 1 and 100 (compression from max to min).
@ -66,6 +71,28 @@ public sealed class JpegEncoder : ImageEncoder
}
}
/// <summary>
/// Gets numbers of MCUs between restart markers.
/// Defaults to <value>0</value>.
/// </summary>
/// <remarks>
/// Currently supported in progressive encoding only.
/// </remarks>
/// <exception cref="ArgumentException">Restart interval must be in [0..65535] range.</exception>
public int RestartInterval
{
get => this.restartInterval;
init
{
if (value is < 0 or > 65535)
{
throw new ArgumentException("Restart interval must be in [0..65535] range.");
}
this.restartInterval = value;
}
}
/// <summary>
/// Gets the component encoding mode.
/// </summary>

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

@ -106,6 +106,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
// Write the quantization tables.
this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata, buffer);
// Write define restart interval
this.WriteDri(this.encoder.RestartInterval, buffer);
// Write scans with actual pixel data
using SpectralConverter<TPixel> spectralConverter = new(frame, image, this.QuantizationTables);
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken);
@ -426,6 +429,25 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
}
}
/// <summary>
/// Writes the DRI marker
/// </summary>
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
/// <param name="buffer">Temporary buffer.</param>
private void WriteDri(int restartInterval, Span<byte> buffer)
{
if (restartInterval <= 0)
{
return;
}
this.WriteMarkerHeader(JpegConstants.Markers.DRI, 4, buffer);
buffer[1] = (byte)(restartInterval & 0xff);
buffer[0] = (byte)(restartInterval >> 8);
this.outputStream.Write(buffer);
}
/// <summary>
/// Writes the App1 header.
/// </summary>
@ -742,7 +764,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
{
this.WriteStartOfScan(components.Slice(i, 1), buffer, 0x00, 0x00);
encoder.EncodeDcScan(frame.Components[i], cancellationToken);
encoder.EncodeDcScan(frame.Components[i], this.encoder.RestartInterval, cancellationToken);
}
// Phase 2: AC scans
@ -757,7 +779,7 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
{
this.WriteStartOfScan(components.Slice(i, 1), buffer, (byte)start, (byte)(end - 1));
encoder.EncodeAcScan(frame.Components[i], start, end, cancellationToken);
encoder.EncodeAcScan(frame.Components[i], start, end, this.encoder.RestartInterval, cancellationToken);
}
}
}

Loading…
Cancel
Save