Browse Source

delete golang jpeg decoder

af/merge-core
James Jackson-South 8 years ago
parent
commit
ed07db23ae
  1. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/DoubleBufferedStreamReader.cs
  2. 11
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs
  5. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs
  6. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs
  7. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs
  8. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  9. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs
  10. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs
  11. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  12. 12
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrameComponent.cs
  13. 62
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
  14. 155
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs
  15. 255
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  16. 96
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs
  17. 23
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/EOFException.cs
  18. 253
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs
  19. 29
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponentScan.cs
  20. 27
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangDecoderErrorCode.cs
  21. 260
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangHuffmanTree.cs
  22. 53
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.ComputationData.cs
  23. 51
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.DataPointers.cs
  24. 705
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.cs
  25. 392
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs
  26. 25
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegScanDecoder.md
  27. 15
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/MissingFF00Exception.cs
  28. 42
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoder.cs
  29. 824
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs
  30. 6
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  31. 51
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  32. 42
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs
  33. 20
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  34. 13
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegMultiple.cs
  35. 5
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  36. 9
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
  37. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  38. 19
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  39. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave.cs
  40. 3
      tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
  41. 45
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  42. 26
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
  43. 40
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs
  44. 35
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  45. 7
      tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs
  46. 15
      tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs
  47. 134
      tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
  48. 73
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  49. 17
      tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs
  50. 55
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs
  51. 30
      tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/DoubleBufferedStreamReader.cs

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
using SixLabors.Memory;
// TODO: This could be useful elsewhere.
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading

11
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FastACTables.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs

