Browse Source

Merge pull request #2073 from SixLabors/bp/arithmeticcoding

Add support for decoding jpeg's with arithmetic coding
pull/2103/head
Brian Popow 4 years ago
committed by GitHub
parent
commit
3e7b2c9533
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 12
      src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs
  3. 30
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs
  4. 43
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs
  5. 1238
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
  6. 29
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs
  7. 57
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  8. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  9. 48
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs
  10. 49
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs
  11. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  12. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
  13. 9
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  14. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  15. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  16. 6
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  17. 196
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  18. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  19. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  20. 7
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  21. 1
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
  22. 16
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  23. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  24. 12
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs
  25. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  26. 34
      tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
  27. 7
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  28. 10
      tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
  29. 44
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs
  30. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  31. 2
      tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs
  32. 32
      tests/ImageSharp.Tests/TestFormat.cs
  33. 11
      tests/ImageSharp.Tests/TestImages.cs
  34. 3
      tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-grayscale.jpg
  35. 3
      tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-interleaved.jpg
  36. 3
      tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-restart.jpg
  37. 3
      tests/Images/Input/Jpg/baseline/Calliphora_arithmetic.jpg
  38. 3
      tests/Images/Input/Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg

4
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
@ -389,7 +389,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
var yMulWidth = y * width;
int yMulWidth = y * width;
int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
{

12
src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal enum ComponentType
{
Huffman = 0,
Arithmetic = 1
}
}

30
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal class ArithmeticDecodingComponent : JpegComponent
{
public ArithmeticDecodingComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index)
: base(memoryAllocator, frame, id, horizontalFactor, verticalFactor, quantizationTableIndex, index)
{
}
/// <summary>
/// Gets or sets the dc context.
/// </summary>
public int DcContext { get; set; }
/// <summary>
/// Gets or sets the dc statistics.
/// </summary>
public ArithmeticStatistics DcStatistics { get; set; }
/// <summary>
/// Gets or sets the ac statistics.
/// </summary>
public ArithmeticStatistics AcStatistics { get; set; }
}
}

43
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal class ArithmeticDecodingTable
{
public ArithmeticDecodingTable(byte tableClass, byte identifier)
{
this.TableClass = tableClass;
this.Identifier = identifier;
}
public byte TableClass { get; }
public byte Identifier { get; }
public byte ConditioningTableValue { get; private set; }
public int DcL { get; private set; }
public int DcU { get; private set; }
public int AcKx { get; private set; }
public void Configure(byte conditioningTableValue)
{
this.ConditioningTableValue = conditioningTableValue;
if (this.TableClass == 0)
{
this.DcL = conditioningTableValue & 0x0F;
this.DcU = conditioningTableValue >> 4;
this.AcKx = 0;
}
else
{
this.DcL = 0;
this.DcU = 0;
this.AcKx = conditioningTableValue;
}
}
}
}

1238
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs

File diff suppressed because it is too large

