Skip to content

Commit 63cae5c

Browse files
Merge branch 'main' into js/issue-2801
2 parents 44b124b + ed2c2a1 commit 63cae5c

File tree

6 files changed

+410
-19
lines changed

6 files changed

+410
-19
lines changed

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,9 @@ private void Write4BitPixelData<TPixel>(Configuration configuration, Stream stre
575575
{
576576
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration, new QuantizerOptions()
577577
{
578-
MaxColors = 16
578+
MaxColors = 16,
579+
Dither = this.quantizer.Options.Dither,
580+
DitherScale = this.quantizer.Options.DitherScale
579581
});
580582

581583
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
@@ -623,7 +625,9 @@ private void Write2BitPixelData<TPixel>(Configuration configuration, Stream stre
623625
{
624626
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration, new QuantizerOptions()
625627
{
626-
MaxColors = 4
628+
MaxColors = 4,
629+
Dither = this.quantizer.Options.Dither,
630+
DitherScale = this.quantizer.Options.DitherScale
627631
});
628632

629633
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
@@ -680,7 +684,9 @@ private void Write1BitPixelData<TPixel>(Configuration configuration, Stream stre
680684
{
681685
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration, new QuantizerOptions()
682686
{
683-
MaxColors = 2
687+
MaxColors = 2,
688+
Dither = this.quantizer.Options.Dither,
689+
DitherScale = this.quantizer.Options.DitherScale
684690
});
685691

686692
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);

src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ public void ParseEntropyCodedData(int scanComponentCount)
119119

120120
this.frame.AllocateComponents();
121121

122+
this.todo = this.restartInterval;
123+
122124
if (!this.frame.Progressive)
123125
{
124126
this.ParseBaselineData();

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

Lines changed: 191 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ internal class HuffmanScanEncoder
8787
/// </remarks>
8888
private readonly byte[] streamWriteBuffer;
8989

90+
private readonly int restartInterval;
91+
9092
/// <summary>
9193
/// Number of jagged bits stored in <see cref="accumulatedBits"/>
9294
/// </summary>
@@ -103,13 +105,16 @@ internal class HuffmanScanEncoder
103105
/// Initializes a new instance of the <see cref="HuffmanScanEncoder"/> class.
104106
/// </summary>
105107
/// <param name="blocksPerCodingUnit">Amount of encoded 8x8 blocks per single jpeg macroblock.</param>
108+
/// <param name="restartInterval">Numbers of MCUs between restart markers.</param>
106109
/// <param name="outputStream">Output stream for saving encoded data.</param>
107-
public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream)
110+
public HuffmanScanEncoder(int blocksPerCodingUnit, int restartInterval, Stream outputStream)
108111
{
109112
int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit;
110113
this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)];
111114
this.emitWriteIndex = this.emitBuffer.Length;
112115

116+
this.restartInterval = restartInterval;
117+
113118
this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier];
114119