@ -5,7 +5,7 @@ using System;
using System.Runtime.CompilerServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// The collection of lookup tables used for fast AC entropy scan decoding.
@ -35,10 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
/// <summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="PdfJsFrameComponent.ACHuffmanTableId"/>
/// </summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="JpegFrameComponent.ACHuffmanTableId"/> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetAcTableReference(PdfJsFrameComponent component)
public ref short GetAcTableReference(JpegFrameComponent component)
{
return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
}
@ -48,11 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
/// <param name="index">The table index.</param>
/// <param name="acHuffmanTables">The collection of AC Huffman tables.</param>
public void BuildACTableLut(int index, PdfJsHuffmanTables acHuffmanTables)
public void BuildACTableLut(int index, HuffmanTables acHuffmanTables)
{
const int FastBits = ScanDecoder.FastBits;
Span<short> fastAC = this.tables.GetRowSpan(index);
ref PdfJsHuffmanTable huffman = ref acHuffmanTables[index];
ref HuffmanTable huffman = ref acHuffmanTables[index];
int i;
for (i = 0; i < (1 << FastBits); i++)

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer256.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs

@ -4,7 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedByteBuffer256

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedByteBuffer512.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs

@ -4,7 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedByteBuffer512

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt16Buffer257.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs

@ -4,7 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedInt16Buffer257

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedInt32Buffer18.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs

@ -4,7 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedInt32Buffer18

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/FixedUInt32Buffer18.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs

@ -4,7 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedUInt32Buffer18

8
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -6,13 +6,13 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represents a Huffman Table
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PdfJsHuffmanTable
internal unsafe struct HuffmanTable
{
/// <summary>
/// Gets the max code array
@ -40,12 +40,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
public FixedInt16Buffer257 Sizes;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct.
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="count">The code lengths</param>
/// <param name="values">The huffman values</param>
public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> count, ReadOnlySpan<byte> values)
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> count, ReadOnlySpan<byte> values)
{
const int Length = 257;
using (IBuffer<short> huffcode = memoryAllocator.Allocate<short>(Length))

13
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTables.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs

@ -1,24 +1,23 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Defines a 2 pairs of huffman tables
/// Defines a 2 pairs of huffman tables.
/// </summary>
internal sealed class PdfJsHuffmanTables
internal sealed class HuffmanTables
{
private readonly PdfJsHuffmanTable[] tables = new PdfJsHuffmanTable[4];
private readonly HuffmanTable[] tables = new HuffmanTable[4];
/// <summary>
/// Gets or sets the table at the given index.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The <see cref="PdfJsHuffmanTable"/></returns>
public ref PdfJsHuffmanTable this[int index]
/// <returns>The <see cref="HuffmanTable"/></returns>
public ref HuffmanTable this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this.tables[index];

14
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFileMarker.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs

@ -3,19 +3,19 @@
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represents a jpeg file marker
/// Represents a jpeg file marker.
/// </summary>
internal readonly struct PdfJsFileMarker
internal readonly struct JpegFileMarker
{
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsFileMarker"/> struct.
/// Initializes a new instance of the <see cref="JpegFileMarker"/> struct.
/// </summary>
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
public PdfJsFileMarker(byte marker, long position)
public JpegFileMarker(byte marker, long position)
{
this.Marker = marker;
this.Position = position;
@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsFileMarker"/> struct.
/// Initializes a new instance of the <see cref="JpegFileMarker"/> struct.
/// </summary>
/// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param>
/// <param name="invalid">Whether the current marker is invalid</param>
public PdfJsFileMarker(byte marker, long position, bool invalid)
public JpegFileMarker(byte marker, long position, bool invalid)
{
this.Marker = marker;
this.Position = position;

8
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrame.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -3,12 +3,12 @@
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represent a single jpeg frame
/// </summary>
internal sealed class PdfJsFrame : IDisposable
internal sealed class JpegFrame : IDisposable
{
/// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <summary>
/// Gets or sets the frame component collection
/// </summary>
public PdfJsFrameComponent[] Components { get; set; }
public JpegFrameComponent[] Components { get; set; }
/// <summary>
/// Gets or sets the maximum horizontal sampling factor
@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
for (int i = 0; i < this.ComponentCount; i++)
{
PdfJsFrameComponent component = this.Components[i];
JpegFrameComponent component = this.Components[i];
component.Init();
}
}

12
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrameComponent.cs

@ -5,21 +5,19 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represents a single frame component
/// </summary>
internal class PdfJsFrameComponent : IDisposable, IJpegComponent
internal class JpegFrameComponent : IDisposable, IJpegComponent
{
private readonly MemoryAllocator memoryAllocator;
public PdfJsFrameComponent(MemoryAllocator memoryAllocator, PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index)
public JpegFrameComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index)
{
this.memoryAllocator = memoryAllocator;
this.Frame = frame;
@ -89,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
public int ACHuffmanTableId { get; set; }
public PdfJsFrame Frame { get; }
public JpegFrame Frame { get; }
/// <inheritdoc/>
public void Dispose()
@ -125,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
else
{
PdfJsFrameComponent c0 = this.Frame.Components[0];
JpegFrameComponent c0 = this.Frame.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}

62
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/ScanDecoder.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs

@ -2,10 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Decodes the Huffman encoded spectral scan.
@ -23,13 +21,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// LUT Bias[n] = (-1 << n) + 1
private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 };
private readonly PdfJsFrame frame;
private readonly PdfJsHuffmanTables dcHuffmanTables;
private readonly PdfJsHuffmanTables acHuffmanTables;
private readonly JpegFrame frame;
private readonly HuffmanTables dcHuffmanTables;
private readonly HuffmanTables acHuffmanTables;
private readonly FastACTables fastACTables;
private readonly DoubleBufferedStreamReader stream;
private readonly PdfJsFrameComponent[] components;
private readonly JpegFrameComponent[] components;
private readonly ZigZag dctZigZag;
// The restart interval.
@ -97,9 +95,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="successiveLow">The successive approximation bit low end.</param>
public ScanDecoder(
DoubleBufferedStreamReader stream,
PdfJsFrame frame,
PdfJsHuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables,
JpegFrame frame,
HuffmanTables dcHuffmanTables,
HuffmanTables acHuffmanTables,
FastACTables fastACTables,
int componentIndex,
int componentsLength,
@ -177,10 +175,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++)
{
PdfJsFrameComponent component = this.components[k];
JpegFrameComponent component = this.components[k];
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -231,13 +229,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
private void ParseBaselineDataNonInterleaved()
{
PdfJsFrameComponent component = this.components[this.componentIndex];
JpegFrameComponent component = this.components[this.componentIndex];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int mcu = 0;
@ -296,8 +294,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++)
{
PdfJsFrameComponent component = this.components[k];
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
JpegFrameComponent component = this.components[k];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -345,13 +343,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
private void ParseProgressiveDataNonInterleaved()
{
PdfJsFrameComponent component = this.components[this.componentIndex];
JpegFrameComponent component = this.components[this.componentIndex];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int mcu = 0;
@ -396,11 +394,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
private void DecodeBlockBaseline(
PdfJsFrameComponent component,
JpegFrameComponent component,
int row,
int col,
ref PdfJsHuffmanTable dcTable,
ref PdfJsHuffmanTable acTable,
ref HuffmanTable dcTable,
ref HuffmanTable acTable,
ref short fastACRef)
{
this.CheckBits();
@ -475,10 +473,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
private void DecodeBlockProgressiveDC(
PdfJsFrameComponent component,
JpegFrameComponent component,
int row,
int col,
ref PdfJsHuffmanTable dcTable)
ref HuffmanTable dcTable)
{
if (this.spectralEnd != 0)
{
@ -511,10 +509,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
private void DecodeBlockProgressiveAC(
PdfJsFrameComponent component,
JpegFrameComponent component,
int row,
int col,
ref PdfJsHuffmanTable acTable,
ref HuffmanTable acTable,
ref short fastACRef)
{
if (this.spectralStart == 0)
@ -603,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
}
private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref PdfJsHuffmanTable acTable)
private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable)
{
int k;
@ -805,7 +803,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(InliningOptions.ShortMethod)]
private int DecodeHuffman(ref PdfJsHuffmanTable table)
private int DecodeHuffman(ref HuffmanTable table)
{
this.CheckBits();
@ -830,7 +828,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(InliningOptions.ColdPath)]
private int DecodeHuffmanSlow(ref PdfJsHuffmanTable table)
private int DecodeHuffmanSlow(ref HuffmanTable table)
{
// Naive test is to shift the code_buffer down so k bits are
// valid, then test against MaxCode. To speed this up, we've
@ -941,7 +939,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
for (int i = 0; i < this.components.Length; i++)
{
PdfJsFrameComponent c = this.components[i];
JpegFrameComponent c = this.components[i];
c.DcPredictor = 0;
}

155
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs

@ -1,155 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream.
/// The n least significant bits of a form the unread bits, to be read in MSB to
/// LSB order.
/// </summary>
internal struct Bits
{
/// <summary>
/// Gets or sets the accumulator.
/// </summary>
public int Accumulator;
/// <summary>
/// Gets or sets the mask.
/// <![CDATA[mask==1<<(unreadbits-1) when unreadbits>0, with mask==0 when unreadbits==0.]]>
/// </summary>
public int Mask;
/// <summary>
/// Gets or sets the number of unread bits in the accumulator.
/// </summary>
public int UnreadBits;
/// <summary>
/// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
/// least n. For best performance (avoiding function calls inside hot loops),
/// the caller is the one responsible for first checking that bits.UnreadBits &lt; n.
/// </summary>
/// <param name="n">The number of bits to ensure.</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnsureNBits(int n, ref InputProcessor inputProcessor)
{
GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(n, ref inputProcessor);
errorCode.EnsureNoError();
}
/// <summary>
/// Reads bytes from the byte buffer to ensure that bits.UnreadBits is at
/// least n. For best performance (avoiding function calls inside hot loops),
/// the caller is the one responsible for first checking that bits.UnreadBits &lt; n.
/// This method does not throw. Returns <see cref="GolangDecoderErrorCode"/> instead.
/// </summary>
/// <param name="n">The number of bits to ensure.</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>Error code</returns>
public GolangDecoderErrorCode EnsureNBitsUnsafe(int n, ref InputProcessor inputProcessor)
{
while (true)
{
GolangDecoderErrorCode errorCode = this.EnsureBitsStepImpl(ref inputProcessor);
if (errorCode != GolangDecoderErrorCode.NoError || this.UnreadBits >= n)
{
return errorCode;
}
}
}
/// <summary>
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==8
/// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode Ensure8BitsUnsafe(ref InputProcessor inputProcessor)
{
return this.EnsureBitsStepImpl(ref inputProcessor);
}
/// <summary>
/// Unrolled version of <see cref="EnsureNBitsUnsafe"/> for n==1
/// </summary>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode Ensure1BitUnsafe(ref InputProcessor inputProcessor)
{
return this.EnsureBitsStepImpl(ref inputProcessor);
}
/// <summary>
/// Receive extend
/// </summary>
/// <param name="t">Byte</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <returns>Read bits value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReceiveExtend(int t, ref InputProcessor inputProcessor)
{
GolangDecoderErrorCode errorCode = this.ReceiveExtendUnsafe(t, ref inputProcessor, out int x);
errorCode.EnsureNoError();
return x;
}
/// <summary>
/// Receive extend
/// </summary>
/// <param name="t">Byte</param>
/// <param name="inputProcessor">The <see cref="InputProcessor"/></param>
/// <param name="x">Read bits value</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, ref InputProcessor inputProcessor, out int x)
{
if (this.UnreadBits < t)
{
GolangDecoderErrorCode errorCode = this.EnsureNBitsUnsafe(t, ref inputProcessor);
if (errorCode != GolangDecoderErrorCode.NoError)
{
x = int.MaxValue;
return errorCode;
}
}
this.UnreadBits -= t;
this.Mask >>= t;
int s = 1 << t;
x = (this.Accumulator >> this.UnreadBits) & (s - 1);
if (x < (s >> 1))
{
x += ((-1) << t) + 1;
}
return GolangDecoderErrorCode.NoError;
}
private GolangDecoderErrorCode EnsureBitsStepImpl(ref InputProcessor inputProcessor)
{
GolangDecoderErrorCode errorCode = inputProcessor.Bytes.ReadByteStuffedByteUnsafe(inputProcessor.InputStream, out int c);
if (errorCode != GolangDecoderErrorCode.NoError)
{
return errorCode;
}
this.Accumulator = (this.Accumulator << 8) | c;
this.UnreadBits += 8;
if (this.Mask == 0)
{
this.Mask = 1 << 7;
}
else
{
this.Mask <<= 8;
}
return errorCode;
}
}
}

255
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs

@ -1,255 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Bytes is a byte buffer, similar to a stream, except that it
/// has to be able to unread more than 1 byte, due to byte stuffing.
/// Byte stuffing is specified in section F.1.2.3.
/// TODO: Optimize buffer management inside this class!
/// </summary>
internal struct Bytes : IDisposable
{
/// <summary>
/// Specifies the buffer size for <see cref="Buffer"/> and <see cref="BufferAsInt"/>
/// </summary>
public const int BufferSize = 4096;
/// <summary>
/// Gets or sets the buffer.
/// buffer[i:j] are the buffered bytes read from the underlying
/// stream that haven't yet been passed further on.
/// </summary>
public byte[] Buffer;
/// <summary>
/// Values of <see cref="Buffer"/> converted to <see cref="int"/>-s
/// </summary>
public int[] BufferAsInt;
/// <summary>
/// Start of bytes read
/// </summary>
public int I;
/// <summary>
/// End of bytes read
/// </summary>
public int J;
/// <summary>
/// Gets or sets the unreadable bytes. The number of bytes to back up i after
/// overshooting. It can be 0, 1 or 2.
/// </summary>
public int UnreadableBytes;
/// <summary>
/// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer.
/// </summary>
/// <returns>The bytes created</returns>
public static Bytes Create()
{
// DO NOT bother with buffers and array pooling here!
// It only makes things worse!
return new Bytes
{
Buffer = new byte[BufferSize],
BufferAsInt = new int[BufferSize]
};
}
/// <summary>
/// Disposes of the underlying buffer
/// </summary>
public void Dispose()
{
this.Buffer = null;
this.BufferAsInt = null;
}
/// <summary>
/// ReadByteStuffedByte is like ReadByte but is for byte-stuffed Huffman data.
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <param name="x">The result byte as <see cref="int"/></param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReadByteStuffedByteUnsafe(Stream inputStream, out int x)
{
// Take the fast path if bytes.buf contains at least two bytes.
if (this.I + 2 <= this.J)
{
x = this.BufferAsInt[this.I];
this.I++;
this.UnreadableBytes = 1;
if (x != JpegConstants.Markers.XFFInt)
{
return GolangDecoderErrorCode.NoError;
}
if (this.BufferAsInt[this.I] != 0x00)
{
return GolangDecoderErrorCode.MissingFF00;
}
this.I++;
this.UnreadableBytes = 2;
x = JpegConstants.Markers.XFF;
return GolangDecoderErrorCode.NoError;
}
this.UnreadableBytes = 0;
GolangDecoderErrorCode errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 1;
if (errorCode != GolangDecoderErrorCode.NoError)
{
return errorCode;
}
if (x != JpegConstants.Markers.XFF)
{
return GolangDecoderErrorCode.NoError;
}
errorCode = this.ReadByteAsIntUnsafe(inputStream, out x);
this.UnreadableBytes = 2;
if (errorCode != GolangDecoderErrorCode.NoError)
{
return errorCode;
}
if (x != 0x00)
{
return GolangDecoderErrorCode.MissingFF00;
}
x = JpegConstants.Markers.XFF;
return GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte(Stream inputStream)
{
GolangDecoderErrorCode errorCode = this.ReadByteUnsafe(inputStream, out byte result);
errorCode.EnsureNoError();
return result;
}
/// <summary>
/// Extracts the next byte, whether buffered or not buffered into the result out parameter. It does not care about byte stuffing.
/// This method does not throw on format error, it returns a <see cref="GolangDecoderErrorCode"/> instead.
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <param name="result">The result <see cref="byte"/> as out parameter</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReadByteUnsafe(Stream inputStream, out byte result)
{
GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError;
while (this.I == this.J)
{
errorCode = this.FillUnsafe(inputStream);
if (errorCode != GolangDecoderErrorCode.NoError)
{
result = 0;
return errorCode;
}
}
result = this.Buffer[this.I];
this.I++;
this.UnreadableBytes = 0;
return errorCode;
}
/// <summary>
/// Same as <see cref="ReadByteUnsafe"/> but the result is an <see cref="int"/>
/// </summary>
/// <param name="inputStream">The input stream</param>
/// <param name="result">The result <see cref="int"/></param>
/// <returns>A <see cref="GolangDecoderErrorCode"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GolangDecoderErrorCode ReadByteAsIntUnsafe(Stream inputStream, out int result)
{
GolangDecoderErrorCode errorCode = GolangDecoderErrorCode.NoError;
while (this.I == this.J)
{
errorCode = this.FillUnsafe(inputStream);
if (errorCode != GolangDecoderErrorCode.NoError)
{
result = 0;
return errorCode;
}
}
result = this.BufferAsInt[this.I];
this.I++;
this.UnreadableBytes = 0;
return errorCode;
}
/// <summary>
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
/// </summary>
/// <exception cref="EOFException">Thrown when reached end of stream unexpectedly.</exception>
/// <param name="inputStream">Input stream</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Fill(Stream inputStream)
{
GolangDecoderErrorCode errorCode = this.FillUnsafe(inputStream);
errorCode.EnsureNoError();
}
/// <summary>
/// Fills up the bytes buffer from the underlying stream.
/// It should only be called when there are no unread bytes in bytes.
/// This method does not throw <see cref="EOFException"/>, returns a <see cref="GolangDecoderErrorCode"/> instead!
/// </summary>
/// <param name="inputStream">Input stream</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode FillUnsafe(Stream inputStream)
{
if (this.I != this.J)
{
// Unrecoverable error in the input, throwing!
DecoderThrowHelper.ThrowImageFormatException.FillCalledWhenUnreadBytesExist();
}
// Move the last 2 bytes to the start of the buffer, in case we need
// to call UnreadByteStuffedByte.
if (this.J > 2)
{
this.Buffer[0] = this.Buffer[this.J - 2];
this.Buffer[1] = this.Buffer[this.J - 1];
this.I = 2;
this.J = 2;
}
// Fill in the rest of the buffer.
int n = inputStream.Read(this.Buffer, this.J, this.Buffer.Length - this.J);
if (n == 0)
{
return GolangDecoderErrorCode.UnexpectedEndOfStream;
}
this.J += n;
for (int i = 0; i < this.Buffer.Length; i++)
{
this.BufferAsInt[i] = this.Buffer[i];
}
return GolangDecoderErrorCode.NoError;
}
}
}

96
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs

@ -1,96 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Encapsulates exception thrower methods for the Jpeg Encoder
/// </summary>
internal static class DecoderThrowHelper
{
/// <summary>
/// Throws an exception that belongs to the given <see cref="GolangDecoderErrorCode"/>
/// </summary>
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowExceptionForErrorCode(this GolangDecoderErrorCode errorCode)
{
// REMARK: If this method throws for an image that is expected to be decodable,
// consider using the ***Unsafe variant of the parsing method that asks for ThrowExceptionForErrorCode()
// then verify the error code + implement fallback logic manually!
switch (errorCode)
{
case GolangDecoderErrorCode.NoError:
throw new ArgumentException("ThrowExceptionForErrorCode() called with NoError!", nameof(errorCode));
case GolangDecoderErrorCode.MissingFF00:
throw new MissingFF00Exception();
case GolangDecoderErrorCode.UnexpectedEndOfStream:
throw new EOFException();
default:
throw new ArgumentOutOfRangeException(nameof(errorCode), errorCode, null);
}
}
/// <summary>
/// Throws an exception if the given <see cref="GolangDecoderErrorCode"/> defines an error.
/// </summary>
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureNoError(this GolangDecoderErrorCode errorCode)
{
if (errorCode != GolangDecoderErrorCode.NoError)
{
ThrowExceptionForErrorCode(errorCode);
}
}
/// <summary>
/// Throws an exception if the given <see cref="GolangDecoderErrorCode"/> is <see cref="GolangDecoderErrorCode.UnexpectedEndOfStream"/>.
/// </summary>
/// <param name="errorCode">The <see cref="GolangDecoderErrorCode"/></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureNoEOF(this GolangDecoderErrorCode errorCode)
{
if (errorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{
errorCode.ThrowExceptionForErrorCode();
}
}
/// <summary>
/// Encapsulates methods throwing different flavours of <see cref="ImageFormatException"/>-s.
/// </summary>
public static class ThrowImageFormatException
{
/// <summary>
/// Throws "Fill called when unread bytes exist".
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void FillCalledWhenUnreadBytesExist()
{
throw new ImageFormatException("Fill called when unread bytes exist!");
}
/// <summary>
/// Throws "Bad Huffman code".
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void BadHuffmanCode()
{
throw new ImageFormatException("Bad Huffman code!");
}
/// <summary>
/// Throws "Uninitialized Huffman table".
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UninitializedHuffmanTable()
{
throw new ImageFormatException("Uninitialized Huffman table");
}
}
}
}

23
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/EOFException.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// The EOF (End of File exception).
/// Thrown when the decoder encounters an EOF marker without a proceeding EOI (End Of Image) marker
/// TODO: Rename to UnexpectedEndOfStreamException
/// </summary>
internal class EOFException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="EOFException"/> class.
/// </summary>
public EOFException()
: base("Reached end of stream before proceeding EOI marker!")
{
}
}
}

253
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs

@ -1,253 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <inheritdoc cref="IJpegComponent" />
/// <summary>
/// Represents a single color component
/// </summary>
internal class GolangComponent : IDisposable, IJpegComponent
{
public GolangComponent(byte identifier, int index)
{
this.Identifier = identifier;
this.Index = index;
}
/// <summary>
/// Gets the identifier
/// </summary>
public byte Identifier { get; }
/// <inheritdoc />
public int Index { get; }
public Size SizeInBlocks { get; private set; }
public Size SamplingFactors { get; private set; }
public Size SubSamplingDivisors { get; private set; }
public int HorizontalSamplingFactor => this.SamplingFactors.Width;
public int VerticalSamplingFactor => this.SamplingFactors.Height;
/// <inheritdoc />
public int QuantizationTableIndex { get; private set; }
/// <inheritdoc />
/// <summary>
/// Gets the <see cref="T:SixLabors.ImageSharp.Memory.Buffer`1" /> storing the "raw" frequency-domain decoded blocks.
/// We need to apply IDCT, dequantiazition and unzigging to transform them into color-space blocks.
/// This is done by <see cref="M:SixLabors.ImageSharp.Formats.Jpeg.GolangPort.OrigJpegDecoderCore.ProcessBlocksIntoJpegImageChannels" />.
/// When <see cref="P:SixLabors.ImageSharp.Formats.Jpeg.GolangPort.OrigJpegDecoderCore.IsProgressive" /> us true, we are touching these blocks multiple times - each time we process a Scan.
/// </summary>
public Buffer2D<Block8x8> SpectralBlocks { get; private set; }
/// <summary>
/// Initializes <see cref="SpectralBlocks"/>
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void InitializeDerivedData(MemoryAllocator memoryAllocator, GolangJpegDecoderCore decoder)
{
// For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
// Theoretically, 4-component JPEG images could mix and match hv values
// but in practice, those two combinations are the only ones in use,
// and it simplifies the applyBlack code below if we can assume that:
// - for CMYK, the C and K channels have full samples, and if the M
// and Y channels subsample, they subsample both horizontally and
// vertically.
// - for YCbCrK, the Y and K channels have full samples.
this.SizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(this.SamplingFactors);
if (this.Index == 0 || this.Index == 3)
{
this.SubSamplingDivisors = new Size(1, 1);
}
else
{
GolangComponent c0 = decoder.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
}
this.SpectralBlocks = memoryAllocator.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, AllocationOptions.Clean);
}
/// <summary>
/// Initializes all component data except <see cref="SpectralBlocks"/>.
/// </summary>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void InitializeCoreData(GolangJpegDecoderCore decoder)
{
// Section B.2.2 states that "the value of C_i shall be different from
// the values of C_1 through C_(i-1)".
int i = this.Index;
for (int j = 0; j < this.Index; j++)
{
if (this.Identifier == decoder.Components[j].Identifier)
{
throw new ImageFormatException("Repeated component identifier");
}
}
this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)];
if (this.QuantizationTableIndex > GolangJpegDecoderCore.MaxTq)
{
throw new ImageFormatException("Bad Tq value");
}
byte hv = decoder.Temp[7 + (3 * i)];
int h = hv >> 4;
int v = hv & 0x0f;
if (h < 1 || h > 4 || v < 1 || v > 4)
{
throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio");
}
if (h == 3 || v == 3)
{
throw new ImageFormatException("Lnsupported subsampling ratio");
}
switch (decoder.ComponentCount)
{
case 1:
// If a JPEG image has only one component, section A.2 says "this data
// is non-interleaved by definition" and section A.2.2 says "[in this
// case...] the order of data units within a scan shall be left-to-right
// and top-to-bottom... regardless of the values of H_1 and V_1". Section
// 4.8.2 also says "[for non-interleaved data], the MCU is defined to be
// one data unit". Similarly, section A.1.1 explains that it is the ratio
// of H_i to max_j(H_j) that matters, and similarly for V. For grayscale
// images, H_1 is the maximum H_j for all components j, so that ratio is
// always 1. The component's (h, v) is effectively always (1, 1): even if
// the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
// MCUs, not two 16x8 MCUs.
h = 1;
v = 1;
break;
case 3:
// For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
// 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
// (h, v) values for the Y component are either (1, 1), (1, 2),
// (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
// must be a multiple of the Cb and Cr component's values. We also
// assume that the two chroma components have the same subsampling
// ratio.
switch (i)
{
case 0:
{
// Y.
// We have already verified, above, that h and v are both
// either 1, 2 or 4, so invalid (h, v) combinations are those
// with v == 4.
if (v == 4)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
case 1:
{
// Cb.
Size s0 = decoder.Components[0].SamplingFactors;
if (s0.Width % h != 0 || s0.Height % v != 0)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
case 2:
{
// Cr.
Size s1 = decoder.Components[1].SamplingFactors;
if (s1.Width != h || s1.Height != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
}
break;
case 4:
// For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
// Theoretically, 4-component JPEG images could mix and match hv values
// but in practice, those two combinations are the only ones in use,
// and it simplifies the applyBlack code below if we can assume that:
// - for CMYK, the C and K channels have full samples, and if the M
// and Y channels subsample, they subsample both horizontally and
// vertically.
// - for YCbCrK, the Y and K channels have full samples.
switch (i)
{
case 0:
if (hv != 0x11 && hv != 0x22)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
case 1:
case 2:
if (hv != 0x11)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
case 3:
Size s0 = decoder.Components[0].SamplingFactors;
if (s0.Width != h || s0.Height != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
break;
}
this.SamplingFactors = new Size(h, v);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Block8x8 GetBlockReference(int column, int row)
{
return ref this.SpectralBlocks[column, row];
}
public void Dispose()
{
this.SpectralBlocks?.Dispose();
}
}
}

29
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponentScan.cs

@ -1,29 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Represents a component scan
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct GolangComponentScan
{
/// <summary>
/// Gets or sets the component index.
/// </summary>
public byte ComponentIndex;
/// <summary>
/// Gets or sets the DC table selector
/// </summary>
public byte DcTableSelector;
/// <summary>
/// Gets or sets the AC table selector
/// </summary>
public byte AcTableSelector;
}
}

27
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangDecoderErrorCode.cs

@ -1,27 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Represents "recoverable" decoder errors.
/// </summary>
internal enum GolangDecoderErrorCode
{
/// <summary>
/// NoError
/// </summary>
NoError,
/// <summary>
/// MissingFF00
/// </summary>
// ReSharper disable once InconsistentNaming
MissingFF00,
/// <summary>
/// End of stream reached unexpectedly
/// </summary>
UnexpectedEndOfStream
}
}

260
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangHuffmanTree.cs

@ -1,260 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Represents a Huffman tree
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct GolangHuffmanTree
{
/// <summary>
/// The index of the AC table row
/// </summary>
public const int AcTableIndex = 1;
/// <summary>
/// The index of the DC table row
/// </summary>
public const int DcTableIndex = 0;
/// <summary>
/// The maximum (inclusive) number of codes in a Huffman tree.
/// </summary>
public const int MaxNCodes = 256;
/// <summary>
/// The maximum (inclusive) number of bits in a Huffman code.
/// </summary>
public const int MaxCodeLength = 16;
/// <summary>
/// The maximum number of Huffman table classes
/// </summary>
public const int MaxTc = 1;
/// <summary>
/// The maximum number of Huffman table identifiers
/// </summary>
public const int MaxTh = 3;
/// <summary>
/// Row size of the Huffman table
/// </summary>
public const int ThRowSize = MaxTh + 1;
/// <summary>
/// Number of Hufman Trees in the Huffman table
/// </summary>
public const int NumberOfTrees = (MaxTc + 1) * (MaxTh + 1);
/// <summary>
/// The log-2 size of the Huffman decoder's look-up table.
/// </summary>
public const int LutSizeLog2 = 8;
/// <summary>
/// Gets or sets the number of codes in the tree.
/// </summary>
public int Length;
/// <summary>
/// Gets the look-up table for the next LutSize bits in the bit-stream.
/// The high 8 bits of the uint16 are the encoded value. The low 8 bits
/// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits.
/// </summary>
public FixedInt32Buffer256 Lut;
/// <summary>
/// Gets the the decoded values, sorted by their encoding.
/// </summary>
public FixedInt32Buffer256 Values;
/// <summary>
/// Gets the array of minimum codes.
/// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
/// </summary>
public FixedInt32Buffer16 MinCodes;
/// <summary>
/// Gets the array of maximum codes.
/// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
/// </summary>
public FixedInt32Buffer16 MaxCodes;
/// <summary>
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
/// </summary>
public FixedInt32Buffer16 Indices;
/// <summary>
/// Creates and initializes an array of <see cref="GolangHuffmanTree" /> instances of size <see cref="NumberOfTrees" />
/// </summary>
/// <returns>An array of <see cref="GolangHuffmanTree" /> instances representing the Huffman tables</returns>
public static GolangHuffmanTree[] CreateHuffmanTrees()
{
return new GolangHuffmanTree[NumberOfTrees];
}
/// <summary>
/// Internal part of the DHT processor, whatever does it mean
/// </summary>
/// <param name="inputProcessor">The decoder instance</param>
/// <param name="defineHuffmanTablesData">The temporary buffer that holds the data that has been read from the Jpeg stream</param>
/// <param name="remaining">Remaining bits</param>
public void ProcessDefineHuffmanTablesMarkerLoop(
ref InputProcessor inputProcessor,
byte[] defineHuffmanTablesData,
ref int remaining)
{
// Read nCodes and huffman.Valuess (and derive h.Length).
// nCodes[i] is the number of codes with code length i.
// h.Length is the total number of codes.
this.Length = 0;
int[] ncodes = new int[MaxCodeLength];
for (int i = 0; i < ncodes.Length; i++)
{
ncodes[i] = defineHuffmanTablesData[i + 1];
this.Length += ncodes[i];
}
if (this.Length == 0)
{
throw new ImageFormatException("Huffman table has zero length");
}
if (this.Length > MaxNCodes)
{
throw new ImageFormatException("Huffman table has excessive length");
}
remaining -= this.Length + 17;
if (remaining < 0)
{
throw new ImageFormatException("DHT has wrong length");
}
byte[] values = new byte[MaxNCodes];
inputProcessor.ReadFull(values, 0, this.Length);
fixed (int* valuesPtr = this.Values.Data)
fixed (int* lutPtr = this.Lut.Data)
{
for (int i = 0; i < values.Length; i++)
{
valuesPtr[i] = values[i];
}
// Derive the look-up table.
for (int i = 0; i < MaxNCodes; i++)
{
lutPtr[i] = 0;
}
int x = 0, code = 0;
for (int i = 0; i < LutSizeLog2; i++)
{
code <<= 1;
for (int j = 0; j < ncodes[i]; j++)
{
// The codeLength is 1+i, so shift code by 8-(1+i) to
// calculate the high bits for every 8-bit sequence
// whose codeLength's high bits matches code.
// The high 8 bits of lutValue are the encoded value.
// The low 8 bits are 1 plus the codeLength.
int base2 = code << (7 - i);
int lutValue = (valuesPtr[x] << 8) | (2 + i);
for (int k = 0; k < 1 << (7 - i); k++)
{
lutPtr[base2 | k] = lutValue;
}
code++;
x++;
}
}
}
fixed (int* minCodesPtr = this.MinCodes.Data)
fixed (int* maxCodesPtr = this.MaxCodes.Data)
fixed (int* indicesPtr = this.Indices.Data)
{
// Derive minCodes, maxCodes, and indices.
int c = 0, index = 0;
for (int i = 0; i < ncodes.Length; i++)
{
int nc = ncodes[i];
if (nc == 0)
{
minCodesPtr[i] = -1;
maxCodesPtr[i] = -1;
indicesPtr[i] = -1;
}
else
{
minCodesPtr[i] = c;
maxCodesPtr[i] = c + nc - 1;
indicesPtr[i] = index;
c += nc;
index += nc;
}
c <<= 1;
}
}
}
/// <summary>
/// Gets the value for the given code and index.
/// </summary>
/// <param name="code">The code</param>
/// <param name="codeLength">The code length</param>
/// <returns>The value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetValue(int code, int codeLength)
{
return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]];
}
[StructLayout(LayoutKind.Sequential)]
internal struct FixedInt32Buffer256
{
public fixed int Data[256];
public int this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref int self = ref Unsafe.As<FixedInt32Buffer256, int>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct FixedInt32Buffer16
{
public fixed int Data[16];
public int this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref int self = ref Unsafe.As<FixedInt32Buffer16, int>(ref this);
return Unsafe.Add(ref self, idx);
}
}
}
}
}

53
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.ComputationData.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <content>
/// Conains the definition of <see cref="ComputationData"/>
/// </content>
internal unsafe partial struct GolangJpegScanDecoder
{
/// <summary>
/// Holds the "large" data blocks needed for computations.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ComputationData
{
/// <summary>
/// The main input/working block
/// </summary>
public Block8x8 Block;
/// <summary>
/// The jpeg unzig data
/// </summary>
public ZigZag Unzig;
/// <summary>
/// The buffer storing the <see cref="GolangComponentScan"/>-s for each component
/// </summary>
public fixed byte ScanData[3 * GolangJpegDecoderCore.MaxComponents];
/// <summary>
/// The DC values for each component
/// </summary>
public fixed int Dc[GolangJpegDecoderCore.MaxComponents];
/// <summary>
/// Creates and initializes a new <see cref="ComputationData"/> instance
/// </summary>
/// <returns>The <see cref="ComputationData"/></returns>
public static ComputationData Create()
{
ComputationData data = default;
data.Unzig = ZigZag.CreateUnzigTable();
return data;
}
}
}
}

51
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.DataPointers.cs

@ -1,51 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <content>
/// Conains the definition of <see cref="DataPointers"/>
/// </content>
internal unsafe partial struct GolangJpegScanDecoder
{
/// <summary>
/// Contains pointers to the memory regions of <see cref="ComputationData"/> so they can be easily passed around to pointer based utility methods of <see cref="Block8x8F"/>
/// </summary>
public struct DataPointers
{
/// <summary>
/// Pointer to <see cref="ComputationData.Block"/>
/// </summary>
public Block8x8* Block;
/// <summary>
/// Pointer to <see cref="ComputationData.Unzig"/> as byte*
/// </summary>
public byte* Unzig;
/// <summary>
/// Pointer to <see cref="ComputationData.ScanData"/> as Scan*
/// </summary>
public GolangComponentScan* ComponentScan;
/// <summary>
/// Pointer to <see cref="ComputationData.Dc"/>
/// </summary>
public int* Dc;
/// <summary>
/// Initializes a new instance of the <see cref="DataPointers" /> struct.
/// </summary>
/// <param name="basePtr">The pointer pointing to <see cref="ComputationData"/></param>
public DataPointers(ComputationData* basePtr)
{
this.Block = &basePtr->Block;
this.Unzig = basePtr->Unzig.Data;
this.ComponentScan = (GolangComponentScan*)basePtr->ScanData;
this.Dc = basePtr->Dc;
}
}
}
}

705
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.cs

@ -1,705 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Encapsulates the impementation of Jpeg SOS Huffman decoding. See JpegScanDecoder.md!
///
/// <see cref="zigStart"/> and <see cref="zigEnd"/> are the spectral selection bounds.
/// <see cref="ah"/> and <see cref="al"/> are the successive approximation high and low values.
/// The spec calls these values Ss, Se, Ah and Al.
/// For progressive JPEGs, these are the two more-or-less independent
/// aspects of progression. Spectral selection progression is when not
/// all of a block's 64 DCT coefficients are transmitted in one pass.
/// For example, three passes could transmit coefficient 0 (the DC
/// component), coefficients 1-5, and coefficients 6-63, in zig-zag
/// order. Successive approximation is when not all of the bits of a
/// band of coefficients are transmitted in one pass. For example,
/// three passes could transmit the 6 most significant bits, followed
/// by the second-least significant bit, followed by the least
/// significant bit.
/// For baseline JPEGs, these parameters are hard-coded to 0/63/0/0.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GolangJpegScanDecoder
{
// The JpegScanDecoder members should be ordered in a way that results in optimal memory layout.
#pragma warning disable SA1202 // ElementsMustBeOrderedByAccess
/// <summary>
/// The <see cref="ComputationData"/> buffer
/// </summary>
private ComputationData data;
/// <summary>
/// Pointers to elements of <see cref="data"/>
/// </summary>
private DataPointers pointers;
/// <summary>
/// The current component index
/// </summary>
public int ComponentIndex;
/// <summary>
/// X coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
/// </summary>
private int bx;
/// <summary>
/// Y coordinate of the current block, in units of 8x8. (The third block in the first row has (bx, by) = (2, 0))
/// </summary>
private int by;
/// <summary>
/// Start index of the zig-zag selection bound
/// </summary>
private int zigStart;
/// <summary>
/// End index of the zig-zag selection bound
/// </summary>
private int zigEnd;
/// <summary>
/// Successive approximation high value
/// </summary>
private int ah;
/// <summary>
/// Successive approximation low value
/// </summary>
private int al;
/// <summary>
/// The number of component scans
/// </summary>
private int componentScanCount;
/// <summary>
/// Horizontal sampling factor at the current component index
/// </summary>
private int hi;
/// <summary>
/// End-of-Band run, specified in section G.1.2.2.
/// </summary>
private int eobRun;
/// <summary>
/// The block counter
/// </summary>
private int blockCounter;
/// <summary>
/// The MCU counter
/// </summary>
private int mcuCounter;
/// <summary>
/// The expected RST marker value
/// </summary>
private byte expectedRst;
/// <summary>
/// Initializes a default-constructed <see cref="GolangJpegScanDecoder"/> instance for reading data from <see cref="GolangJpegDecoderCore"/>-s stream.
/// </summary>
/// <param name="p">Pointer to <see cref="GolangJpegScanDecoder"/> on the stack</param>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
public static void InitStreamReading(GolangJpegScanDecoder* p, GolangJpegDecoderCore decoder, int remaining)
{
p->data = ComputationData.Create();
p->pointers = new DataPointers(&p->data);
p->InitStreamReadingImpl(decoder, remaining);
}
/// <summary>
/// Read Huffman data from Jpeg scans in <see cref="GolangJpegDecoderCore.InputStream"/>,
/// and decode it as <see cref="Block8x8"/> into <see cref="GolangComponent.SpectralBlocks"/>.
///
/// The blocks are traversed one MCU at a time. For 4:2:0 chroma
/// subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
/// For a baseline 32x16 pixel image, the Y blocks visiting order is:
/// 0 1 4 5
/// 2 3 6 7
/// For progressive images, the interleaved scans (those with component count &gt; 1)
/// are traversed as above, but non-interleaved scans are traversed left
/// to right, top to bottom:
/// 0 1 2 3
/// 4 5 6 7
/// Only DC scans (zigStart == 0) can be interleave AC scans must have
/// only one component.
/// To further complicate matters, for non-interleaved scans, there is no
/// data for any blocks that are inside the image at the MCU level but
/// outside the image at the pixel level. For example, a 24x16 pixel 4:2:0
/// progressive image consists of two 16x16 MCUs. The interleaved scans
/// will process 8 Y blocks:
/// 0 1 4 5
/// 2 3 6 7
/// The non-interleaved scans will process only 6 Y blocks:
/// 0 1 2
/// 3 4 5
/// </summary>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void DecodeBlocks(GolangJpegDecoderCore decoder)
{
decoder.InputProcessor.ResetErrorState();
this.blockCounter = 0;
this.mcuCounter = 0;
this.expectedRst = JpegConstants.Markers.RST0;
for (int my = 0; my < decoder.MCUCountY; my++)
{
for (int mx = 0; mx < decoder.MCUCountX; mx++)
{
this.DecodeBlocksAtMcuIndex(decoder, mx, my);
this.mcuCounter++;
// Handling restart intervals
// Useful info: https://stackoverflow.com/a/8751802
if (decoder.IsAtRestartInterval(this.mcuCounter))
{
this.ProcessRSTMarker(decoder);
this.Reset(decoder);
}
}
}
}
private void DecodeBlocksAtMcuIndex(GolangJpegDecoderCore decoder, int mx, int my)
{
for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++)
{
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
GolangComponent component = decoder.Components[this.ComponentIndex];
this.hi = component.HorizontalSamplingFactor;
int vi = component.VerticalSamplingFactor;
for (int j = 0; j < this.hi * vi; j++)
{
if (this.componentScanCount != 1)
{
this.bx = (this.hi * mx) + (j % this.hi);
this.by = (vi * my) + (j / this.hi);
}
else
{
int q = decoder.MCUCountX * this.hi;
this.bx = this.blockCounter % q;
this.by = this.blockCounter / q;
this.blockCounter++;
if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight)
{
continue;
}
}
// Find the block at (bx,by) in the component's buffer:
ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by);
// Copy block to stack
this.data.Block = blockRefOnHeap;
if (!decoder.InputProcessor.ReachedEOF)
{
this.DecodeBlock(decoder, scanIndex);
}
// Store the result block:
blockRefOnHeap = this.data.Block;
}
}
}
private void ProcessRSTMarker(GolangJpegDecoderCore decoder)
{
// Attempt to look for RST[0-7] markers to resynchronize from corrupt input.
if (!decoder.InputProcessor.ReachedEOF)
{
decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2);
if (decoder.InputProcessor.CheckEOFEnsureNoError())
{
if (decoder.Temp[0] != 0xFF || decoder.Temp[1] != this.expectedRst)
{
bool invalidRst = true;
// Most jpeg's containing well-formed input will have a RST[0-7] marker following immediately
// but some, see Issue #481, contain padding bytes "0xFF" before the RST[0-7] marker.
// If we identify that case we attempt to read until we have bypassed the padded bytes.
// We then check again for our RST marker and throw if invalid.
// No other methods are attempted to resynchronize from corrupt input.
if (decoder.Temp[0] == 0xFF && decoder.Temp[1] == 0xFF)
{
while (decoder.Temp[0] == 0xFF && decoder.InputProcessor.CheckEOFEnsureNoError())
{
decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 1);
if (!decoder.InputProcessor.CheckEOFEnsureNoError())
{
break;
}
}
// Have we found a valid restart marker?
invalidRst = decoder.Temp[0] != this.expectedRst;
}
if (invalidRst)
{
throw new ImageFormatException("Bad RST marker");
}
}
this.expectedRst++;
if (this.expectedRst == JpegConstants.Markers.RST7 + 1)
{
this.expectedRst = JpegConstants.Markers.RST0;
}
}
}
}
private void Reset(GolangJpegDecoderCore decoder)
{
decoder.InputProcessor.ResetHuffmanDecoder();
this.ResetDcValues();
// Reset the progressive decoder state, as per section G.1.2.2.
this.eobRun = 0;
}
/// <summary>
/// Reset the DC components, as per section F.2.1.3.1.
/// </summary>
private void ResetDcValues()
{
Unsafe.InitBlock(this.pointers.Dc, default, sizeof(int) * GolangJpegDecoderCore.MaxComponents);
}
/// <summary>
/// The implementation part of <see cref="InitStreamReading"/> as an instance method.
/// </summary>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/></param>
/// <param name="remaining">The remaining bytes</param>
private void InitStreamReadingImpl(GolangJpegDecoderCore decoder, int remaining)
{
if (decoder.ComponentCount == 0)
{
throw new ImageFormatException("Missing SOF marker");
}
if (remaining < 6 || 4 + (2 * decoder.ComponentCount) < remaining || remaining % 2 != 0)
{
throw new ImageFormatException("SOS has wrong length");
}
decoder.InputProcessor.ReadFull(decoder.Temp, 0, remaining);
this.componentScanCount = decoder.Temp[0];
int scanComponentCountX2 = 2 * this.componentScanCount;
if (remaining != 4 + scanComponentCountX2)
{
throw new ImageFormatException("SOS length inconsistent with number of components");
}
int totalHv = 0;
for (int i = 0; i < this.componentScanCount; i++)
{
this.InitComponentScan(decoder, i, ref this.pointers.ComponentScan[i], ref totalHv);
}
// Section B.2.3 states that if there is more than one component then the
// total H*V values in a scan must be <= 10.
if (decoder.ComponentCount > 1 && totalHv > 10)
{
throw new ImageFormatException("Total sampling factors too large.");
}
this.zigEnd = Block8x8F.Size - 1;
if (decoder.IsProgressive)
{
this.zigStart = decoder.Temp[1 + scanComponentCountX2];
this.zigEnd = decoder.Temp[2 + scanComponentCountX2];
this.ah = decoder.Temp[3 + scanComponentCountX2] >> 4;
this.al = decoder.Temp[3 + scanComponentCountX2] & 0x0f;
if ((this.zigStart == 0 && this.zigEnd != 0) || this.zigStart > this.zigEnd
|| this.zigEnd >= Block8x8F.Size)
{
throw new ImageFormatException("Bad spectral selection bounds");
}
if (this.zigStart != 0 && this.componentScanCount != 1)
{
throw new ImageFormatException("Progressive AC coefficients for more than one component");
}
if (this.ah != 0 && this.ah != this.al + 1)
{
throw new ImageFormatException("Bad successive approximation values");
}
}
}
/// <summary>
/// Read the current the current block at (<see cref="bx"/>, <see cref="by"/>) from the decoders stream
/// </summary>
/// <param name="decoder">The decoder</param>
/// <param name="scanIndex">The index of the scan</param>
private void DecodeBlock(GolangJpegDecoderCore decoder, int scanIndex)
{
Block8x8* b = this.pointers.Block;
int huffmannIdx = (GolangHuffmanTree.AcTableIndex * GolangHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector;
if (this.ah != 0)
{
this.Refine(ref decoder.InputProcessor, ref decoder.HuffmanTrees[huffmannIdx], 1 << this.al);
}
else
{
int zig = this.zigStart;
if (zig == 0)
{
zig++;
// Decode the DC coefficient, as specified in section F.2.2.1.
int huffmanIndex = (GolangHuffmanTree.DcTableIndex * GolangHuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].DcTableSelector;
decoder.InputProcessor.DecodeHuffmanUnsafe(
ref decoder.HuffmanTrees[huffmanIndex],
out int value);
if (!decoder.InputProcessor.CheckEOF())
{
return;
}
if (value > 16)
{
throw new ImageFormatException("Excessive DC component");
}
decoder.InputProcessor.ReceiveExtendUnsafe(value, out int deltaDC);
if (!decoder.InputProcessor.CheckEOFEnsureNoError())
{
return;
}
this.pointers.Dc[this.ComponentIndex] += deltaDC;
// b[0] = dc[compIndex] << al;
value = this.pointers.Dc[this.ComponentIndex] << this.al;
Block8x8.SetScalarAt(b, 0, (short)value);
}
if (zig <= this.zigEnd && this.eobRun > 0)
{
this.eobRun--;
}
else
{
// Decode the AC coefficients, as specified in section F.2.2.2.
for (; zig <= this.zigEnd; zig++)
{
decoder.InputProcessor.DecodeHuffmanUnsafe(ref decoder.HuffmanTrees[huffmannIdx], out int value);
if (decoder.InputProcessor.HasError)
{
return;
}
int val0 = value >> 4;
int val1 = value & 0x0f;
if (val1 != 0)
{
zig += val0;
if (zig > this.zigEnd)
{
break;
}
decoder.InputProcessor.ReceiveExtendUnsafe(val1, out int ac);
if (decoder.InputProcessor.HasError)
{
return;
}
// b[Unzig[zig]] = ac << al;
value = ac << this.al;
Block8x8.SetScalarAt(b, this.pointers.Unzig[zig], (short)value);
}
else
{
if (val0 != 0x0f)
{
this.eobRun = (ushort)(1 << val0);
if (val0 != 0)
{
this.DecodeEobRun(val0, ref decoder.InputProcessor);
if (!decoder.InputProcessor.CheckEOFEnsureNoError())
{
return;
}
}
this.eobRun--;
break;
}
zig += 0x0f;
}
}
}
}
}
private void DecodeEobRun(int count, ref InputProcessor processor)
{
processor.DecodeBitsUnsafe(count, out int bitsResult);
if (processor.LastErrorCode != GolangDecoderErrorCode.NoError)
{
return;
}
this.eobRun |= bitsResult;
}
private void InitComponentScan(GolangJpegDecoderCore decoder, int i, ref GolangComponentScan currentComponentScan, ref int totalHv)
{
// Component selector.
int cs = decoder.Temp[1 + (2 * i)];
int compIndex = -1;
for (int j = 0; j < decoder.ComponentCount; j++)
{
// Component compv = ;
if (cs == decoder.Components[j].Identifier)
{
compIndex = j;
}
}
if (compIndex < 0)
{
throw new ImageFormatException("Unknown component selector");
}
currentComponentScan.ComponentIndex = (byte)compIndex;
this.ProcessComponentImpl(decoder, i, ref currentComponentScan, ref totalHv, decoder.Components[compIndex]);
}
private void ProcessComponentImpl(
GolangJpegDecoderCore decoder,
int i,
ref GolangComponentScan currentComponentScan,
ref int totalHv,
GolangComponent currentComponent)
{
// Section B.2.3 states that "the value of Cs_j shall be different from
// the values of Cs_1 through Cs_(j-1)". Since we have previously
// verified that a frame's component identifiers (C_i values in section
// B.2.2) are unique, it suffices to check that the implicit indexes
// into comp are unique.
for (int j = 0; j < i; j++)
{
if (currentComponentScan.ComponentIndex == this.pointers.ComponentScan[j].ComponentIndex)
{
throw new ImageFormatException("Repeated component selector");
}
}
totalHv += currentComponent.HorizontalSamplingFactor * currentComponent.VerticalSamplingFactor;
currentComponentScan.DcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] >> 4);
if (currentComponentScan.DcTableSelector > GolangHuffmanTree.MaxTh)
{
throw new ImageFormatException("Bad DC table selector value");
}
currentComponentScan.AcTableSelector = (byte)(decoder.Temp[2 + (2 * i)] & 0x0f);
if (currentComponentScan.AcTableSelector > GolangHuffmanTree.MaxTh)
{
throw new ImageFormatException("Bad AC table selector value");
}
}
/// <summary>
/// Decodes a successive approximation refinement block, as specified in section G.1.2.
/// </summary>
/// <param name="bp">The <see cref="InputProcessor"/> instance</param>
/// <param name="h">The Huffman tree</param>
/// <param name="delta">The low transform offset</param>
private void Refine(ref InputProcessor bp, ref GolangHuffmanTree h, int delta)
{
Block8x8* b = this.pointers.Block;
// Refining a DC component is trivial.
if (this.zigStart == 0)
{
if (this.zigEnd != 0)
{
throw new ImageFormatException("Invalid state for zig DC component");
}
bp.DecodeBitUnsafe(out bool bit);
if (!bp.CheckEOFEnsureNoError())
{
return;
}
if (bit)
{
int stuff = Block8x8.GetScalarAt(b, 0);
// int stuff = (int)b[0];
stuff |= delta;
// b[0] = stuff;
Block8x8.SetScalarAt(b, 0, (short)stuff);
}
return;
}
// Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3.
int zig = this.zigStart;
if (this.eobRun == 0)
{
for (; zig <= this.zigEnd; zig++)
{
bool done = false;
int z = 0;
bp.DecodeHuffmanUnsafe(ref h, out int val);
if (!bp.CheckEOF())
{
return;
}
int val0 = val >> 4;
int val1 = val & 0x0f;
switch (val1)
{
case 0:
if (val0 != 0x0f)
{
this.eobRun = 1 << val0;
if (val0 != 0)
{
this.DecodeEobRun(val0, ref bp);
if (!bp.CheckEOFEnsureNoError())
{
return;
}
}
done = true;
}
break;
case 1:
z = delta;
bp.DecodeBitUnsafe(out bool bit);
if (!bp.CheckEOFEnsureNoError())
{
return;
}
if (!bit)
{
z = -z;
}
break;
default:
throw new ImageFormatException("Unexpected Huffman code");
}
if (done)
{
break;
}
zig = this.RefineNonZeroes(ref bp, zig, val0, delta);
if (bp.ReachedEOF || bp.HasError)
{
return;
}
if (z != 0 && zig <= this.zigEnd)
{
// b[Unzig[zig]] = z;
Block8x8.SetScalarAt(b, this.pointers.Unzig[zig], (short)z);
}
}
}
if (this.eobRun > 0)
{
this.eobRun--;
this.RefineNonZeroes(ref bp, zig, -1, delta);
}
}
/// <summary>
/// Refines non-zero entries of b in zig-zag order.
/// If <paramref name="nz" /> >= 0, the first <paramref name="nz" /> zero entries are skipped over.
/// </summary>
/// <param name="bp">The <see cref="InputProcessor"/></param>
/// <param name="zig">The zig-zag start index</param>
/// <param name="nz">The non-zero entry</param>
/// <param name="delta">The low transform offset</param>
/// <returns>The <see cref="int" /></returns>
private int RefineNonZeroes(ref InputProcessor bp, int zig, int nz, int delta)
{
Block8x8* b = this.pointers.Block;
for (; zig <= this.zigEnd; zig++)
{
int u = this.pointers.Unzig[zig];
int bu = Block8x8.GetScalarAt(b, u);
// TODO: Are the equality comparsions OK with floating point values? Isn't an epsilon value necessary?
if (bu == 0)
{
if (nz == 0)
{
break;
}
nz--;
continue;
}
bp.DecodeBitUnsafe(out bool bit);
if (bp.HasError)
{
return int.MinValue;
}
if (!bit)
{
continue;
}
int val = bu >= 0 ? bu + delta : bu - delta;
Block8x8.SetScalarAt(b, u, (short)val);
}
return zig;
}
}
}

