Skip to content

Commit f131d60

Browse files
committed
Add restart interval
1 parent 53d776d commit f131d60

File tree

3 files changed

+101
-4
lines changed

3 files changed

+101
-4
lines changed

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

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,18 @@ ref Unsafe.Add(ref blockRef, k),
241241
/// Encodes the DC coefficients for a given component's blocks in a scan.
242242
/// </summary>
243243
/// <param name="component">The component whose DC coefficients need to be encoded.</param>
244+
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
244245
/// <param name="cancellationToken">The token to request cancellation.</param>
245-
public void EncodeDcScan(Component component, CancellationToken cancellationToken)
246+
public void EncodeDcScan(Component component, int restartInterval, CancellationToken cancellationToken)
246247
{
247248
int h = component.HeightInBlocks;
248249
int w = component.WidthInBlocks;
249250

250251
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
251252

253+
int restarts = 0;
254+
int restartsToGo = restartInterval;
255+
252256
for (int i = 0; i < h; i++)
253257
{
254258
cancellationToken.ThrowIfCancellationRequested();
@@ -258,6 +262,13 @@ public void EncodeDcScan(Component component, CancellationToken cancellationToke
258262

259263
for (nuint k = 0; k < (uint)w; k++)
260264
{
265+
if (restartInterval > 0 && restartsToGo == 0)
266+
{
267+
this.FlushRemainingBytes();
268+
this.WriteRestart(restarts % 8);
269+
component.DcPredictor = 0;
270+
}
271+
261272
this.WriteDc(
262273
component,
263274
ref Unsafe.Add(ref blockRef, k),
@@ -267,6 +278,18 @@ ref Unsafe.Add(ref blockRef, k),
267278
{
268279
this.FlushToStream();
269280
}
281+
282+
if (restartInterval > 0)
283+
{
284+
if (restartsToGo == 0)
285+
{
286+
restartsToGo = restartInterval;
287+
restarts++;
288+
restarts &= 7;
289+
}
290+
291+
restartsToGo--;
292+
}
270293
}
271294
}
272295

@@ -279,12 +302,16 @@ ref Unsafe.Add(ref blockRef, k),
279302
/// <param name="component">The component whose AC coefficients need to be encoded.</param>
280303
/// <param name="start">The starting index of the AC coefficient range to encode.</param>
281304
/// <param name="end">The ending index of the AC coefficient range to encode.</param>
305+
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
282306
/// <param name="cancellationToken">The token to request cancellation.</param>
283-
public void EncodeAcScan(Component component, nint start, nint end, CancellationToken cancellationToken)
307+
public void EncodeAcScan(Component component, nint start, nint end, int restartInterval, CancellationToken cancellationToken)
284308
{
285309
int h = component.HeightInBlocks;
286310
int w = component.WidthInBlocks;
287311

312+
int restarts = 0;
313+
int restartsToGo = restartInterval;
314+
288315
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
289316

290317
for (int i = 0; i < h; i++)
@@ -296,6 +323,12 @@ public void EncodeAcScan(Component component, nint start, nint end, Cancellation
296323

297324
for (nuint k = 0; k < (uint)w; k++)
298325
{
326+
if (restartInterval > 0 && restartsToGo == 0)
327+
{
328+
this.FlushRemainingBytes();
329+
this.WriteRestart(restarts % 8);
330+
}
331+
299332
this.WriteAcBlock(
300333
ref Unsafe.Add(ref blockRef, k),
301334
start,
@@ -306,6 +339,18 @@ ref Unsafe.Add(ref blockRef, k),
306339
{
307340
this.FlushToStream();
308341
}
342+
343+
if (restartInterval > 0)
344+
{
345+
if (restartsToGo == 0)
346+
{
347+
restartsToGo = restartInterval;
348+
restarts++;
349+
restarts &= 7;
350+
}
351+
352+
restartsToGo--;
353+
}
309354
}
310355
}
311356

@@ -508,6 +553,9 @@ private void WriteBlock(
508553
this.WriteAcBlock(ref block, 1, 64, ref acTable);
509554
}
510555

556+
private void WriteRestart(int restart) =>
557+
this.target.Write([0xff, (byte)(JpegConstants.Markers.RST0 + restart)]);
558+
511559
/// <summary>
512560
/// Emits the most significant count of bits to the buffer.
513561
/// </summary>

src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public sealed class JpegEncoder : ImageEncoder
1818
/// </summary>
1919
private int progressiveScans = 4;
2020

21+
/// <summary>
22+
/// Backing field for <see cref="RestartInterval"/>
23+
/// </summary>
24+
private int restartInterval = 0;
25+
2126
/// <summary>
2227
/// Gets the quality, that will be used to encode the image. Quality
2328
/// index must be between 1 and 100 (compression from max to min).
@@ -66,6 +71,28 @@ public int ProgressiveScans
6671
}
6772
}
6873

74+
/// <summary>
75+
/// Gets numbers of MCUs between restart markers.
76+
/// Defaults to <value>0</value>.
77+
/// </summary>
78+
/// <remarks>
79+
/// Currently supported in progressive encoding only.
80+
/// </remarks>
81+
/// <exception cref="ArgumentException">Restart interval must be in [0..65535] range.</exception>
82+
public int RestartInterval
83+
{
84+
get => this.restartInterval;
85+
init
86+
{
87+
if (value is < 0 or > 65535)
88+
{
89+
throw new ArgumentException("Restart interval must be in [0..65535] range.");
90+
}
91+
92+
this.restartInterval = value;
93+
}
94+
}
95+
6996
/// <summary>
7097
/// Gets the component encoding mode.
7198
/// </summary>

src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
106106
// Write the quantization tables.
107107
this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.encoder.Quality, jpegMetadata, buffer);
108108

109+
// Write define restart interval
110+
this.WriteDri(this.encoder.RestartInterval, buffer);
111+
109112
// Write scans with actual pixel data
110113
using SpectralConverter<TPixel> spectralConverter = new(frame, image, this.QuantizationTables);
111114
this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, buffer, cancellationToken);
@@ -426,6 +429,25 @@ private void WriteXmpProfile(XmpProfile xmpProfile, Span<byte> buffer)
426429
}
427430
}
428431