29
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal class ArithmeticStatistics
{
private readonly byte[] statistics;
public ArithmeticStatistics(bool dc, int identifier)
{
this.IsDcStatistics = dc;
this.Identifier = identifier;
this.statistics = dc ? new byte[64] : new byte[256];
}
public bool IsDcStatistics { get; private set; }
public int Identifier { get; private set; }
public ref byte GetReference() => ref this.statistics[0];
public ref byte GetReference(int offset) => ref this.statistics[offset];
public void Reset() => this.statistics.AsSpan().Clear();
}
}

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

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// Originally ported from <see href="https://github.com/t0rakka/mango"/>
/// with additional fixes for both performance and common encoding errors.
/// </summary>
internal class HuffmanScanDecoder
internal class HuffmanScanDecoder : IJpegScanDecoder
{
private readonly BufferedReadStream stream;
@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Shortcut for <see cref="frame"/>.Components.
/// </summary>
private JpegComponent[] components;
private IJpegComponent[] components;
/// <summary>
/// Number of component in the current scan.
@ -54,11 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private readonly HuffmanTable[] dcHuffmanTables;
/// <summary>
/// The AC Huffman tables
/// The AC Huffman tables.
/// </summary>
private readonly HuffmanTable[] acHuffmanTables;
private HuffmanScanBuffer scanBuffer;
private JpegBitReader scanBuffer;
private readonly SpectralConverter spectralConverter;
@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.scanComponentCount = scanComponentCount;
this.scanBuffer = new HuffmanScanBuffer(this.stream);
this.scanBuffer = new JpegBitReader(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
this.frame.AllocateComponents(fullScan);
@ -139,6 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
/// <inheritdoc/>
public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
this.frame = frame;
@ -170,7 +171,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine;
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
for (int j = 0; j < mcusPerColumn; j++)
{
@ -184,10 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int k = 0; k < this.scanComponentCount; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
var component = this.components[order] as JpegComponent;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -233,14 +234,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineDataNonInterleaved()
{
JpegComponent component = this.components[this.frame.ComponentOrder[0]];
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
ref JpegBitReader buffer = ref this.scanBuffer;
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int j = 0; j < h; j++)
{
@ -268,14 +269,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseBaselineDataSingleComponent()
{
JpegComponent component = this.frame.Components[0];
var component = this.frame.Components[0] as JpegComponent;
int mcuLines = this.frame.McusPerColumn;
int w = component.WidthInBlocks;
int h = component.SamplingFactors.Height;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
for (int i = 0; i < mcuLines; i++)
{
@ -382,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
int mcusPerLine = this.frame.McusPerLine;
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
for (int j = 0; j < mcusPerColumn; j++)
{
@ -394,8 +395,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int k = 0; k < this.scanComponentCount; k++)
{
int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
var component = this.components[order] as JpegComponent;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -435,15 +436,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void ParseProgressiveDataNonInterleaved()
{
JpegComponent component = this.components[this.frame.ComponentOrder[0]];
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
ref JpegBitReader buffer = ref this.scanBuffer;
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
if (this.SpectralStart == 0)
{
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
for (int j = 0; j < h; j++)
{
@ -470,7 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
else
{
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
for (int j = 0; j < h; j++)
{
@ -503,7 +504,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanTable acTable)
{
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
// DC
int t = buffer.DecodeHuffman(ref dcTable);
@ -545,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable)
{
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
if (this.SuccessiveHigh == 0)
{
@ -581,7 +582,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return;
}
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
int start = this.SpectralStart;
int end = this.SpectralEnd;
int low = this.SuccessiveLow;
@ -626,7 +627,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable)
{
// Refinement scan for these AC coefficients
ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref JpegBitReader buffer = ref this.scanBuffer;
int start = this.SpectralStart;
int end = this.SpectralEnd;

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to
/// ensure <see cref="HuffmanScanBuffer.DecodeHuffman"/> terminates.
/// ensure <see cref="JpegBitReader.DecodeHuffman"/> terminates.
/// </summary>
public fixed ulong MaxCode[18];

48
src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs

@ -10,6 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
internal interface IJpegComponent
{
/// <summary>
/// Gets the component id.
/// </summary>
byte Id { get; }
/// <summary>
/// Gets the component's position in the components array.
/// </summary>
@ -25,6 +30,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
Size SamplingFactors { get; }
/// <summary>
/// Gets the horizontal sampling factor.
/// </summary>
int HorizontalSamplingFactor { get; }
/// <summary>
/// Gets the vertical sampling factor.
/// </summary>
int VerticalSamplingFactor { get; }
/// <summary>
/// Gets the divisors needed to apply when calculating colors.
/// <see>
@ -44,5 +59,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// We need to apply IDCT and dequantization to transform them into color-space blocks.
/// </summary>
Buffer2D<Block8x8> SpectralBlocks { get; }
/// <summary>
/// Gets or sets DC coefficient predictor.
/// </summary>
int DcPredictor { get; set; }
/// <summary>
/// Gets or sets the index for the DC table.
/// </summary>
int DcTableId { get; set; }
/// <summary>
/// Gets or sets the index for the AC table.
/// </summary>
int AcTableId { get; set; }
/// <summary>
/// Initializes component for future buffers initialization.
/// </summary>
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
void Init(int maxSubFactorH, int maxSubFactorV);
/// <summary>
/// Allocates the spectral blocks.
/// </summary>
/// <param name="fullScan">if set to true, use the full height of a block, otherwise use the vertical sampling factor.</param>
void AllocateSpectral(bool fullScan);
/// <summary>
/// Releases resources.
/// </summary>
void Dispose();
}
}

49
src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Interface for a JPEG scan decoder.
/// </summary>
internal interface IJpegScanDecoder
{
/// <summary>
/// Sets the reset interval.
/// </summary>
int ResetInterval { set; }
/// <summary>
/// Gets or sets the spectral selection start.
/// </summary>
int SpectralStart { get; set; }
/// <summary>
/// Gets or sets the spectral selection end.
/// </summary>
int SpectralEnd { get; set; }
/// <summary>
/// Gets or sets the successive approximation high bit end.
/// </summary>
int SuccessiveHigh { get; set; }
/// <summary>
/// Gets or sets the successive approximation low bit end.
/// </summary>
int SuccessiveLow { get; set; }
/// <summary>
/// Decodes the entropy coded data.
/// </summary>
/// <param name="scanComponentCount">Component count in the current scan.</param>
void ParseEntropyCodedData(int scanComponentCount);
/// <summary>
/// Sets the JpegFrame and its components and injects the frame data into the spectral converter.
/// </summary>
/// <param name="frame">The frame.</param>
/// <param name="jpegData">The raw JPEG data.</param>
void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets the components.
/// </summary>
IJpegComponent[] Components { get; }
JpegComponent[] Components { get; }
/// <summary>
/// Gets the quantization tables, in natural order.

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Used to buffer and track the bits read from the Huffman entropy encoded data.
/// </summary>
internal struct HuffmanScanBuffer
internal struct JpegBitReader
{
private readonly BufferedReadStream stream;
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Whether there is no more good data to pull from the stream for the current mcu.
private bool badData;
public HuffmanScanBuffer(BufferedReadStream stream)
public JpegBitReader(BufferedReadStream stream)
{
this.stream = stream;
this.data = 0ul;

9
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Represents a single frame component.
/// </summary>
internal sealed class JpegComponent : IDisposable, IJpegComponent
internal class JpegComponent : IDisposable, IJpegComponent
{
private readonly MemoryAllocator memoryAllocator;
@ -78,12 +78,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets or sets the index for the DC Huffman table.
/// </summary>
public int DCHuffmanTableId { get; set; }
public int DcTableId { get; set; }
/// <summary>
/// Gets or sets the index for the AC Huffman table.
/// </summary>
public int ACHuffmanTableId { get; set; }
public int AcTableId { get; set; }
public JpegFrame Frame { get; }
@ -119,11 +119,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
/// <inheritdoc/>
public void AllocateSpectral(bool fullScan)
{
if (this.SpectralBlocks != null)
{
// this method will be called each scan marker so we need to allocate only once
// This method will be called each scan marker so we need to allocate only once.
return;
}

2
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// To be "more accurate", we need to emulate this by rounding!
workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue);
// Write to color buffer acording to sampling factors
// Write to color buffer according to sampling factors
int xColorBufferStart = xBlock * this.blockAreaSize.Width;
workspaceBlock.ScaledCopyTo(
ref colorBufferRow[xColorBufferStart],

14
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -6,14 +6,14 @@ using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represent a single jpeg frame
/// Represent a single jpeg frame.
/// </summary>
internal sealed class JpegFrame : IDisposable
{
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
this.Extended = sofMarker.Marker == JpegConstants.Markers.SOF1;
this.Progressive = sofMarker.Marker == JpegConstants.Markers.SOF2;
this.Extended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9;
this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10;
this.Precision = precision;
this.MaxColorChannelValue = MathF.Pow(2, precision) - 1;
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets the pixel size of the image.
/// </summary>
public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
public Size PixelSize => new(this.PixelWidth, this.PixelHeight);
/// <summary>
/// Gets the number of components within a frame.
@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Gets the mcu size of the image.
/// </summary>
public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
public Size McuSize => new(this.McusPerLine, this.McusPerColumn);
/// <summary>
/// Gets the color depth, in number of bits per pixel.
@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
IJpegComponent component = this.Components[i];
component.Init(maxSubFactorH, maxSubFactorV);
}
}
@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
IJpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);
}
}

6
src/ImageSharp/Formats/Jpeg/JpegConstants.cs

@ -306,17 +306,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public const int RegisterSize = 64;
/// <summary>
/// The number of bits to fetch when filling the <see cref="HuffmanScanBuffer"/> buffer.
/// The number of bits to fetch when filling the <see cref="JpegBitReader"/> buffer.
/// </summary>
public const int FetchBits = 48;
/// <summary>
/// The number of times to read the input stream when filling the <see cref="HuffmanScanBuffer"/> buffer.
/// The number of times to read the input stream when filling the <see cref="JpegBitReader"/> buffer.
/// </summary>
public const int FetchLoop = FetchBits / 8;
/// <summary>
/// The minimum number of bits allowed before by the <see cref="HuffmanScanBuffer"/> before fetching.
/// The minimum number of bits allowed before by the <see cref="JpegBitReader"/> before fetching.
/// </summary>
public const int MinBits = RegisterSize - FetchBits;

196
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -97,7 +98,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Scan decoder.
/// </summary>
private HuffmanScanDecoder scanDecoder;
private IJpegScanDecoder scanDecoder;
/// <summary>
/// The arithmetic decoding tables.
/// </summary>
private List<ArithmeticDecodingTable> arithmeticDecodingTables;
/// <summary>
/// The restart interval.
/// </summary>
private int? resetInterval;
/// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
@ -140,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegComponent[] Components => this.Frame.Components;
/// <inheritdoc/>
IJpegComponent[] IRawJpegData.Components => this.Components;
JpegComponent[] IRawJpegData.Components => this.Components;
/// <inheritdoc/>
public Block8x8F[] QuantizationTables { get; private set; }
@ -188,9 +199,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
this.ParseStream(stream, scanDecoder, cancellationToken);
this.ParseStream(stream, spectralConverter, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
@ -206,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ParseStream(stream, scanDecoder: null, cancellationToken);
this.ParseStream(stream, spectralConverter: null, cancellationToken);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
@ -222,13 +231,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips).
/// </summary>
/// <param name="tableBytes">The table bytes.</param>
/// <param name="huffmanScanDecoder">The scan decoder.</param>
public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder)
/// <param name="scanDecoder">The scan decoder.</param>
public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder)
{
this.Metadata = new ImageMetadata();
this.QuantizationTables = new Block8x8F[4];
this.scanDecoder = huffmanScanDecoder;
this.scanDecoder = scanDecoder;
if (tableBytes.Length < 4)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
@ -300,13 +308,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Parses the input stream for file markers.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="scanDecoder">Scan decoder used exclusively to decode SOS marker.</param>
/// <param name="spectralConverter">The spectral converter to use.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken)
internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralConverter, CancellationToken cancellationToken)
{
bool metadataOnly = scanDecoder == null;
bool metadataOnly = spectralConverter == null;
this.scanDecoder = scanDecoder;
this.scanDecoder ??= new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
this.Metadata = new ImageMetadata();
@ -335,9 +343,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Get the marker length.
int markerContentByteSize = this.ReadUint16(stream) - 2;
// Check whether stream actually has enought bytes to read
// Check whether stream actually has enough bytes to read
// markerContentByteSize is always positive so we cast
// to uint to avoid sign extension
// to uint to avoid sign extension.
if (stream.RemainingBytes < (uint)markerContentByteSize)
{
JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
@ -348,7 +356,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, metadataOnly);
this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly);
break;
case JpegConstants.Markers.SOF9:
case JpegConstants.Markers.SOF10:
case JpegConstants.Markers.SOF13:
case JpegConstants.Markers.SOF14:
this.scanDecoder = new ArithmeticScanDecoder(stream, spectralConverter, cancellationToken);
if (this.resetInterval.HasValue)
{
this.scanDecoder.ResetInterval = this.resetInterval.Value;
}
this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly);
break;
case JpegConstants.Markers.SOF5:
@ -364,13 +386,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported.");
break;
case JpegConstants.Markers.SOF9:
case JpegConstants.Markers.SOF10:
case JpegConstants.Markers.SOF11:
case JpegConstants.Markers.SOF13:
case JpegConstants.Markers.SOF14:
case JpegConstants.Markers.SOF15:
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported.");
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with lossless arithmetic coding is not supported.");
break;
case JpegConstants.Markers.SOS:
@ -454,7 +472,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
break;
case JpegConstants.Markers.DAC:
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported.");
if (metadataOnly)
{
stream.Skip(markerContentByteSize);
}
else
{
this.ProcessArithmeticTable(stream, markerContentByteSize);
}
break;
}
}
@ -889,6 +915,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
/// <summary>
/// Processes a DAC marker, decoding the arithmetic tables.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessArithmeticTable(BufferedReadStream stream, int remaining)
{
this.arithmeticDecodingTables ??= new List<ArithmeticDecodingTable>(4);
while (remaining > 0)
{
int tableClassAndIdentifier = stream.ReadByte();
remaining--;
byte tableClass = (byte)(tableClassAndIdentifier >> 4);
byte identifier = (byte)(tableClassAndIdentifier & 0xF);
byte conditioningTableValue = (byte)stream.ReadByte();
remaining--;
var arithmeticTable = new ArithmeticDecodingTable(tableClass, identifier);
arithmeticTable.Configure(conditioningTableValue);
bool tableEntryReplaced = false;
for (int i = 0; i < this.arithmeticDecodingTables.Count; i++)
{
ArithmeticDecodingTable item = this.arithmeticDecodingTables[i];
if (item.TableClass == arithmeticTable.TableClass && item.Identifier == arithmeticTable.Identifier)
{
this.arithmeticDecodingTables[i] = arithmeticTable;
tableEntryReplaced = true;
break;
}
}
if (!tableEntryReplaced)
{
this.arithmeticDecodingTables.Add(arithmeticTable);
}
}
}
/// <summary>
/// Reads the adobe image resource block name: a Pascal string (padded to make size even).
/// </summary>
@ -950,7 +1017,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the tables do not match the header
/// Thrown if the tables do not match the header.
/// </exception>
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining)
{
@ -1056,8 +1123,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
/// <param name="metadataOnly">Whether to parse metadata only</param>
private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, bool metadataOnly)
/// <param name="decodingComponentType">The jpeg decoding component type.</param>
/// <param name="metadataOnly">Whether to parse metadata only.</param>
private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly)
{
if (this.Frame != null)
{
@ -1069,17 +1137,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
}
// Read initial marker definitions
// Read initial marker definitions.
const int length = 6;
stream.Read(this.temp, 0, length);
int bytesRead = stream.Read(this.temp, 0, length);
if (bytesRead != length)
{
JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data.");
}
// 1 byte: Bits/sample precision
// 1 byte: Bits/sample precision.
byte precision = this.temp[0];
// Validate: only 8-bit and 12-bit precisions are supported
// Validate: only 8-bit and 12-bit precisions are supported.
if (Array.IndexOf(this.supportedPrecisions, precision) == -1)
{
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported.");
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported.");
}
// 2 byte: Height
@ -1088,18 +1160,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// 2 byte: Width
int frameWidth = (this.temp[3] << 8) | this.temp[4];
// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that).
if (frameHeight == 0 || frameWidth == 0)
{
JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight);
}
// 1 byte: Number of components
// 1 byte: Number of components.
byte componentCount = this.temp[5];
// Validate: componentCount more than 4 can lead to a buffer overflow during stream
// reading so we must limit it to 4
// We do not support jpeg images with more than 4 components anyway
// reading so we must limit it to 4.
// We do not support jpeg images with more than 4 components anyway.
if (componentCount > 4)
{
JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount);
@ -1168,9 +1240,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex);
}
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
IJpegComponent component = decodingComponentType is ComponentType.Huffman ?
new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) :
new ArithmeticDecodingComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
this.Frame.Components[i] = component;
this.Frame.Components[i] = (JpegComponent)component;
this.Frame.ComponentIds[i] = componentId;
index += componentBytes;
@ -1198,11 +1272,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int codeValuesMaxByteSize = 256;
const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize;
var huffmanScanDecoder = this.scanDecoder as HuffmanScanDecoder;
if (huffmanScanDecoder is null)
{
JpegThrowHelper.ThrowInvalidImageContentException("missing huffman table data");
}
int length = remaining;
using (IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(totalBufferSize))
{
Span<byte> bufferSpan = buffer.GetSpan();
Span<byte> huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);
Span<byte> huffmanLengthsSpan = bufferSpan.Slice(0, codeLengthsByteSize);
Span<byte> huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize);
Span<uint> tableWorkspace = MemoryMarshal.Cast<byte, uint>(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize));
@ -1224,12 +1304,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}.");
}
stream.Read(huffmanLegthsSpan, 1, 16);
stream.Read(huffmanLengthsSpan, 1, 16);
int codeLengthSum = 0;
for (int j = 1; j < 17; j++)
{
codeLengthSum += huffmanLegthsSpan[j];
codeLengthSum += huffmanLengthsSpan[j];
}
length -= 17;
@ -1243,10 +1323,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
i += 17 + codeLengthSum;
this.scanDecoder.BuildHuffmanTable(
huffmanScanDecoder!.BuildHuffmanTable(
tableType,
tableIndex,
huffmanLegthsSpan,
huffmanLengthsSpan,
huffmanValuesSpan.Slice(0, codeLengthSum),
tableWorkspace);
}
@ -1254,8 +1334,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
/// macroblocks
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers,
/// in macroblocks.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
@ -1266,7 +1346,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining);
}
this.scanDecoder.ResetInterval = this.ReadUint16(stream);
// Save the reset interval, because it can come before or after the SOF marker.
// If the reset interval comes after the SOF marker, the scanDecoder has not been created.
this.resetInterval = this.ReadUint16(stream);
if (this.scanDecoder != null)
{
this.scanDecoder.ResetInterval = this.resetInterval.Value;
}
}
/// <summary>
@ -1279,7 +1366,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
}
// 1 byte: Number of components in scan
// 1 byte: Number of components in scan.
int selectorsCount = stream.ReadByte();
// Validate: 0 < count <= totalComponents
@ -1289,7 +1376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}.");
}
// Validate: marker must contain exactly (4 + selectorsCount*2) bytes
// Validate: Marker must contain exactly (4 + selectorsCount*2) bytes
int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes)
{
@ -1316,7 +1403,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
// Validate: must be found among registered components
// Validate: Must be found among registered components.
if (componentIndex == -1)
{
// TODO: extract as separate method?
@ -1325,7 +1412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.Frame.ComponentOrder[i / 2] = (byte)componentIndex;
JpegComponent component = this.Frame.Components[componentIndex];
IJpegComponent component = this.Frame.Components[componentIndex];
// 1 byte: Huffman table selectors.
// 4 bits - dc
@ -1341,8 +1428,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}");
}
component.DCHuffmanTableId = dcTableIndex;
component.ACHuffmanTableId = acTableIndex;
component.DcTableId = dcTableIndex;
component.AcTableId = acTableIndex;
}
// 3 bytes: Progressive scan decoding data.
@ -1362,11 +1449,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4;
this.scanDecoder.SuccessiveLow = successiveApproximation & 15;
if (this.scanDecoder is ArithmeticScanDecoder arithmeticScanDecoder)
{
arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables);
}
this.scanDecoder.ParseEntropyCodedData(selectorsCount);
}
/// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <returns>The <see cref="ushort"/></returns>