392
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs

@ -1,392 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// Encapsulates stream reading and processing data and operations for <see cref="GolangJpegDecoderCore"/>.
/// It's a value type for imporved data locality, and reduced number of CALLVIRT-s
/// </summary>
internal struct InputProcessor : IDisposable
{
/// <summary>
/// Holds the unprocessed bits that have been taken from the byte-stream.
/// </summary>
public Bits Bits;
/// <summary>
/// The byte buffer
/// </summary>
public Bytes Bytes;
/// <summary>
/// Initializes a new instance of the <see cref="InputProcessor"/> struct.
/// </summary>
/// <param name="inputStream">The input <see cref="Stream"/></param>
/// <param name="temp">Temporal buffer, same as <see cref="GolangJpegDecoderCore.Temp"/></param>
public InputProcessor(Stream inputStream, byte[] temp)
{
this.Bits = default;
this.Bytes = Bytes.Create();
this.InputStream = inputStream;
this.Temp = temp;
this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Gets the input stream
/// </summary>
public Stream InputStream { get; }
/// <summary>
/// Gets the temporary buffer, same instance as <see cref="GolangJpegDecoderCore.Temp"/>
/// </summary>
public byte[] Temp { get; }
/// <summary>
/// Gets a value indicating whether an unexpected EOF reached in <see cref="InputStream"/>.
/// </summary>
public bool ReachedEOF => this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream;
public bool HasError => this.LastErrorCode != GolangDecoderErrorCode.NoError;
public GolangDecoderErrorCode LastErrorCode { get; private set; }
public void ResetErrorState() => this.LastErrorCode = GolangDecoderErrorCode.NoError;
/// <summary>
/// If errorCode indicates unexpected EOF, sets <see cref="ReachedEOF"/> to true and returns false.
/// Calls <see cref="DecoderThrowHelper.EnsureNoError"/> and returns true otherwise.
/// </summary>
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
public bool CheckEOFEnsureNoError()
{
if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{
return false;
}
this.LastErrorCode.EnsureNoError();
return true;
}
/// <summary>
/// If errorCode indicates unexpected EOF, sets <see cref="ReachedEOF"/> to true and returns false.
/// Returns true otherwise.
/// </summary>
/// <returns>A <see cref="bool"/> indicating whether EOF reached</returns>
public bool CheckEOF()
{
if (this.LastErrorCode == GolangDecoderErrorCode.UnexpectedEndOfStream)
{
return false;
}
return true;
}
/// <inheritdoc />
public void Dispose()
{
this.Bytes.Dispose();
}
/// <summary>
/// Returns the next byte, whether buffered or not buffered. It does not care about byte stuffing.
/// </summary>
/// <returns>The <see cref="byte" /></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
return this.Bytes.ReadByte(this.InputStream);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GolangDecoderErrorCode ReadByteUnsafe(out byte result)
{
this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result);
return this.LastErrorCode;
}
/// <summary>
/// Decodes a single bit
/// TODO: This method (and also the usages) could be optimized by batching!
/// </summary>
/// <param name="result">The decoded bit as a <see cref="bool"/></param>
/// <returns>The <see cref="GolangDecoderErrorCode" /></returns>
public GolangDecoderErrorCode DecodeBitUnsafe(out bool result)
{
if (this.Bits.UnreadBits == 0)
{
this.LastErrorCode = this.Bits.Ensure1BitUnsafe(ref this);
if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{
result = false;
return this.LastErrorCode;
}
}
result = (this.Bits.Accumulator & this.Bits.Mask) != 0;
this.Bits.UnreadBits--;
this.Bits.Mask >>= 1;
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing.
/// Does not throw on errors, returns <see cref="GolangJpegDecoderCore"/> instead!
/// </summary>
/// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReadFullUnsafe(byte[] data, int offset, int length)
{
// Unread the overshot bytes, if any.
if (this.Bytes.UnreadableBytes != 0)
{
if (this.Bits.UnreadBits >= 8)
{
this.UnreadByteStuffedByte();
}
this.Bytes.UnreadableBytes = 0;
}
this.LastErrorCode = GolangDecoderErrorCode.NoError;
while (length > 0 && this.LastErrorCode == GolangDecoderErrorCode.NoError)
{
if (this.Bytes.J - this.Bytes.I >= length)
{
Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, length);
this.Bytes.I += length;
length -= length;
}
else
{
Array.Copy(this.Bytes.Buffer, this.Bytes.I, data, offset, this.Bytes.J - this.Bytes.I);
offset += this.Bytes.J - this.Bytes.I;
length -= this.Bytes.J - this.Bytes.I;
this.Bytes.I += this.Bytes.J - this.Bytes.I;
this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream);
}
}
return this.LastErrorCode;
}
/// <summary>
/// Decodes the given number of bits
/// </summary>
/// <param name="count">The number of bits to decode.</param>
/// <param name="result">The <see cref="uint" /> result</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode DecodeBitsUnsafe(int count, out int result)
{
if (this.Bits.UnreadBits < count)
{
this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(count, ref this);
if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{
result = 0;
return this.LastErrorCode;
}
}
result = this.Bits.Accumulator >> (this.Bits.UnreadBits - count);
result = result & ((1 << count) - 1);
this.Bits.UnreadBits -= count;
this.Bits.Mask >>= count;
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Extracts the next Huffman-coded value from the bit-stream into result, decoded according to the given value.
/// </summary>
/// <param name="huffmanTree">The huffman value</param>
/// <param name="result">The decoded <see cref="byte" /></param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode DecodeHuffmanUnsafe(ref GolangHuffmanTree huffmanTree, out int result)
{
result = 0;
if (huffmanTree.Length == 0)
{
DecoderThrowHelper.ThrowImageFormatException.UninitializedHuffmanTable();
}
if (this.Bits.UnreadBits < 8)
{
this.LastErrorCode = this.Bits.Ensure8BitsUnsafe(ref this);
if (this.LastErrorCode == GolangDecoderErrorCode.NoError)
{
int lutIndex = (this.Bits.Accumulator >> (this.Bits.UnreadBits - GolangHuffmanTree.LutSizeLog2)) & 0xFF;
int v = huffmanTree.Lut[lutIndex];
if (v != 0)
{
int n = (v & 0xFF) - 1;
this.Bits.UnreadBits -= n;
this.Bits.Mask >>= n;
result = v >> 8;
return this.LastErrorCode;
}
}
else
{
this.UnreadByteStuffedByte();
return this.LastErrorCode;
}
}
int code = 0;
for (int i = 0; i < GolangHuffmanTree.MaxCodeLength; i++)
{
if (this.Bits.UnreadBits == 0)
{
this.LastErrorCode = this.Bits.EnsureNBitsUnsafe(1, ref this);
if (this.HasError)
{
return this.LastErrorCode;
}
}
if ((this.Bits.Accumulator & this.Bits.Mask) != 0)
{
code |= 1;
}
this.Bits.UnreadBits--;
this.Bits.Mask >>= 1;
if (code <= huffmanTree.MaxCodes[i])
{
result = huffmanTree.GetValue(code, i);
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
code <<= 1;
}
// Unrecoverable error, throwing:
DecoderThrowHelper.ThrowImageFormatException.BadHuffmanCode();
// DUMMY RETURN! C# doesn't know we have thrown an exception!
return GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Skips the next n bytes.
/// </summary>
/// <param name="count">The number of bytes to ignore.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
this.LastErrorCode = this.SkipUnsafe(count);
this.LastErrorCode.EnsureNoError();
}
/// <summary>
/// Skips the next n bytes.
/// Does not throw, returns <see cref="GolangDecoderErrorCode"/> instead!
/// </summary>
/// <param name="count">The number of bytes to ignore.</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode SkipUnsafe(int count)
{
// Unread the overshot bytes, if any.
if (this.Bytes.UnreadableBytes != 0)
{
if (this.Bits.UnreadBits >= 8)
{
this.UnreadByteStuffedByte();
}
this.Bytes.UnreadableBytes = 0;
}
while (true)
{
int m = this.Bytes.J - this.Bytes.I;
if (m > count)
{
m = count;
}
this.Bytes.I += m;
count -= m;
if (count == 0)
{
break;
}
this.LastErrorCode = this.Bytes.FillUnsafe(this.InputStream);
if (this.LastErrorCode != GolangDecoderErrorCode.NoError)
{
return this.LastErrorCode;
}
}
return this.LastErrorCode = GolangDecoderErrorCode.NoError;
}
/// <summary>
/// Reads exactly length bytes into data. It does not care about byte stuffing.
/// </summary>
/// <param name="data">The data to write to.</param>
/// <param name="offset">The offset in the source buffer</param>
/// <param name="length">The number of bytes to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadFull(byte[] data, int offset, int length)
{
this.LastErrorCode = this.ReadFullUnsafe(data, offset, length);
this.LastErrorCode.EnsureNoError();
}
/// <summary>
/// Undoes the most recent ReadByteStuffedByte call,
/// giving a byte of data back from bits to bytes. The Huffman look-up table
/// requires at least 8 bits for look-up, which means that Huffman decoding can
/// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
/// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
/// </summary>
public void UnreadByteStuffedByte()
{
this.Bytes.I -= this.Bytes.UnreadableBytes;
this.Bytes.UnreadableBytes = 0;
if (this.Bits.UnreadBits >= 8)
{
this.Bits.Accumulator >>= 8;
this.Bits.UnreadBits -= 8;
this.Bits.Mask >>= 8;
}
}
/// <summary>
/// Receive extend
/// </summary>
/// <param name="t">Byte</param>
/// <param name="x">Read bits value</param>
/// <returns>The <see cref="GolangDecoderErrorCode"/></returns>
public GolangDecoderErrorCode ReceiveExtendUnsafe(int t, out int x)
{
this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x);
return this.LastErrorCode;
}
/// <summary>
/// Reset the Huffman decoder.
/// </summary>
public void ResetHuffmanDecoder()
{
this.Bits = default;
}
}
}