115120
this.target = outputStream;
@@ -211,6 +216,9 @@ public void EncodeScanBaseline(Component component, CancellationToken cancellati
211216
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
212217
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
213218

219+
int restarts = 0;
220+
int restartsToGo = this.restartInterval;
221+
214222
for (int i = 0; i < h; i++)
215223
{
216224
cancellationToken.ThrowIfCancellationRequested();
@@ -221,6 +229,13 @@ public void EncodeScanBaseline(Component component, CancellationToken cancellati
221229

222230
for (nuint k = 0; k < (uint)w; k++)
223231
{
232+
if (this.restartInterval > 0 && restartsToGo == 0)
233+
{
234+
this.FlushRemainingBytes();
235+
this.WriteRestart(restarts % 8);
236+
component.DcPredictor = 0;
237+
}
238+
224239
this.WriteBlock(
225240
component,
226241
ref Unsafe.Add(ref blockRef, k),
@@ -231,6 +246,133 @@ ref Unsafe.Add(ref blockRef, k),
231246
{
232247
this.FlushToStream();
233248
}
249+
250+
if (this.restartInterval > 0)
251+
{
252+
if (restartsToGo == 0)
253+
{
254+
restartsToGo = this.restartInterval;
255+
restarts++;
256+
}
257+
258+
restartsToGo--;
259+
}
260+
}
261+
}
262+
263+
this.FlushRemainingBytes();
264+
}
265+
266+
/// <summary>
267+
/// Encodes the DC coefficients for a given component's blocks in a scan.
268+
/// </summary>
269+
/// <param name="component">The component whose DC coefficients need to be encoded.</param>
270+
/// <param name="cancellationToken">The token to request cancellation.</param>
271+
public void EncodeDcScan(Component component, CancellationToken cancellationToken)
272+
{
273+
int h = component.HeightInBlocks;
274+
int w = component.WidthInBlocks;
275+
276+
ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
277+
278+
int restarts = 0;
279+
int restartsToGo = this.restartInterval;
280+
281+
for (int i = 0; i < h; i++)
282+
{
283+
cancellationToken.ThrowIfCancellationRequested();
284+
285+
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i);
286+
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
287+
288+
for (nuint k = 0; k < (uint)w; k++)
289+
{
290+
if (this.restartInterval > 0 && restartsToGo == 0)
291+
{
292+
this.FlushRemainingBytes();
293+
this.WriteRestart(restarts % 8);
294+
component.DcPredictor = 0;
295+
}
296+
297+
this.WriteDc(
298+
component,
299+
ref Unsafe.Add(ref blockRef, k),
300+
ref dcHuffmanTable);
301+
302+
if (this.IsStreamFlushNeeded)
303+
{
304+
this.FlushToStream();
305+
}
306+
307+
if (this.restartInterval > 0)
308+
{
309+
if (restartsToGo == 0)
310+
{
311+
restartsToGo = this.restartInterval;
312+
restarts++;
313+
}
314+
315+
restartsToGo--;
316+
}
317+
}
318+
}
319+
320+
this.FlushRemainingBytes();
321+
}
322+
323+
/// <summary>
324+
/// Encodes the AC coefficients for a specified range of blocks in a component's scan.
325+
/// </summary>
326+
/// <param name="component">The component whose AC coefficients need to be encoded.</param>
327+
/// <param name="start">The starting index of the AC coefficient range to encode.</param>
328+
/// <param name="end">The ending index of the AC coefficient range to encode.</param>
329+
/// <param name="cancellationToken">The token to request cancellation.</param>
330+
public void EncodeAcScan(Component component, nint start, nint end, CancellationToken cancellationToken)
331+
{
332+
int h = component.HeightInBlocks;
333+
int w = component.WidthInBlocks;
334+
335+
int restarts = 0;
336+
int restartsToGo = this.restartInterval;
337+
338+
ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
339+
340+
for (int i = 0; i < h; i++)
341+
{
342+
cancellationToken.ThrowIfCancellationRequested();
343+
344+
Span<Block8x8> blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i);
345+
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
346+
347+
for (nuint k = 0; k < (uint)w; k++)
348+
{
349+
if (this.restartInterval > 0 && restartsToGo == 0)
350+
{
351+
this.FlushRemainingBytes();
352+
this.WriteRestart(restarts % 8);
353+
}
354+
355+
this.WriteAcBlock(
356+
ref Unsafe.Add(ref blockRef, k),
357+
start,
358+
end,
359+
ref acHuffmanTable);
360+
361+
if (this.IsStreamFlushNeeded)
362+
{
363+
this.FlushToStream();
364+
}
365+
366+
if (this.restartInterval > 0)
367+
{
368+
if (restartsToGo == 0)
369+
{
370+
restartsToGo = this.restartInterval;
371+
restarts++;
372+
}
373+
374+
restartsToGo--;
375+
}
234376
}
235377
}
236378

@@ -250,6 +392,9 @@ private void EncodeScanBaselineInterleaved<TPixel>(JpegFrame frame, SpectralConv
250392
int mcusPerColumn = frame.McusPerColumn;
251393
int mcusPerLine = frame.McusPerLine;
252394

395+
int restarts = 0;
396+
int restartsToGo = this.restartInterval;
397+
253398
for (int j = 0; j < mcusPerColumn; j++)
254399
{
255400
cancellationToken.ThrowIfCancellationRequested();
@@ -260,6 +405,16 @@ private void EncodeScanBaselineInterleaved<TPixel>(JpegFrame frame, SpectralConv
260405
// Encode spectral to binary
261406
for (int i = 0; i < mcusPerLine; i++)
262407
{
408+
if (this.restartInterval > 0 && restartsToGo == 0)
409+
{
410+
this.FlushRemainingBytes();
411+
this.WriteRestart(restarts % 8);
412+
foreach (var component in frame.Components)
413+
{
414+
component.DcPredictor = 0;
415+
}
416+
}
417+
263418
// Scan an interleaved mcu... process components in order
264419
int mcuCol = mcu % mcusPerLine;
265420
for (int k = 0; k < frame.Components.Length; k++)
@@ -300,6 +455,17 @@ ref Unsafe.Add(ref blockRef, blockCol),
300455
{
301456
this.FlushToStream();
302457
}
458+
459+
if (this.restartInterval > 0)
460+
{
461+
if (restartsToGo == 0)
462+
{
463+
restartsToGo = this.restartInterval;
464+
restarts++;
465+
}
466+
467+
restartsToGo--;
468+
}
303469
}
304470
}
305471

@@ -371,25 +537,29 @@ ref Unsafe.Add(ref c2BlockRef, i),
371537
this.FlushRemainingBytes();
372538
}
373539

374-
private void WriteBlock(
540+
private void WriteDc(
375541
Component component,
376542
ref Block8x8 block,
377-
ref HuffmanLut dcTable,
378-
ref HuffmanLut acTable)
543+
ref HuffmanLut dcTable)
379544
{
380545
// Emit the DC delta.
381546
int dc = block[0];
382547
this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor);
383548
component.DcPredictor = dc;
549+
}
384550

551+
private void WriteAcBlock(
552+
ref Block8x8 block,
553+
nint start,
554+
nint end,
555+
ref HuffmanLut acTable)
556+
{
385557
// Emit the AC components.
386558
int[] acHuffTable = acTable.Values;
387559

388-
nint lastValuableIndex = block.GetLastNonZeroIndex();
389-
390560
int runLength = 0;
391561
ref short blockRef = ref Unsafe.As<Block8x8, short>(ref block);
392-
for (nint zig = 1; zig <= lastValuableIndex; zig++)
562+
for (nint zig = start; zig < end; zig++)
393563
{
394564
const int zeroRun1 = 1 << 4;
395565
const int zeroRun16 = 16 << 4;
@@ -413,14 +583,25 @@ private void WriteBlock(
413583
}
414584

415585
// if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over
416-
// this can be done for any number of trailing zeros, even when all 63 ac values are zero
417-
// (Block8x8F.Size - 1) == 63 - last index of the mcu elements
418-
if (lastValuableIndex != Block8x8F.Size - 1)
586+
if (runLength > 0)
419587
{
420588
this.EmitHuff(acHuffTable, 0x00);
421589
}
422590
}
423591

592+
private void WriteBlock(
593+
Component component,
594+
ref Block8x8 block,
595+
ref HuffmanLut dcTable,
596+
ref HuffmanLut acTable)
597+
{
598+
this.WriteDc(component, ref block, ref dcTable);
599+
this.WriteAcBlock(ref block, 1, 64, ref acTable);
600+
}
601+
602+
private void WriteRestart(int restart) =>
603+
this.target.Write([0xff, (byte)(JpegConstants.Markers.RST0 + restart)], 0, 2);
604+
424605
/// <summary>
425606
/// Emits the most significant count of bits to the buffer.
426607
/// </summary>

0 commit comments

Comments
 (0)