4
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
using SpectralConverter<L8> spectralConverterGray = new GrayJpegSpectralConverter<L8>(this.configuration);
var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray);
jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None);
jpegDecoder.ParseStream(stream, spectralConverterGray, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder?
using var decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
new RgbJpegSpectralConverter<Rgb24>(this.configuration) : new SpectralConverter<Rgb24>(this.configuration);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
jpegDecoder.ParseStream(stream, spectralConverter, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder?
using var decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);

4
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs

@ -40,8 +40,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream);
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder { IgnoreMetadata = true });
var scanDecoder = new HuffmanScanDecoder(bufferedStream, new NoopSpectralConverter(), cancellationToken: default);
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);
var spectralConverter = new NoopSpectralConverter();
decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default);
}
// We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels

7
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -10,12 +10,10 @@ using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using PhotoSauce.MagicScaler;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
using SkiaSharp;
@ -228,8 +226,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
public async Task ImageSharpResizeAsync(string input)
{
using FileStream output = File.Open(this.OutputPath(input), FileMode.Create);
// Resize it to fit a 150x150 square
using var image = await ImageSharpImage.LoadAsync(input);
// Resize it to fit a 150x150 square.
using ImageSharpImage image = await ImageSharpImage.LoadAsync(input);
this.LogImageProcessed(image.Width, image.Height);
image.Mutate(i => i.Resize(new ResizeOptions

1
tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

@ -67,7 +67,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
lrs.leakFrequency = options.LeakFrequency;
lrs.gcFrequency = options.GcFrequency;
timer = Stopwatch.StartNew();
try
{

16
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs

@ -3,7 +3,7 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
// ReSharper disable InconsistentNaming
@ -49,6 +49,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// .Dispose();
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding01, PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding02, PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingGray, PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingInterleaved, PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingWithRestart, PixelTypes.Rgb24)]
public void DecodeJpeg_WithArithmeticCoding<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Tolerant(0.002f), ReferenceDecoder);
}
[Theory]
[WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)]
public void UnrecoverableImage_Throws_InvalidImageContentException<TPixel>(TestImageProvider<TPixel> provider)