25
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegScanDecoder.md

@ -1,25 +0,0 @@
## JpegScanDecoder
Encapsulates the impementation of the Jpeg top-to bottom scan decoder triggered by the `SOS` marker.
The implementation is optimized to hold most of the necessary data in a single value type, which is intended to be used as an on-stack object.
#### Benefits:
- Maximized locality of reference by keeping most of the operation data on the stack
- Achieving this without long parameter lists, most of the values describing the state of the decoder algorithm
are members of the `JpegScanDecoder` struct
- Most of the logic related to Scan decoding is refactored & simplified now to live in the methods of `JpegScanDecoder`
- The first step is done towards separating the stream reading from block processing. They can be refactored later to be executed in two disctinct loops.
- The input processing loop can be `async`
- The block processing loop can be parallelized
#### Data layout
|JpegScanDecoder |
|-------------------|
|Variables |
|DataPointers |
|ComputationData |
- **ComputationData** holds the "large" data blocks needed for computations (Mostly `Block8x8F`-s)
- **DataPointers** contains pointers to the memory regions of `ComponentData` so they can be easily passed around to pointer based utility methods of `Block8x8F`

15
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/MissingFF00Exception.cs

@ -1,15 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{
/// <summary>
/// The missing ff00 exception.
/// </summary>
// ReSharper disable once InconsistentNaming
internal class MissingFF00Exception : Exception
{
}
}