432+
/// <summary>
433+
/// Writes the DRI marker
434+
/// </summary>
435+
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
436+
/// <param name="buffer">Temporary buffer.</param>
437+
private void WriteDri(int restartInterval, Span<byte> buffer)
438+
{
439+
if (restartInterval <= 0)
440+
{
441+
return;
442+
}
443+
444+
this.WriteMarkerHeader(JpegConstants.Markers.DRI, 4, buffer);
445+
446+
buffer[1] = (byte)(restartInterval & 0xff);
447+
buffer[0] = (byte)(restartInterval >> 8);
448+
this.outputStream.Write(buffer);
449+
}
450+
429451
/// <summary>
430452
/// Writes the App1 header.
431453
/// </summary>
@@ -742,7 +764,7 @@ private void WriteProgressiveScans<TPixel>(
742764
{
743765
this.WriteStartOfScan(components.Slice(i, 1), buffer, 0x00, 0x00);
744766

745-
encoder.EncodeDcScan(frame.Components[i], cancellationToken);
767+
encoder.EncodeDcScan(frame.Components[i], this.encoder.RestartInterval, cancellationToken);
746768
}
747769

748770
// Phase 2: AC scans
@@ -757,7 +779,7 @@ private void WriteProgressiveScans<TPixel>(
757779
{
758780
this.WriteStartOfScan(components.Slice(i, 1), buffer, (byte)start, (byte)(end - 1));
759781

760-
encoder.EncodeAcScan(frame.Components[i], start, end, cancellationToken);
782+
encoder.EncodeAcScan(frame.Components[i], start, end, this.encoder.RestartInterval, cancellationToken);
761783
}
762784
}
763785
}

0 commit comments

Comments
 (0)