4
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -72,10 +72,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.Fuzz.NullReferenceException823,
TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount,
// Arithmetic coding
TestImages.Jpeg.Baseline.ArithmeticCoding,
TestImages.Jpeg.Baseline.ArithmeticCodingProgressive,
// Lossless jpeg
TestImages.Jpeg.Baseline.Lossless
};

12
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs

@ -4,6 +4,7 @@
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
// ReSharper disable InconsistentNaming
@ -29,6 +30,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive01, PixelTypes.Rgb24)]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive02, PixelTypes.Rgb24)]
public void DecodeProgressiveJpeg_WithArithmeticCoding<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Tolerant(0.004f), ReferenceDecoder);
}
[Theory]
[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgb24)]
public void DecodeProgressiveJpeg_WithLimitedAllocatorBufferCapacity(TestImageProvider<Rgb24> provider)

4
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -13,7 +13,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
using Xunit.Abstractions;
@ -25,6 +25,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[ValidateDisposedMemoryAllocations]
public partial class JpegDecoderTests
{
private static MagickReferenceDecoder ReferenceDecoder => new();
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector;
private const float BaselineTolerance = 0.001F / 100;

34
tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs

@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize);
var uniform1 = new Size(1, 1);
JpegComponent c0 = decoder.Components[0];
IJpegComponent c0 = decoder.Components[0];
VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1);
}
}
@ -70,8 +70,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
sb.AppendLine(imageFile);
sb.AppendLine($"Size:{decoder.Frame.PixelSize} MCU:{decoder.Frame.McuSize}");
JpegComponent c0 = decoder.Components[0];
JpegComponent c1 = decoder.Components[1];
IJpegComponent c0 = decoder.Components[0];
IJpegComponent c1 = decoder.Components[1];
sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}");
sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}");
@ -80,17 +80,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine(sb.ToString());
}
public static readonly TheoryData<string, int, object, object> ComponentVerificationData = new TheoryData<string, int, object, object>
{
{ TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) },
public static readonly TheoryData<string, int, object, object> ComponentVerificationData = new()
{
{ TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) },
// TODO: Find Ycck or Cmyk images with different subsampling
{ TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) },
};
// TODO: Find Ycck or Cmyk images with different subsampling
{ TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) },
{ TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) },
};
[Theory]
[MemberData(nameof(ComponentVerificationData))]
@ -108,9 +108,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(componentCount, decoder.Frame.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
JpegComponent c0 = decoder.Components[0];
JpegComponent c1 = decoder.Components[1];
JpegComponent c2 = decoder.Components[2];
IJpegComponent c0 = decoder.Components[0];
IJpegComponent c1 = decoder.Components[1];
IJpegComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1);
@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (componentCount == 4)
{
JpegComponent c3 = decoder.Components[2];
IJpegComponent c3 = decoder.Components[2];
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1);
}
}