42
src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoder.cs

@ -1,42 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
/// <summary>
/// Image decoder for generating an image out of a jpg stream.
/// </summary>
internal sealed class GolangJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new GolangJpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new GolangJpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}
}
}
}

824
src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs

@ -1,824 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
{
/// <inheritdoc />
/// <summary>
/// Performs the jpeg decoding operation.
/// </summary>
internal sealed unsafe class GolangJpegDecoderCore : IRawJpegData
{
/// <summary>
/// The maximum number of color components
/// </summary>
public const int MaxComponents = 4;
/// <summary>
/// The maximum number of quantization tables
/// </summary>
public const int MaxTq = 3;
/// <summary>
/// The only supported precision
/// </summary>
public const int SupportedPrecision = 8;
// Complex value type field + mutable + available to other classes = the field MUST NOT be private :P
#pragma warning disable SA1401 // FieldsMustBePrivate
/// <summary>
/// Encapsulates stream reading and processing data and operations for <see cref="GolangJpegDecoderCore"/>.
/// It's a value type for improved data locality, and reduced number of CALLVIRT-s
/// </summary>
public InputProcessor InputProcessor;
#pragma warning restore SA401
/// <summary>
/// The global configuration
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Whether the image has a JFIF header
/// It's faster to check this than to use the equality operator on the struct
/// </summary>
private bool isJFif;
/// <summary>
/// Contains information about the JFIF marker
/// </summary>
private JFifMarker jFif;
/// <summary>
/// Whether the image has a EXIF header
/// </summary>
private bool isExif;
/// <summary>
/// Whether the image has an Adobe marker.
/// It's faster to check this than to use the equality operator on the struct
/// </summary>
private bool isAdobe;
/// <summary>
/// Contains information about the Adobe marker
/// </summary>
private AdobeMarker adobe;
/// <summary>
/// Initializes a new instance of the <see cref="GolangJpegDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public GolangJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.IgnoreMetadata = options.IgnoreMetadata;
this.configuration = configuration ?? Configuration.Default;
this.Temp = new byte[2 * Block8x8F.Size];
}
/// <inheritdoc />
public JpegColorSpace ColorSpace { get; private set; }
/// <summary>
/// Gets the component array
/// </summary>
public GolangComponent[] Components { get; private set; }
/// <summary>
/// Gets the huffman trees
/// </summary>
public GolangHuffmanTree[] HuffmanTrees { get; private set; }
/// <inheritdoc />
public Block8x8F[] QuantizationTables { get; private set; }
/// <summary>
/// Gets the temporary buffer used to store bytes read from the stream.
/// TODO: Should be stack allocated, fixed sized buffer!
/// </summary>
public byte[] Temp { get; }
/// <inheritdoc />
public Size ImageSizeInPixels { get; private set; }
/// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
public Size ImageSizeInMCU { get; private set; }
/// <inheritdoc />
public int ComponentCount { get; private set; }
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
/// <summary>
/// Gets the color depth, in number of bits per pixel.
/// </summary>
public int BitsPerPixel => this.ComponentCount * SupportedPrecision;
/// <summary>
/// Gets the image height
/// </summary>
public int ImageHeight => this.ImageSizeInPixels.Height;
/// <summary>
/// Gets the image width
/// </summary>
public int ImageWidth => this.ImageSizeInPixels.Width;
/// <summary>
/// Gets the input stream.
/// </summary>
public Stream InputStream { get; private set; }
/// <summary>
/// Gets a value indicating whether the image is interlaced (progressive)
/// </summary>
public bool IsProgressive { get; private set; }
/// <summary>
/// Gets the restart interval
/// </summary>
public int RestartInterval { get; private set; }
/// <summary>
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the X axis
/// </summary>
public int MCUCountX => this.ImageSizeInMCU.Width;
/// <summary>
/// Gets the number of MCU-s (Minimum Coded Units) in the image along the Y axis
/// </summary>
public int MCUCountY => this.ImageSizeInMCU.Height;
/// <summary>
/// Gets the total number of MCU-s (Minimum Coded Units) in the image.
/// </summary>
public int TotalMCUCount => this.MCUCountX * this.MCUCountY;
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; }
/// <summary>
/// Gets the <see cref="ImageMetaData"/> decoded by this decoder instance.
/// </summary>
public ImageMetaData MetaData { get; private set; }
/// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream, where the image should be.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream);
return this.PostProcessIntoImage<TPixel>();
}
/// <summary>
/// Reads the raw image information from the specified stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
/// <inheritdoc />
public void Dispose()
{
if (this.Components != null)
{
foreach (GolangComponent component in this.Components)
{
component?.Dispose();
}
}
this.InputProcessor.Dispose();
}
/// <summary>
/// Read metadata from stream and read the blocks in the scans into <see cref="GolangComponent.SpectralBlocks"/>.
/// </summary>
/// <param name="stream">The stream</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param>
public void ParseStream(Stream stream, bool metadataOnly = false)
{
this.MetaData = new ImageMetaData();
this.InputStream = stream;
this.InputProcessor = new InputProcessor(stream, this.Temp);
if (!metadataOnly)
{
this.HuffmanTrees = GolangHuffmanTree.CreateHuffmanTrees();
this.QuantizationTables = new Block8x8F[MaxTq + 1];
}
// Check for the Start Of Image marker.
this.InputProcessor.ReadFull(this.Temp, 0, 2);
if (this.Temp[0] != JpegConstants.Markers.XFF || this.Temp[1] != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
}
// Process the remaining segments until the End Of Image marker.
bool processBytes = true;
// we can't currently short circute progressive images so don't try.
while (processBytes)
{
this.InputProcessor.ReadFull(this.Temp, 0, 2);
if (this.InputProcessor.ReachedEOF)
{
// We've reached the end of the stream.
processBytes = false;
}
while (this.Temp[0] != 0xff)
{
// Strictly speaking, this is a format error. However, libjpeg is
// liberal in what it accepts. As of version 9, next_marker in
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// continues to decode the stream. Even before next_marker sees
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// bytes as it can, possibly past the end of a scan's data. It
// effectively puts back any markers that it overscanned (e.g. an
// "\xff\xd9" EOI marker), but it does not put back non-marker data,
// and thus it can silently ignore a small number of extraneous
// non-marker bytes before next_marker has a chance to see them (and
// print a warning).
// We are therefore also liberal in what we accept. Extraneous data
// is silently ignore
// This is similar to, but not exactly the same as, the restart
// mechanism within a scan (the RST[0-7] markers).
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as
// "\xff\x00", and so are detected a little further down below.
this.Temp[0] = this.Temp[1];
this.Temp[1] = this.InputProcessor.ReadByte();
}
byte marker = this.Temp[1];
if (marker == 0)
{
// Treat "\xff\x00" as extraneous data.
continue;
}
while (marker == 0xff)
{
// Section B.1.1.2 says, "Any marker may optionally be preceded by any
// number of fill bytes, which are bytes assigned code X'FF'".
this.InputProcessor.ReadByteUnsafe(out marker);
if (this.InputProcessor.ReachedEOF)
{
// We've reached the end of the stream.
processBytes = false;
break;
}
}
// End Of Image.
if (marker == JpegConstants.Markers.EOI)
{
break;
}
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{
// Figures B.2 and B.16 of the specification suggest that restart markers should
// only occur between Entropy Coded Segments and not after the final ECS.
// However, some encoders may generate incorrect JPEGs with a final restart
// marker. That restart marker will be seen here instead of inside the ProcessSOS
// method, and is ignored as a harmless error. Restart markers have no extra data,
// so we check for this before we read the 16-bit length of the segment.
continue;
}
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
// length itself, so we subtract 2 to get the number of remaining bytes.
this.InputProcessor.ReadFullUnsafe(this.Temp, 0, 2);
int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
if (remaining < 0)
{
throw new ImageFormatException("Short segment length.");
}
switch (marker)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining, metadataOnly);
break;
case JpegConstants.Markers.DHT:
if (metadataOnly)
{
this.InputProcessor.Skip(remaining);
}
else
{
this.ProcessDefineHuffmanTablesMarker(remaining);
}
break;
case JpegConstants.Markers.DQT:
if (metadataOnly)
{
this.InputProcessor.Skip(remaining);
}
else
{
this.ProcessDefineQuantizationTablesMarker(remaining);
}
break;
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker(remaining);
if (this.InputProcessor.ReachedEOF)
{
// If unexpected EOF reached. We can stop processing bytes as we now have the image data.
processBytes = false;
}
}
else
{
// It's highly unlikely that APPn related data will be found after the SOS marker
// We should have gathered everything we need by now.
processBytes = false;
}
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
this.InputProcessor.Skip(remaining);
}
else
{
this.ProcessDefineRestartIntervalMarker(remaining);
}
break;
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(remaining);
break;
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining);
break;
case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(remaining);
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
default:
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == JpegConstants.Markers.COM)
{
this.InputProcessor.Skip(remaining);
}
break;
}
}
this.InitDerivedMetaDataProperties();
}
/// <summary>
/// Returns true if 'mcuCounter' is at restart interval
/// </summary>
public bool IsAtRestartInterval(int mcuCounter)
{
return this.RestartInterval > 0 && mcuCounter % this.RestartInterval == 0
&& mcuCounter < this.TotalMCUCount;
}
/// <summary>
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary>
private void InitDerivedMetaDataProperties()
{
if (this.isJFif)
{
this.MetaData.HorizontalResolution = this.jFif.XDensity;
this.MetaData.VerticalResolution = this.jFif.YDensity;
}
else if (this.isExif)
{
double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizonalTag)
? ((Rational)horizonalTag.Value).ToDouble()
: 0;
double verticalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.YResolution, out ExifValue verticalTag)
? ((Rational)verticalTag.Value).ToDouble()
: 0;
if (horizontalValue > 0 && verticalValue > 0)
{
this.MetaData.HorizontalResolution = horizontalValue;
this.MetaData.VerticalResolution = verticalValue;
}
}
if (this.MetaData.IccProfile?.CheckIsValid() == false)
{
this.MetaData.IccProfile = null;
}
}
/// <summary>
/// Processes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApplicationHeaderMarker(int remaining)
{
if (remaining < 5)
{
this.InputProcessor.Skip(remaining);
return;
}
const int MarkerLength = JFifMarker.Length;
this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength);
remaining -= MarkerLength;
this.isJFif = JFifMarker.TryParse(this.Temp, out this.jFif);
if (remaining > 0)
{
this.InputProcessor.Skip(remaining);
}
}
/// <summary>
/// Processes the App1 marker retrieving any stored metadata
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp1Marker(int remaining)
{
if (remaining < 6 || this.IgnoreMetadata)
{
this.InputProcessor.Skip(remaining);
return;
}
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{
this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile);
}
}
/// <summary>
/// Processes the App2 marker retrieving any stored ICC profile information
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp2Marker(int remaining)
{
// Length is 14 though we only need to check 12.
const int Icclength = 14;
if (remaining < Icclength || this.IgnoreMetadata)
{
this.InputProcessor.Skip(remaining);
return;
}
byte[] identifier = new byte[Icclength];
this.InputProcessor.ReadFull(identifier, 0, Icclength);
remaining -= Icclength; // We have read it by this point
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
if (this.MetaData.IccProfile == null)
{
this.MetaData.IccProfile = new IccProfile(profile);
}
else
{
this.MetaData.IccProfile.Extend(profile);
}
}
else
{
// Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this.
this.InputProcessor.Skip(remaining);
}
}
/// <summary>
/// Processes the application header containing the Adobe identifier
/// which stores image encoding information for DCT filters.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp14Marker(int remaining)
{
const int MarkerLength = AdobeMarker.Length;
if (remaining < MarkerLength)
{
// Skip the application header length
this.InputProcessor.Skip(remaining);
return;
}
this.InputProcessor.ReadFull(this.Temp, 0, MarkerLength);
remaining -= MarkerLength;
this.isAdobe = AdobeMarker.TryParse(this.Temp, out this.adobe);
if (remaining > 0)
{
this.InputProcessor.Skip(remaining);
}
}
/// <summary>
/// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the tables do not match the header
/// </exception>
private void ProcessDefineQuantizationTablesMarker(int remaining)
{
while (remaining > 0)
{
bool done = false;
remaining--;
byte x = this.InputProcessor.ReadByte();
int tq = x & 0x0F;
if (tq > MaxTq)
{
throw new ImageFormatException("Bad Tq value");
}
switch (x >> 4)
{
case 0:
if (remaining < Block8x8F.Size)
{
done = true;
break;
}
remaining -= Block8x8F.Size;
this.InputProcessor.ReadFull(this.Temp, 0, Block8x8F.Size);
for (int i = 0; i < Block8x8F.Size; i++)
{
this.QuantizationTables[tq][i] = this.Temp[i];
}
break;
case 1:
if (remaining < 2 * Block8x8F.Size)
{
done = true;
break;
}
remaining -= 2 * Block8x8F.Size;
this.InputProcessor.ReadFull(this.Temp, 0, 2 * Block8x8F.Size);
for (int i = 0; i < Block8x8F.Size; i++)
{
this.QuantizationTables[tq][i] = (this.Temp[2 * i] << 8) | this.Temp[(2 * i) + 1];
}
break;
default:
throw new ImageFormatException("Bad Pq value");
}
if (done)
{
break;
}
}
if (remaining != 0)
{
throw new ImageFormatException("DQT has wrong length");
}
}
/// <summary>
/// Processes the Start of Frame marker. Specified in section B.2.2.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param>
private void ProcessStartOfFrameMarker(int remaining, bool metadataOnly)
{
if (this.ComponentCount != 0)
{
throw new ImageFormatException("Multiple SOF markers");
}
switch (remaining)
{
case 6 + (3 * 1): // grayscale image.
this.ComponentCount = 1;
break;
case 6 + (3 * 3): // YCbCr or RGB image.
this.ComponentCount = 3;
break;
case 6 + (3 * 4): // YCbCrK or CMYK image.
this.ComponentCount = 4;
break;
default:
throw new ImageFormatException("Incorrect number of components");
}
this.InputProcessor.ReadFull(this.Temp, 0, remaining);
// We only support 8-bit precision.
if (this.Temp[0] != SupportedPrecision)
{
throw new ImageFormatException("Only 8-Bit precision supported.");
}
int height = (this.Temp[1] << 8) + this.Temp[2];
int width = (this.Temp[3] << 8) + this.Temp[4];
this.ImageSizeInPixels = new Size(width, height);
if (this.Temp[5] != this.ComponentCount)
{
throw new ImageFormatException("SOF has wrong length");
}
if (!metadataOnly)
{
this.Components = new GolangComponent[this.ComponentCount];
for (int i = 0; i < this.ComponentCount; i++)
{
byte componentIdentifier = this.Temp[6 + (3 * i)];
var component = new GolangComponent(componentIdentifier, i);
component.InitializeCoreData(this);
this.Components[i] = component;
}
int h0 = this.Components[0].HorizontalSamplingFactor;
int v0 = this.Components[0].VerticalSamplingFactor;
this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0);
this.ColorSpace = this.DeduceJpegColorSpace();
foreach (GolangComponent component in this.Components)
{
component.InitializeDerivedData(this.configuration.MemoryAllocator, this);
}
}
}
/// <summary>
/// Processes a Define Huffman Table marker, and initializes a huffman
/// struct from its contents. Specified in section B.2.4.2.
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineHuffmanTablesMarker(int remaining)
{
while (remaining > 0)
{
if (remaining < 17)
{
throw new ImageFormatException($"DHT has wrong length. {remaining}");
}
this.InputProcessor.ReadFull(this.Temp, 0, 17);
int tc = this.Temp[0] >> 4;
if (tc > GolangHuffmanTree.MaxTc)
{
throw new ImageFormatException("Bad Tc value");
}
int th = this.Temp[0] & 0x0f;
if (th > GolangHuffmanTree.MaxTh)
{
throw new ImageFormatException("Bad Th value");
}
int huffTreeIndex = (tc * GolangHuffmanTree.ThRowSize) + th;
this.HuffmanTrees[huffTreeIndex].ProcessDefineHuffmanTablesMarkerLoop(
ref this.InputProcessor,
this.Temp,
ref remaining);
}
}
/// <summary>
/// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, in
/// macroblocks
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessDefineRestartIntervalMarker(int remaining)
{
if (remaining != 2)
{
throw new ImageFormatException("DRI has wrong length");
}
this.InputProcessor.ReadFull(this.Temp, 0, remaining);
this.RestartInterval = (this.Temp[0] << 8) + this.Temp[1];
}
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <exception cref="ImageFormatException">
/// Missing SOF Marker
/// SOS has wrong length
/// </exception>
private void ProcessStartOfScanMarker(int remaining)
{
GolangJpegScanDecoder scan = default;
GolangJpegScanDecoder.InitStreamReading(&scan, this, remaining);
this.InputProcessor.Bits = default;
scan.DecodeBlocks(this);
}
private JpegColorSpace DeduceJpegColorSpace()
{
switch (this.ComponentCount)
{
case 1:
return JpegColorSpace.Grayscale;
case 3:
if (!this.isAdobe || this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYCbCr)
{
return JpegColorSpace.YCbCr;
}
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{
return JpegColorSpace.RGB;
}
break;
case 4:
if (this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck)
{
return JpegColorSpace.Ycck;
}
return JpegColorSpace.Cmyk;
}
throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}."
+ "JpegDecoder only supports YCbCr, RGB, YccK, CMYK and grayscale color spaces.");
}
private Image<TPixel> PostProcessIntoImage<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryAllocator, this))
{
var image = new Image<TPixel>(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData);
postProcessor.PostProcess(image.Frames.RootFrame);
return image;
}
}
}
}

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

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -24,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
using (var decoder = new JpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
@ -35,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
using (var decoder = new JpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}

51
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs → src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -10,7 +10,6 @@ using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@ -19,14 +18,14 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Performs the jpeg decoding operation.
/// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// with additional fixes for both performance and common encoding errors.
/// </summary>
internal sealed class PdfJsJpegDecoderCore : IRawJpegData
internal sealed class JpegDecoderCore : IRawJpegData
{
/// <summary>
/// The only supported precision
@ -51,12 +50,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// The DC HUffman tables
/// </summary>
private PdfJsHuffmanTables dcHuffmanTables;
private HuffmanTables dcHuffmanTables;
/// <summary>
/// The AC HUffman tables
/// </summary>
private PdfJsHuffmanTables acHuffmanTables;
private HuffmanTables acHuffmanTables;
/// <summary>
/// The fast AC tables used for entropy decoding
@ -84,11 +83,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private AdobeMarker adobe;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsJpegDecoderCore" /> class.
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public PdfJsJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.configuration = configuration ?? Configuration.Default;
this.IgnoreMetadata = options.IgnoreMetadata;
@ -97,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Gets the frame
/// </summary>
public PdfJsFrame Frame { get; private set; }
public JpegFrame Frame { get; private set; }
/// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; }
@ -146,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Gets the components.
/// </summary>
public PdfJsFrameComponent[] Components => this.Frame.Components;
public JpegFrameComponent[] Components => this.Frame.Components;
/// <inheritdoc/>
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
@ -159,14 +158,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns>
public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
/// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{
int value = stream.Read(marker, 0, 2);
if (value == 0)
{
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
if (marker[0] == JpegConstants.Markers.XFF)
@ -179,16 +178,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int suffix = stream.ReadByte();
if (suffix == -1)
{
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
m = suffix;
}
return new PdfJsFileMarker((byte)m, stream.Position - 2);
return new JpegFileMarker((byte)m, stream.Position - 2);
}
return new PdfJsFileMarker(marker[1], stream.Position - 2, true);
return new JpegFileMarker(marker[1], stream.Position - 2, true);
}
/// <summary>
@ -228,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
// Check for the Start Of Image marker.
this.InputStream.Read(this.markerBuffer, 0, 2);
var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
@ -236,14 +235,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.InputStream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2);
fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2);
// Only assign what we need
if (!metadataOnly)
{
this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new HuffmanTables();
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
}
@ -630,7 +629,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <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(int remaining, PdfJsFileMarker frameMarker, bool metadataOnly)
private void ProcessStartOfFrameMarker(int remaining, JpegFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{
@ -645,7 +644,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Only 8-Bit precision supported.");
}
this.Frame = new PdfJsFrame
this.Frame = new JpegFrame
{
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
@ -667,7 +666,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
// No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
this.Frame.Components = new JpegFrameComponent[this.Frame.ComponentCount];
this.ColorSpace = this.DeduceJpegColorSpace();
for (int i = 0; i < this.Frame.ComponentCount; i++)
@ -686,7 +685,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
maxV = v;
}
var component = new PdfJsFrameComponent(this.configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
var component = new JpegFrameComponent(this.configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
@ -794,7 +793,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Unknown component selector");
}
ref PdfJsFrameComponent component = ref this.Frame.Components[componentIndex];
ref JpegFrameComponent component = ref this.Frame.Components[componentIndex];
int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
@ -831,9 +830,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="codeLengths">The codelengths</param>
/// <param name="values">The values</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BuildHuffmanTable(PdfJsHuffmanTables tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
tables[index] = new PdfJsHuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
}
/// <summary>

42
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs

@ -1,42 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
/// <summary>
/// Image decoder for generating an image out of a jpg stream.
/// </summary>
internal sealed class PdfJsJpegDecoder : IImageDecoder, IJpegDecoderOptions, IImageInfoDetector
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}
}
}
}

20
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs

@ -4,8 +4,8 @@
using System.Drawing;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using CoreSize = SixLabors.Primitives.Size;
@ -45,23 +45,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark(Description = "Decode Jpeg - ImageSharp")]
public CoreSize JpegImageSharpOrig()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
using (var image = Image.Load<Rgba32>(memoryStream, new GolangJpegDecoder()))
{
return new CoreSize(image.Width, image.Height);
}
}
}
[Benchmark(Description = "Decode Jpeg - ImageSharp PdfJs")]
public CoreSize JpegImageSharpPdfJs()
public CoreSize JpegImageSharp()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
using (var image = Image.Load<Rgba32>(memoryStream, new PdfJsJpegDecoder()))
using (var image = Image.Load<Rgba32>(memoryStream, new JpegDecoder()))
{
return new CoreSize(image.Width, image.Height);
}

13
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegMultiple.cs

@ -3,8 +3,7 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SDImage = System.Drawing.Image;
@ -22,15 +21,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
protected override IEnumerable<string> SearchPatterns => new[] { "*.jpg" };
[Benchmark(Description = "DecodeJpegMultiple - ImageSharp")]
public void DecodeJpegImageSharpOrig()
public void DecodeJpegImageSharp()
{
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new GolangJpegDecoder()));
}
[Benchmark(Description = "DecodeJpegMultiple - ImageSharp PDFJs")]
public void DecodeJpegImageSharpPdfJs()
{
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new PdfJsJpegDecoder()));
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
}
[Benchmark(Baseline = true, Description = "DecodeJpegMultiple - System.Drawing")]

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