7
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default);
// This would parse entire image
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);
decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default);
VerifyJpeg.SaveSpectralImage(provider, debugConverter.SpectralData);
}
@ -85,10 +85,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// internal scan decoder which we substitute to assert spectral correctness
var debugConverter = new DebugSpectralConverter<TPixel>();
var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default);
// This would parse entire image
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);
decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default);
// Actual verification
this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData);
@ -195,7 +194,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var spectralComponents = new LibJpegTools.ComponentData[frame.ComponentCount];
for (int i = 0; i < spectralComponents.Length; i++)
{
JpegComponent component = frame.Components[i];
var component = frame.Components[i] as JpegComponent;
spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index);
}

10
tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -26,10 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.MultiScanBaselineCMYK
};
public SpectralToPixelConversionTests(ITestOutputHelper output)
{
this.Output = output;
}
public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
@ -47,14 +43,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var converter = new SpectralConverter<TPixel>(Configuration.Default);
using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default);
decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default);
decoder.ParseStream(bufferedStream, converter, cancellationToken: default);
// Test metadata
provider.Utility.TestGroupName = nameof(JpegDecoderTests);
provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName;
// Comparison
using (Image<TPixel> image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()))
using (var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()))
using (Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false))
{
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);

44
tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs

@ -26,11 +26,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
this.SpectralBlocks = Configuration.Default.MemoryAllocator.Allocate2D<Block8x8>(this.WidthInBlocks, this.HeightInBlocks);
}
public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks);
public byte Id { get; }
public Size Size => new(this.WidthInBlocks, this.HeightInBlocks);
public int Index { get; }
public Size SizeInBlocks => new Size(this.WidthInBlocks, this.HeightInBlocks);
public Size SizeInBlocks => new(this.WidthInBlocks, this.HeightInBlocks);
public Size SamplingFactors => throw new NotSupportedException();
@ -48,6 +50,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public short MaxVal { get; private set; } = short.MinValue;
public int HorizontalSamplingFactor => throw new NotImplementedException();
public int VerticalSamplingFactor => throw new NotImplementedException();
public int DcPredictor { get; set; }
public int DcTableId { get; set; }
public int AcTableId { get; set; }
internal void MakeBlock(Block8x8 block, int y, int x)
{
block.TransposeInplace();
@ -77,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
}
}
public void LoadSpectral(JpegComponent c)
public void LoadSpectral(IJpegComponent c)
{
Buffer2D<Block8x8> data = c.SpectralBlocks;
for (int y = 0; y < this.HeightInBlocks; y++)
@ -201,25 +213,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
return this.Equals((ComponentData)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal);
}
public override int GetHashCode() => HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal);
public ref Block8x8 GetBlockReference(int column, int row)
{
throw new NotImplementedException();
}
public ref Block8x8 GetBlockReference(int column, int row) => throw new NotImplementedException();
public static bool operator ==(ComponentData left, ComponentData right)
{
return object.Equals(left, right);
}
public void Init(int maxSubFactorH, int maxSubFactorV) => throw new NotImplementedException();
public static bool operator !=(ComponentData left, ComponentData right)
{
return !object.Equals(left, right);
}
public void AllocateSpectral(bool fullScan) => throw new NotImplementedException();
public void Dispose() => throw new NotImplementedException();
public static bool operator ==(ComponentData left, ComponentData right) => Equals(left, right);
public static bool operator !=(ComponentData left, ComponentData right) => !Equals(left, right);
}
}
}