@ -5,7 +5,6 @@ using BenchmarkDotNet.Attributes;
using System.Drawing;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
@ -38,12 +37,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
}
[Benchmark(Description = "PdfJsJpegDecoderCore.ParseStream")]
[Benchmark(Description = "JpegDecoderCore.ParseStream")]
public void ParseStreamPdfJs()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder() { IgnoreMetadata = true });
var decoder = new JpegDecoderCore(Configuration.Default, new Formats.Jpeg.JpegDecoder() { IgnoreMetadata = true });
decoder.ParseStream(memoryStream);
decoder.Dispose();
}

9
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs

@ -4,16 +4,17 @@
using System;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
public class DoubleBufferedStreams
{
private byte[] buffer = CreateTestBytes();
private byte[] chunk1 = new byte[2];
private byte[] chunk2 = new byte[2];
private readonly byte[] buffer = CreateTestBytes();
private readonly byte[] chunk1 = new byte[2];
private readonly byte[] chunk2 = new byte[2];
private MemoryStream stream1;
private MemoryStream stream2;

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

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public void JpegSystemDrawing()
{
using (MemoryStream memoryStream = new MemoryStream())
using (var memoryStream = new MemoryStream())
{
this.bmpDrawing.Save(memoryStream, ImageFormat.Jpeg);
}
@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Description = "ImageSharp Jpeg")]
public void JpegCore()
{
using (MemoryStream memoryStream = new MemoryStream())
using (var memoryStream = new MemoryStream())
{
this.bmpCore.SaveAsJpeg(memoryStream);
}

19
tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs

@ -3,8 +3,8 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
@ -29,22 +29,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark]
public IImageInfo IdentifyGolang()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new GolangJpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
}
[Benchmark]
public IImageInfo IdentifyPdfJs()
public IImageInfo Identify()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new PdfJsJpegDecoder();
var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
}

8
tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(
TestImages.Jpeg.Baseline.Jpeg420Exif
TestImages.Jpeg.Baseline.Jpeg420Exif
//, TestImages.Jpeg.Baseline.Calliphora
)]
public string TestImage { get; set; }
@ -74,10 +74,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark]
public void ImageSharp()
{
using (var source = Image.Load(
this.configuration,
this.sourceBytes,
new JpegDecoder { IgnoreMetadata = true }))
var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true });
using (source)
using (var destStream = new MemoryStream(this.destBytes))
{
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4));

3
tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs

@ -3,7 +3,8 @@
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.Memory;
using Xunit;

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
// ReSharper disable InconsistentNaming
@ -13,33 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
{
return;
}
// For 32 bit test enviroments:
provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
using (Image<TPixel> image = provider.GetImage(GolangJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeBaselineJpegOutputName;
image.CompareToReferenceOutput(
this.GetImageComparer(provider),
provider,
appendPixelTypeToFileName: false);
}
provider.Configuration.MemoryAllocator.ReleaseRetainedResources();
}
[Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
public void DecodeBaselineJpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
@ -48,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return;
}
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
@ -62,20 +36,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow_Golang<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
// TODO: We need a public ImageDecoderException class in ImageSharp!
Assert.ThrowsAny<Exception>(() => provider.GetImage(GolangJpegDecoder));
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
// TODO: We need a public ImageDecoderException class in ImageSharp!
Assert.ThrowsAny<Exception>(() => provider.GetImage(PdfJsJpegDecoder));
Assert.ThrowsAny<Exception>(() => provider.GetImage(JpegDecoder));
}
[Theory(Skip = "Debug only, enable manually!")]

26
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(MetaDataTestData))]
public void MetaDataIsParsedCorrectly_Orig(
public void MetaDataIsParsedCorrectly(
bool useIdentify,
string imagePath,
int expectedPixelSize,
@ -60,25 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
TestMetaDataImpl(
useIdentify,
GolangJpegDecoder,
imagePath,
expectedPixelSize,
exifProfilePresent,
iccProfilePresent);
}
[Theory]
[MemberData(nameof(MetaDataTestData))]
public void MetaDataIsParsedCorrectly_PdfJs(
bool useIdentify,
string imagePath,
int expectedPixelSize,
bool exifProfilePresent,
bool iccProfilePresent)
{
TestMetaDataImpl(
useIdentify,
PdfJsJpegDecoder,
JpegDecoder,
imagePath,
expectedPixelSize,
exifProfilePresent,
@ -216,7 +198,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify)
{
TestImageInfo(TestImages.Jpeg.Baseline.Floorplan, DefaultJpegDecoder, useIdentify,
TestImageInfo(TestImages.Jpeg.Baseline.Floorplan, JpegDecoder, useIdentify,
imageInfo =>
{
Assert.Equal(300, imageInfo.MetaData.HorizontalResolution);
@ -229,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify)
{
TestImageInfo(TestImages.Jpeg.Baseline.Jpeg420Exif, DefaultJpegDecoder, useIdentify,
TestImageInfo(TestImages.Jpeg.Baseline.Jpeg420Exif, JpegDecoder, useIdentify,
imageInfo =>
{
Assert.Equal(72, imageInfo.MetaData.HorizontalResolution);

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

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Linq;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
// ReSharper disable InconsistentNaming
@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
public void DecodeProgressiveJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider)
public void DecodeProgressiveJpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
@ -24,41 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return;
}
// Golang decoder is unable to decode these:
if (PdfJsOnly.Any(fn => fn.Contains(provider.SourceFileOrDescription)))
{
return;
}
// For 32 bit test enviroments:
provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
using (Image<TPixel> image = provider.GetImage(GolangJpegDecoder))
{
image.DebugSave(provider);
provider.Utility.TestName = DecodeProgressiveJpegOutputName;
image.CompareToReferenceOutput(
this.GetImageComparer(provider),
provider,
appendPixelTypeToFileName: false);
}
provider.Configuration.MemoryAllocator.ReleaseRetainedResources();
}
[Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)]
public void DecodeProgressiveJpeg_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
{
// skipping to avoid OutOfMemoryException on CI
return;
}
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);

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

@ -2,14 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -64,11 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; }
private static GolangJpegDecoder GolangJpegDecoder => new GolangJpegDecoder();
private static PdfJsJpegDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder();
private static JpegDecoder DefaultJpegDecoder => new JpegDecoder();
private static JpegDecoder JpegDecoder => new JpegDecoder();
[Fact]
public void ParseStream_BasicPropertiesAreCorrect1_PdfJs()
@ -76,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
using (var ms = new MemoryStream(bytes))
{
var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder());
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms);
// I don't know why these numbers are different. All I know is that the decoder works
@ -89,9 +81,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg";
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)]
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)]
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, bool useOldDecoder)
[WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)]
public void JpegDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (SkipTest(provider))
@ -102,8 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// For 32 bit test enviroments:
provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling();
IImageDecoder decoder = useOldDecoder ? (IImageDecoder)GolangJpegDecoder : PdfJsJpegDecoder;
using (Image<TPixel> image = provider.GetImage(decoder))
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(provider);
@ -125,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
appendPixelTypeToFileName: false
).SingleOrDefault();
if (report != null && report.TotalNormalizedDifference.HasValue)
if (report?.TotalNormalizedDifference != null)
{
return report.DifferencePercentageString;
}
@ -139,17 +129,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output.WriteLine(provider.SourceFileOrDescription);
provider.Utility.TestName = testName;
using (Image<TPixel> image = provider.GetImage(GolangJpegDecoder))
{
string d = this.GetDifferenceInPercentageString(image, provider);
this.Output.WriteLine($"Difference using ORIGINAL decoder: {d}");
}
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder))
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
string d = this.GetDifferenceInPercentageString(image, provider);
this.Output.WriteLine($"Difference using PDFJS decoder: {d}");
this.Output.WriteLine($"Difference using decoder: {d}");
}
}
@ -175,7 +158,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Image<TPixel> expectedImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false))
using (var pdfJsOriginalResult = Image.Load(pdfJsOriginalResultPath))
using (var pdfJsPortResult = Image.Load(sourceBytes, PdfJsJpegDecoder))
using (var pdfJsPortResult = Image.Load(sourceBytes, JpegDecoder))
{
ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult);
ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult);

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

@ -1,9 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -48,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
where TPixel : struct, IPixel<TPixel>
{
string imageFile = provider.SourceFileOrDescription;
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(imageFile))
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
using (var pp = new JpegImagePostProcessor(Configuration.Default.MemoryAllocator, decoder))
using (var imageFrame = new ImageFrame<Rgba32>(Configuration.Default, decoder.ImageWidth, decoder.ImageHeight))
{
@ -68,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
where TPixel : struct, IPixel<TPixel>
{
string imageFile = provider.SourceFileOrDescription;
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(imageFile))
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
using (var pp = new JpegImagePostProcessor(Configuration.Default.MemoryAllocator, decoder))
using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight))
{

15
tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs

@ -8,8 +8,6 @@ using System.Numerics;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -34,18 +32,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Jpeg444,
};
//[Theory] // Benchmark, enable manually
//[MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg_Original(string fileName)
{
this.DecodeJpegBenchmarkImpl(fileName, new GolangJpegDecoder());
}
// [Theory] // Benchmark, enable manually
// [MemberData(nameof(DecodeJpegData))]
public void DecodeJpeg_PdfJs(string fileName)
public void DecodeJpeg(string fileName)
{
this.DecodeJpegBenchmarkImpl(fileName, new PdfJsJpegDecoder());
this.DecodeJpegBenchmarkImpl(fileName, new JpegDecoder());
}
private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder)
@ -70,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
ExecutionCount,
() =>
{
Image<Rgba32> img = Image.Load<Rgba32>(bytes, decoder);
var img = Image.Load<Rgba32>(bytes, decoder);
},
// ReSharper disable once ExplicitCallerInfoArgument
$"Decode {fileName}");

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

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.Primitives;
@ -30,53 +28,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)]
[InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)]
public void ColorSpace_IsDeducedCorrectlyGolang(string imageFile, object expectedColorSpaceValue)
public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue)
{
var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue;
using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile))
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{
Assert.Equal(expecteColorSpace, decoder.ColorSpace);
}
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)]
[InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)]
public void ColorSpace_IsDeducedCorrectlyPdfJs(string imageFile, object expectedColorSpaceValue)
{
var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue;
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(imageFile))
{
Assert.Equal(expecteColorSpace, decoder.ColorSpace);
}
}
[Fact]
public void ComponentScalingIsCorrect_1ChannelJpegGolang()
{
using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(TestImages.Jpeg.Baseline.Jpeg400))
{
Assert.Equal(1, decoder.ComponentCount);
Assert.Equal(1, decoder.Components.Length);
Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8);
Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU);
var uniform1 = new Size(1, 1);
GolangComponent c0 = decoder.Components[0];
VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1);
}
}
[Fact]
public void ComponentScalingIsCorrect_1ChannelJpegPdfJs()
public void ComponentScalingIsCorrect_1ChannelJpeg()
{
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(TestImages.Jpeg.Baseline.Jpeg400))
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400))
{
Assert.Equal(1, decoder.ComponentCount);
Assert.Equal(1, decoder.Components.Length);
@ -86,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU);
var uniform1 = new Size(1, 1);
PdfJsFrameComponent c0 = decoder.Components[0];
JpegFrameComponent c0 = decoder.Components[0];
VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1);
}
}
@ -98,40 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(TestImages.Jpeg.Baseline.Testorig420)]
[InlineData(TestImages.Jpeg.Baseline.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk)]
public void PrintComponentDataGolang(string imageFile)
{
var sb = new StringBuilder();
using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile))
{
sb.AppendLine(imageFile);
sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}");
GolangComponent c0 = decoder.Components[0];
GolangComponent c1 = decoder.Components[1];
sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}");
sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}");
}
this.Output.WriteLine(sb.ToString());
}
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)]
[InlineData(TestImages.Jpeg.Baseline.Testorig420)]
[InlineData(TestImages.Jpeg.Baseline.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk)]
public void PrintComponentDataPdfJs(string imageFile)
public void PrintComponentData(string imageFile)
{
var sb = new StringBuilder();
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(imageFile))
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{
sb.AppendLine(imageFile);
sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}");
PdfJsFrameComponent c0 = decoder.Components[0];
PdfJsFrameComponent c1 = decoder.Components[1];
JpegFrameComponent c0 = decoder.Components[0];
JpegFrameComponent c1 = decoder.Components[1];
sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}");
sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}");
@ -152,48 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[MemberData(nameof(ComponentVerificationData))]
public void ComponentScalingIsCorrect_MultiChannelJpegGolang(
string imageFile,
int componentCount,
object expectedLumaFactors,
object expectedChromaFactors)
{
var fLuma = (Size)expectedLumaFactors;
var fChroma = (Size)expectedChromaFactors;
using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile))
{
Assert.Equal(componentCount, decoder.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
GolangComponent c0 = decoder.Components[0];
GolangComponent c1 = decoder.Components[1];
GolangComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1);
Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma);
Size divisor = fLuma.DivideBy(fChroma);
Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor);
VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1);
VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor);
VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor);
if (componentCount == 4)
{
GolangComponent c3 = decoder.Components[2];
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1);
}
}
}
[Theory]
[MemberData(nameof(ComponentVerificationData))]
public void ComponentScalingIsCorrect_MultiChannelJpegPdfJs(
public void ComponentScalingIsCorrect_MultiChannelJpeg(
string imageFile,
int componentCount,
object expectedLumaFactors,
@ -202,14 +102,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var fLuma = (Size)expectedLumaFactors;
var fChroma = (Size)expectedChromaFactors;
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(imageFile))
using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{
Assert.Equal(componentCount, decoder.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length);
PdfJsFrameComponent c0 = decoder.Components[0];
PdfJsFrameComponent c1 = decoder.Components[1];
PdfJsFrameComponent c2 = decoder.Components[2];
JpegFrameComponent c0 = decoder.Components[0];
JpegFrameComponent c1 = decoder.Components[1];
JpegFrameComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1);
@ -225,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (componentCount == 4)
{
PdfJsFrameComponent c3 = decoder.Components[2];
JpegFrameComponent c3 = decoder.Components[2];
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1);
}
}

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

@ -4,8 +4,6 @@ using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -42,10 +40,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory(Skip = "Debug only, enable manually!")]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void PdfJsDecoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
public void Decoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder());
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
@ -58,25 +56,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
[Theory(Skip = "Debug only, enable manually!")]
[Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void OriginalDecoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
public void VerifySpectralCorrectness<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
var decoder = new GolangJpegDecoderCore(Configuration.Default, new JpegDecoder());
if (!TestEnvironment.IsWindows)
{
return;
}
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
decoder.ParseStream(ms, false);
decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
VerifyJpeg.SaveSpectralImage(provider, data);
this.VerifySpectralCorrectnessImpl(provider, imageSharpData);
}
}
private void VerifySpectralCorrectness<TPixel>(
private void VerifySpectralCorrectnessImpl<TPixel>(
TestImageProvider<TPixel> provider,
LibJpegTools.SpectralData imageSharpData)
where TPixel : struct, IPixel<TPixel>
@ -119,51 +122,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.True(totalDifference < tolerance);
}
[Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void VerifySpectralCorrectness_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (!TestEnvironment.IsWindows)
{
return;
}
var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder());
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.VerifySpectralCorrectness<TPixel>(provider, imageSharpData);
}
}
[Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void VerifySpectralCorrectness_Golang<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
if (!TestEnvironment.IsWindows)
{
return;
}
var decoder = new GolangJpegDecoderCore(Configuration.Default, new GolangJpegDecoder());
byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes))
{
decoder.ParseStream(ms);
var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder);
this.VerifySpectralCorrectness<TPixel>(provider, imageSharpData);
}
}
}
}

17
tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs

@ -10,8 +10,6 @@ using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using Xunit;
using Xunit.Abstractions;
@ -175,23 +173,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
Assert.False(failed);
}
internal static GolangJpegDecoderCore ParseGolangStream(string testFileName, bool metaDataOnly = false)
internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false)
{
byte[] bytes = TestFile.Create(testFileName).Bytes;
using (var ms = new MemoryStream(bytes))
{
var decoder = new GolangJpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms, metaDataOnly);
return decoder;
}
}
internal static PdfJsJpegDecoderCore ParsePdfJsStream(string testFileName, bool metaDataOnly = false)
{
byte[] bytes = TestFile.Create(testFileName).Bytes;
using (var ms = new MemoryStream(bytes))
{
var decoder = new PdfJsJpegDecoderCore(Configuration.Default, new JpegDecoder());
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
decoder.ParseStream(ms, metaDataOnly);
return decoder;
}

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

@ -1,18 +1,15 @@
using System;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
{
using System;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.Memory;
using SixLabors.Primitives;
internal static partial class LibJpegTools
{
/// <summary>
@ -57,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
this.SpectralBlocks[x, y] = new Block8x8(data);
}
public static ComponentData Load(PdfJsFrameComponent c, int index)
public static ComponentData Load(JpegFrameComponent c, int index)
{
var result = new ComponentData(
c.WidthInBlocks,
@ -77,26 +74,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
return result;
}
public static ComponentData Load(GolangComponent c)
{
var result = new ComponentData(
c.SizeInBlocks.Width,
c.SizeInBlocks.Height,
c.Index
);
for (int y = 0; y < result.HeightInBlocks; y++)
{
for (int x = 0; x < result.WidthInBlocks; x++)
{
short[] data = c.GetBlockReference(x, y).ToArray();
result.MakeBlock(data, y, x);
}
}
return result;
}
public Image<Rgba32> CreateGrayScaleImage()
{
var result = new Image<Rgba32>(this.WidthInBlocks * 8, this.HeightInBlocks * 8);
@ -143,8 +120,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public bool Equals(ComponentData other)
{
if (object.ReferenceEquals(null, other)) return false;
if (object.ReferenceEquals(this, other)) return true;
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks
&& this.WidthInBlocks == other.WidthInBlocks;
//&& this.MinVal == other.MinVal
@ -165,8 +150,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public override bool Equals(object obj)
{
if (Object.ReferenceEquals(null, obj)) return false;
if (Object.ReferenceEquals(this, obj)) return true;
if (obj is null) return false;
if (object.ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return this.Equals((ComponentData)obj);
}
@ -175,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
{
unchecked
{
var hashCode = this.Index;
int hashCode = this.Index;
hashCode = (hashCode * 397) ^ this.HeightInBlocks;
hashCode = (hashCode * 397) ^ this.WidthInBlocks;
hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode();

30
tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs

@ -5,11 +5,9 @@ using System;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
@ -32,17 +30,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
this.Components = components;
}
public static SpectralData LoadFromImageSharpDecoder(PdfJsJpegDecoderCore decoder)
public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder)
{
PdfJsFrameComponent[] srcComponents = decoder.Frame.Components;
LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray();
return new SpectralData(destComponents);
}
public static SpectralData LoadFromImageSharpDecoder(GolangJpegDecoderCore decoder)
{
GolangComponent[] srcComponents = decoder.Components;
JpegFrameComponent[] srcComponents = decoder.Frame.Components;
LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray();
return new SpectralData(destComponents);
@ -108,8 +98,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public bool Equals(SpectralData other)
{
if (object.ReferenceEquals(null, other)) return false;
if (object.ReferenceEquals(this, other)) return true;
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (this.ComponentCount != other.ComponentCount)
{
return false;

Loading…
Cancel
Save