2
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)

2
tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs

@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests
this.continueSemaphore.Release();
}
protected override Stream CreateStream() => this.TestFormat.CreateAsyncSamaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable);
protected override Stream CreateStream() => this.TestFormat.CreateAsyncSemaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable);
}
}
}

32
tests/ImageSharp.Tests/TestFormat.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests
// We should not change Configuration.Default in individual tests!
// Create new configuration instances with new Configuration(TestFormat.GlobalTestFormat) instead!
public static TestFormat GlobalTestFormat { get; } = new TestFormat();
public static TestFormat GlobalTestFormat { get; } = new();
public TestFormat()
{
@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests
this.Decoder = new TestDecoder(this);
}
public List<DecodeOperation> DecodeCalls { get; } = new List<DecodeOperation>();
public List<DecodeOperation> DecodeCalls { get; } = new();
public TestEncoder Encoder { get; }
@ -54,12 +54,12 @@ namespace SixLabors.ImageSharp.Tests
return ms;
}
public Stream CreateAsyncSamaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512)
public Stream CreateAsyncSemaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512)
{
var buffer = new byte[size];
this.header.CopyTo(buffer, 0);
var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore);
return seeakable ? (Stream)semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false);
return seeakable ? semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false);
}
public void VerifySpecificDecodeCall<TPixel>(byte[] marker, Configuration config)
@ -191,20 +191,14 @@ namespace SixLabors.ImageSharp.Tests
return null;
}
public TestHeader(TestFormat testFormat)
{
this.testFormat = testFormat;
}
public TestHeader(TestFormat testFormat) => this.testFormat = testFormat;
}
public class TestDecoder : IImageDecoder, IImageInfoDetector
{
private readonly TestFormat testFormat;
public TestDecoder(TestFormat testFormat)
{
this.testFormat = testFormat;
}
public TestDecoder(TestFormat testFormat) => this.testFormat = testFormat;
public IEnumerable<string> MimeTypes => new[] { this.testFormat.MimeType };
@ -216,7 +210,6 @@ namespace SixLabors.ImageSharp.Tests
where TPixel : unmanaged, IPixel<TPixel>
=> this.DecodeImpl<TPixel>(configuration, stream);
private Image<TPixel> DecodeImpl<TPixel>(Configuration config, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -246,10 +239,7 @@ namespace SixLabors.ImageSharp.Tests
{
private readonly TestFormat testFormat;
public TestEncoder(TestFormat testFormat)
{
this.testFormat = testFormat;
}
public TestEncoder(TestFormat testFormat) => this.testFormat = testFormat;
public IEnumerable<string> MimeTypes => new[] { this.testFormat.MimeType };
@ -262,16 +252,12 @@ namespace SixLabors.ImageSharp.Tests
}
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO record this happened so we can verify it.
return Task.CompletedTask;
}
where TPixel : unmanaged, IPixel<TPixel> => Task.CompletedTask; // TODO record this happened so we can verify it.
}
public struct TestPixelForAgnosticDecode : IPixel<TestPixelForAgnosticDecode>
{
public PixelOperations<TestPixelForAgnosticDecode> CreatePixelOperations() => new PixelOperations<TestPixelForAgnosticDecode>();
public PixelOperations<TestPixelForAgnosticDecode> CreatePixelOperations() => new();
public void FromScaledVector4(Vector4 vector)
{

11
tests/ImageSharp.Tests/TestImages.cs

@ -214,14 +214,21 @@ namespace SixLabors.ImageSharp.Tests
public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg";
public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg";
public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg";
public const string ArithmeticCoding = "Jpg/baseline/arithmetic_coding.jpg";
public const string ArithmeticCodingProgressive = "Jpg/progressive/arithmetic_progressive.jpg";
public const string Lossless = "Jpg/baseline/lossless.jpg";
public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg";
public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg";
public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg";
public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg";
// Jpeg's with arithmetic coding.
public const string ArithmeticCoding01 = "Jpg/baseline/Calliphora_arithmetic.jpg";
public const string ArithmeticCoding02 = "Jpg/baseline/arithmetic_coding.jpg";
public const string ArithmeticCodingProgressive01 = "Jpg/progressive/arithmetic_progressive.jpg";
public const string ArithmeticCodingProgressive02 = "Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg";
public const string ArithmeticCodingGray = "Jpg/baseline/Calliphora-arithmetic-grayscale.jpg";
public const string ArithmeticCodingInterleaved = "Jpg/baseline/Calliphora-arithmetic-interleaved.jpg";
public const string ArithmeticCodingWithRestart = "Jpg/baseline/Calliphora-arithmetic-restart.jpg";
public static readonly string[] All =
{
Cmyk, Ycck, Exif, Floorplan,

3
tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-grayscale.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c5800857969f25773606ecb63154a7fee60b831f13932dc845e50276bac11b16
size 177311

3
tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-interleaved.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:09bb5d75c3ca9d92d6e6489611f1d9b9815aaec70a16027f4ca17e371aa69e6e
size 234032

3
tests/Images/Input/Jpg/baseline/Calliphora-arithmetic-restart.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad547d0de50d1d623a49dc7794838c6d35372ea3fe4bda06365ff8b42daf65bf
size 234225

3
tests/Images/Input/Jpg/baseline/Calliphora_arithmetic.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:09bb5d75c3ca9d92d6e6489611f1d9b9815aaec70a16027f4ca17e371aa69e6e
size 234032

3
tests/Images/Input/Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd688e22840892d7fd511782f3ff7043df1a3131fb17b000c6a3ca1d0e069950
size 228527
Loading…
Cancel
Save