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; using SixLabors.Memory;
// TODO: This could be useful elsewhere. // TODO: This could be useful elsewhere.
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading /// 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 System.Runtime.CompilerServices;
using SixLabors.Memory; using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// The collection of lookup tables used for fast AC entropy scan decoding. /// The collection of lookup tables used for fast AC entropy scan decoding.
@ -35,10 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
/// <summary> /// <summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="PdfJsFrameComponent.ACHuffmanTableId"/> /// Gets a reference to the first element of the AC table indexed by <see cref="JpegFrameComponent.ACHuffmanTableId"/> /// </summary>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetAcTableReference(PdfJsFrameComponent component) public ref short GetAcTableReference(JpegFrameComponent component)
{ {
return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0]; return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
} }
@ -48,11 +47,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
/// <param name="index">The table index.</param> /// <param name="index">The table index.</param>
/// <param name="acHuffmanTables">The collection of AC Huffman tables.</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; const int FastBits = ScanDecoder.FastBits;
Span<short> fastAC = this.tables.GetRowSpan(index); Span<short> fastAC = this.tables.GetRowSpan(index);
ref PdfJsHuffmanTable huffman = ref acHuffmanTables[index]; ref HuffmanTable huffman = ref acHuffmanTables[index];
int i; int i;
for (i = 0; i < (1 << FastBits); 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.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedByteBuffer256 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.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedByteBuffer512 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.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedInt16Buffer257 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.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedInt32Buffer18 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.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct FixedUInt32Buffer18 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 System.Runtime.InteropServices;
using SixLabors.Memory; using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Represents a Huffman Table /// Represents a Huffman Table
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct PdfJsHuffmanTable internal unsafe struct HuffmanTable
{ {
/// <summary> /// <summary>
/// Gets the max code array /// Gets the max code array
@ -40,12 +40,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
public FixedInt16Buffer257 Sizes; public FixedInt16Buffer257 Sizes;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct. /// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param> /// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="count">The code lengths</param> /// <param name="count">The code lengths</param>
/// <param name="values">The huffman values</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; const int Length = 257;
using (IBuffer<short> huffcode = memoryAllocator.Allocate<short>(Length)) 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Defines a 2 pairs of huffman tables /// Defines a 2 pairs of huffman tables.
/// </summary> /// </summary>
internal sealed class PdfJsHuffmanTables internal sealed class HuffmanTables
{ {
private readonly PdfJsHuffmanTable[] tables = new PdfJsHuffmanTable[4]; private readonly HuffmanTable[] tables = new HuffmanTable[4];
/// <summary> /// <summary>
/// Gets or sets the table at the given index. /// Gets or sets the table at the given index.
/// </summary> /// </summary>
/// <param name="index">The index</param> /// <param name="index">The index</param>
/// <returns>The <see cref="PdfJsHuffmanTable"/></returns> /// <returns>The <see cref="HuffmanTable"/></returns>
public ref PdfJsHuffmanTable this[int index] public ref HuffmanTable this[int index]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this.tables[index]; 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; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Represents a jpeg file marker /// Represents a jpeg file marker.
/// </summary> /// </summary>
internal readonly struct PdfJsFileMarker internal readonly struct JpegFileMarker
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsFileMarker"/> struct. /// Initializes a new instance of the <see cref="JpegFileMarker"/> struct.
/// </summary> /// </summary>
/// <param name="marker">The marker</param> /// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</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.Marker = marker;
this.Position = position; this.Position = position;
@ -23,12 +23,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsFileMarker"/> struct. /// Initializes a new instance of the <see cref="JpegFileMarker"/> struct.
/// </summary> /// </summary>
/// <param name="marker">The marker</param> /// <param name="marker">The marker</param>
/// <param name="position">The position within the stream</param> /// <param name="position">The position within the stream</param>
/// <param name="invalid">Whether the current marker is invalid</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.Marker = marker;
this.Position = position; 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; using System;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Represent a single jpeg frame /// Represent a single jpeg frame
/// </summary> /// </summary>
internal sealed class PdfJsFrame : IDisposable internal sealed class JpegFrame : IDisposable
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification /// 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> /// <summary>
/// Gets or sets the frame component collection /// Gets or sets the frame component collection
/// </summary> /// </summary>
public PdfJsFrameComponent[] Components { get; set; } public JpegFrameComponent[] Components { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum horizontal sampling factor /// 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++) for (int i = 0; i < this.ComponentCount; i++)
{ {
PdfJsFrameComponent component = this.Components[i]; JpegFrameComponent component = this.Components[i];
component.Init(); 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.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Represents a single frame component /// Represents a single frame component
/// </summary> /// </summary>
internal class PdfJsFrameComponent : IDisposable, IJpegComponent internal class JpegFrameComponent : IDisposable, IJpegComponent
{ {
private readonly MemoryAllocator memoryAllocator; 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.memoryAllocator = memoryAllocator;
this.Frame = frame; this.Frame = frame;
@ -89,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
public int ACHuffmanTableId { get; set; } public int ACHuffmanTableId { get; set; }
public PdfJsFrame Frame { get; } public JpegFrame Frame { get; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
@ -125,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
else else
{ {
PdfJsFrameComponent c0 = this.Frame.Components[0]; JpegFrameComponent c0 = this.Frame.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); 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. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices; 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> /// <summary>
/// Decodes the Huffman encoded spectral scan. /// Decodes the Huffman encoded spectral scan.
@ -23,13 +21,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// LUT Bias[n] = (-1 << n) + 1 // 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 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 JpegFrame frame;
private readonly PdfJsHuffmanTables dcHuffmanTables; private readonly HuffmanTables dcHuffmanTables;
private readonly PdfJsHuffmanTables acHuffmanTables; private readonly HuffmanTables acHuffmanTables;
private readonly FastACTables fastACTables; private readonly FastACTables fastACTables;
private readonly DoubleBufferedStreamReader stream; private readonly DoubleBufferedStreamReader stream;
private readonly PdfJsFrameComponent[] components; private readonly JpegFrameComponent[] components;
private readonly ZigZag dctZigZag; private readonly ZigZag dctZigZag;
// The restart interval. // The restart interval.
@ -97,9 +95,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="successiveLow">The successive approximation bit low end.</param> /// <param name="successiveLow">The successive approximation bit low end.</param>
public ScanDecoder( public ScanDecoder(
DoubleBufferedStreamReader stream, DoubleBufferedStreamReader stream,
PdfJsFrame frame, JpegFrame frame,
PdfJsHuffmanTables dcHuffmanTables, HuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables, HuffmanTables acHuffmanTables,
FastACTables fastACTables, FastACTables fastACTables,
int componentIndex, int componentIndex,
int componentsLength, int componentsLength,
@ -177,10 +175,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++) 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 HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int h = component.HorizontalSamplingFactor; int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor; int v = component.VerticalSamplingFactor;
@ -231,13 +229,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
private void ParseBaselineDataNonInterleaved() private void ParseBaselineDataNonInterleaved()
{ {
PdfJsFrameComponent component = this.components[this.componentIndex]; JpegFrameComponent component = this.components[this.componentIndex];
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int mcu = 0; int mcu = 0;
@ -296,8 +294,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++) 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 HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int h = component.HorizontalSamplingFactor; int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor; int v = component.VerticalSamplingFactor;
@ -345,13 +343,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
private void ParseProgressiveDataNonInterleaved() private void ParseProgressiveDataNonInterleaved()
{ {
PdfJsFrameComponent component = this.components[this.componentIndex]; JpegFrameComponent component = this.components[this.componentIndex];
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component); ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int mcu = 0; int mcu = 0;
@ -396,11 +394,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
private void DecodeBlockBaseline( private void DecodeBlockBaseline(
PdfJsFrameComponent component, JpegFrameComponent component,
int row, int row,
int col, int col,
ref PdfJsHuffmanTable dcTable, ref HuffmanTable dcTable,
ref PdfJsHuffmanTable acTable, ref HuffmanTable acTable,
ref short fastACRef) ref short fastACRef)
{ {
this.CheckBits(); this.CheckBits();
@ -475,10 +473,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
private void DecodeBlockProgressiveDC( private void DecodeBlockProgressiveDC(
PdfJsFrameComponent component, JpegFrameComponent component,
int row, int row,
int col, int col,
ref PdfJsHuffmanTable dcTable) ref HuffmanTable dcTable)
{ {
if (this.spectralEnd != 0) if (this.spectralEnd != 0)
{ {
@ -511,10 +509,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
private void DecodeBlockProgressiveAC( private void DecodeBlockProgressiveAC(
PdfJsFrameComponent component, JpegFrameComponent component,
int row, int row,
int col, int col,
ref PdfJsHuffmanTable acTable, ref HuffmanTable acTable,
ref short fastACRef) ref short fastACRef)
{ {
if (this.spectralStart == 0) 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; int k;
@ -805,7 +803,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private int DecodeHuffman(ref PdfJsHuffmanTable table) private int DecodeHuffman(ref HuffmanTable table)
{ {
this.CheckBits(); this.CheckBits();
@ -830,7 +828,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(InliningOptions.ColdPath)] [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 // Naive test is to shift the code_buffer down so k bits are
// valid, then test against MaxCode. To speed this up, we've // 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++) for (int i = 0; i < this.components.Length; i++)
{ {
PdfJsFrameComponent c = this.components[i]; JpegFrameComponent c = this.components[i];
c.DcPredictor = 0; 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. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -24,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this)) using (var decoder = new JpegDecoderCore(configuration, this))
{ {
return decoder.Decode<TPixel>(stream); return decoder.Decode<TPixel>(stream);
} }
@ -35,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this)) using (var decoder = new JpegDecoderCore(configuration, this))
{ {
return decoder.Identify(stream); 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.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@ -19,14 +18,14 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
/// Performs the jpeg decoding operation. /// Performs the jpeg decoding operation.
/// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/> /// 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. /// with additional fixes for both performance and common encoding errors.
/// </summary> /// </summary>
internal sealed class PdfJsJpegDecoderCore : IRawJpegData internal sealed class JpegDecoderCore : IRawJpegData
{ {
/// <summary> /// <summary>
/// The only supported precision /// The only supported precision
@ -51,12 +50,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary> /// <summary>
/// The DC HUffman tables /// The DC HUffman tables
/// </summary> /// </summary>
private PdfJsHuffmanTables dcHuffmanTables; private HuffmanTables dcHuffmanTables;
/// <summary> /// <summary>
/// The AC HUffman tables /// The AC HUffman tables
/// </summary> /// </summary>
private PdfJsHuffmanTables acHuffmanTables; private HuffmanTables acHuffmanTables;
/// <summary> /// <summary>
/// The fast AC tables used for entropy decoding /// The fast AC tables used for entropy decoding
@ -84,11 +83,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private AdobeMarker adobe; private AdobeMarker adobe;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsJpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</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.configuration = configuration ?? Configuration.Default;
this.IgnoreMetadata = options.IgnoreMetadata; this.IgnoreMetadata = options.IgnoreMetadata;
@ -97,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary> /// <summary>
/// Gets the frame /// Gets the frame
/// </summary> /// </summary>
public PdfJsFrame Frame { get; private set; } public JpegFrame Frame { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; } public Size ImageSizeInPixels { get; private set; }
@ -146,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
public PdfJsFrameComponent[] Components => this.Frame.Components; public JpegFrameComponent[] Components => this.Frame.Components;
/// <inheritdoc/> /// <inheritdoc/>
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components; IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
@ -159,14 +158,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
/// <param name="marker">The buffer to read file markers to</param> /// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param> /// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns> /// <returns>The <see cref="JpegFileMarker"/></returns>
public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream) public static JpegFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{ {
int value = stream.Read(marker, 0, 2); int value = stream.Read(marker, 0, 2);
if (value == 0) 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) if (marker[0] == JpegConstants.Markers.XFF)
@ -179,16 +178,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int suffix = stream.ReadByte(); int suffix = stream.ReadByte();
if (suffix == -1) if (suffix == -1)
{ {
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
} }
m = suffix; 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> /// <summary>
@ -228,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
this.InputStream.Read(this.markerBuffer, 0, 2); 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) if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
throw new ImageFormatException("Missing SOI marker."); throw new ImageFormatException("Missing SOI marker.");
@ -236,14 +235,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.InputStream.Read(this.markerBuffer, 0, 2); this.InputStream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1]; 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 // Only assign what we need
if (!metadataOnly) if (!metadataOnly)
{ {
this.QuantizationTables = new Block8x8F[4]; this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new PdfJsHuffmanTables(); this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables(); this.acHuffmanTables = new HuffmanTables();
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator); 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="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param> /// <param name="frameMarker">The current frame marker.</param>
/// <param name="metadataOnly">Whether to parse metadata only</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) if (this.Frame != null)
{ {
@ -645,7 +644,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Only 8-Bit precision supported."); throw new ImageFormatException("Only 8-Bit precision supported.");
} }
this.Frame = new PdfJsFrame this.Frame = new JpegFrame
{ {
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, 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 // No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount]; 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(); this.ColorSpace = this.DeduceJpegColorSpace();
for (int i = 0; i < this.Frame.ComponentCount; i++) for (int i = 0; i < this.Frame.ComponentCount; i++)
@ -686,7 +685,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
maxV = v; 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.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id; this.Frame.ComponentIds[i] = component.Id;
@ -794,7 +793,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Unknown component selector"); 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(); int tableSpec = this.InputStream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4; component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15; component.ACHuffmanTableId = tableSpec & 15;
@ -831,9 +830,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="codeLengths">The codelengths</param> /// <param name="codeLengths">The codelengths</param>
/// <param name="values">The values</param> /// <param name="values">The values</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> /// <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.Drawing;
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; 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.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using CoreSize = SixLabors.Primitives.Size; using CoreSize = SixLabors.Primitives.Size;
@ -45,23 +45,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
} }
[Benchmark(Description = "Decode Jpeg - ImageSharp")] [Benchmark(Description = "Decode Jpeg - ImageSharp")]
public CoreSize JpegImageSharpOrig() public CoreSize JpegImageSharp()
{
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()
{ {
using (var memoryStream = new MemoryStream(this.jpegBytes)) 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); return new CoreSize(image.Width, image.Height);
} }

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

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

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

@ -4,16 +4,17 @@
using System; using System;
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortClr))]
public class DoubleBufferedStreams public class DoubleBufferedStreams
{ {
private byte[] buffer = CreateTestBytes(); private readonly byte[] buffer = CreateTestBytes();
private byte[] chunk1 = new byte[2]; private readonly byte[] chunk1 = new byte[2];
private byte[] chunk2 = new byte[2]; private readonly byte[] chunk2 = new byte[2];
private MemoryStream stream1; private MemoryStream stream1;
private MemoryStream stream2; 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")] [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public void JpegSystemDrawing() public void JpegSystemDrawing()
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
this.bmpDrawing.Save(memoryStream, ImageFormat.Jpeg); this.bmpDrawing.Save(memoryStream, ImageFormat.Jpeg);
} }
@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Description = "ImageSharp Jpeg")] [Benchmark(Description = "ImageSharp Jpeg")]
public void JpegCore() public void JpegCore()
{ {
using (MemoryStream memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
this.bmpCore.SaveAsJpeg(memoryStream); this.bmpCore.SaveAsJpeg(memoryStream);
} }

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

@ -3,8 +3,8 @@
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
@ -29,22 +29,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
} }
[Benchmark] [Benchmark]
public IImageInfo IdentifyGolang() public IImageInfo Identify()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new GolangJpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
}
[Benchmark]
public IImageInfo IdentifyPdfJs()
{ {
using (var memoryStream = new MemoryStream(this.jpegBytes)) using (var memoryStream = new MemoryStream(this.jpegBytes))
{ {
var decoder = new PdfJsJpegDecoder(); var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream); 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); private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params( [Params(
TestImages.Jpeg.Baseline.Jpeg420Exif TestImages.Jpeg.Baseline.Jpeg420Exif
//, TestImages.Jpeg.Baseline.Calliphora //, TestImages.Jpeg.Baseline.Calliphora
)] )]
public string TestImage { get; set; } public string TestImage { get; set; }
@ -74,10 +74,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark] [Benchmark]
public void ImageSharp() public void ImageSharp()
{ {
using (var source = Image.Load( var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true });
this.configuration, using (source)
this.sourceBytes,
new JpegDecoder { IgnoreMetadata = true }))
using (var destStream = new MemoryStream(this.destBytes)) using (var destStream = new MemoryStream(this.destBytes))
{ {
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); 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;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.Memory; using SixLabors.Memory;
using Xunit; using Xunit;

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
@ -13,33 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
[Theory] [Theory]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_Orig<TPixel>(TestImageProvider<TPixel> provider) public void DecodeBaselineJpeg<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)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (SkipTest(provider)) if (SkipTest(provider))
@ -48,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return; return;
} }
using (Image<TPixel> image = provider.GetImage(PdfJsJpegDecoder)) using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{ {
image.DebugSave(provider); image.DebugSave(provider);
@ -62,20 +36,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow_Golang<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(GolangJpegDecoder));
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: We need a public ImageDecoderException class in ImageSharp! // 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!")] [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] [Theory]
[MemberData(nameof(MetaDataTestData))] [MemberData(nameof(MetaDataTestData))]
public void MetaDataIsParsedCorrectly_Orig( public void MetaDataIsParsedCorrectly(
bool useIdentify, bool useIdentify,
string imagePath, string imagePath,
int expectedPixelSize, int expectedPixelSize,
@ -60,25 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
TestMetaDataImpl( TestMetaDataImpl(
useIdentify, useIdentify,
GolangJpegDecoder, JpegDecoder,
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,
imagePath, imagePath,
expectedPixelSize, expectedPixelSize,
exifProfilePresent, exifProfilePresent,
@ -216,7 +198,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(true)] [InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) 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 => imageInfo =>
{ {
Assert.Equal(300, imageInfo.MetaData.HorizontalResolution); Assert.Equal(300, imageInfo.MetaData.HorizontalResolution);
@ -229,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(true)] [InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) 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 => imageInfo =>
{ {
Assert.Equal(72, imageInfo.MetaData.HorizontalResolution); 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. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Linq;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] [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> where TPixel : struct, IPixel<TPixel>
{ {
if (SkipTest(provider)) if (SkipTest(provider))
@ -24,41 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return; return;
} }
// Golang decoder is unable to decode these: using (Image<TPixel> image = provider.GetImage(JpegDecoder))
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))
{ {
image.DebugSave(provider); image.DebugSave(provider);

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

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

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

@ -1,9 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // 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.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -48,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
string imageFile = provider.SourceFileOrDescription; 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 pp = new JpegImagePostProcessor(Configuration.Default.MemoryAllocator, decoder))
using (var imageFrame = new ImageFrame<Rgba32>(Configuration.Default, decoder.ImageWidth, decoder.ImageHeight)) 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> where TPixel : struct, IPixel<TPixel>
{ {
string imageFile = provider.SourceFileOrDescription; 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 pp = new JpegImagePostProcessor(Configuration.Default.MemoryAllocator, decoder))
using (var image = new Image<Rgba32>(decoder.ImageWidth, decoder.ImageHeight)) 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;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
@ -34,18 +32,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Jpeg444, 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 // [Theory] // Benchmark, enable manually
// [MemberData(nameof(DecodeJpegData))] // [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) private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder)
@ -70,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
ExecutionCount, ExecutionCount,
() => () =>
{ {
Image<Rgba32> img = Image.Load<Rgba32>(bytes, decoder); var img = Image.Load<Rgba32>(bytes, decoder);
}, },
// ReSharper disable once ExplicitCallerInfoArgument // ReSharper disable once ExplicitCallerInfoArgument
$"Decode {fileName}"); $"Decode {fileName}");

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

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Text; using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; 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.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -30,53 +28,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)] [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)]
[InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)] [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] [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; var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue;
using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile)) using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{ {
Assert.Equal(expecteColorSpace, decoder.ColorSpace); 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] [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.ComponentCount);
Assert.Equal(1, decoder.Components.Length); Assert.Equal(1, decoder.Components.Length);
@ -86,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU); Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU);
var uniform1 = new Size(1, 1); var uniform1 = new Size(1, 1);
PdfJsFrameComponent c0 = decoder.Components[0]; JpegFrameComponent c0 = decoder.Components[0];
VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); 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.Testorig420)]
[InlineData(TestImages.Jpeg.Baseline.Ycck)] [InlineData(TestImages.Jpeg.Baseline.Ycck)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk)] [InlineData(TestImages.Jpeg.Baseline.Cmyk)]
public void PrintComponentDataGolang(string imageFile) public void PrintComponentData(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)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
using (PdfJsJpegDecoderCore decoder = JpegFixture.ParsePdfJsStream(imageFile)) using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile))
{ {
sb.AppendLine(imageFile); sb.AppendLine(imageFile);
sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}"); sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}");
PdfJsFrameComponent c0 = decoder.Components[0]; JpegFrameComponent c0 = decoder.Components[0];
PdfJsFrameComponent c1 = decoder.Components[1]; JpegFrameComponent c1 = decoder.Components[1];
sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}");
sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}");
@ -152,48 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[MemberData(nameof(ComponentVerificationData))] [MemberData(nameof(ComponentVerificationData))]
public void ComponentScalingIsCorrect_MultiChannelJpegGolang( public void ComponentScalingIsCorrect_MultiChannelJpeg(
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(
string imageFile, string imageFile,
int componentCount, int componentCount,
object expectedLumaFactors, object expectedLumaFactors,
@ -202,14 +102,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var fLuma = (Size)expectedLumaFactors; var fLuma = (Size)expectedLumaFactors;
var fChroma = (Size)expectedChromaFactors; 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.ComponentCount);
Assert.Equal(componentCount, decoder.Components.Length); Assert.Equal(componentCount, decoder.Components.Length);
PdfJsFrameComponent c0 = decoder.Components[0]; JpegFrameComponent c0 = decoder.Components[0];
PdfJsFrameComponent c1 = decoder.Components[1]; JpegFrameComponent c1 = decoder.Components[1];
PdfJsFrameComponent c2 = decoder.Components[2]; JpegFrameComponent c2 = decoder.Components[2];
var uniform1 = new Size(1, 1); var uniform1 = new Size(1, 1);
@ -225,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (componentCount == 4) if (componentCount == 4)
{ {
PdfJsFrameComponent c3 = decoder.Components[2]; JpegFrameComponent c3 = decoder.Components[2];
VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); 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 System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -42,10 +40,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory(Skip = "Debug only, enable manually!")] [Theory(Skip = "Debug only, enable manually!")]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] [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> 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; 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)] [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> 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; byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes;
using (var ms = new MemoryStream(sourceBytes)) 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); this.VerifySpectralCorrectnessImpl(provider, imageSharpData);
VerifyJpeg.SaveSpectralImage(provider, data);
} }
} }
private void VerifySpectralCorrectness<TPixel>( private void VerifySpectralCorrectnessImpl<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
LibJpegTools.SpectralData imageSharpData) LibJpegTools.SpectralData imageSharpData)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
@ -119,51 +122,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.True(totalDifference < tolerance); 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;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -175,23 +173,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
Assert.False(failed); 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; byte[] bytes = TestFile.Create(testFileName).Bytes;
using (var ms = new MemoryStream(bytes)) using (var ms = new MemoryStream(bytes))
{ {
var decoder = new GolangJpegDecoderCore(Configuration.Default, new JpegDecoder()); var decoder = new JpegDecoderCore(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());
decoder.ParseStream(ms, metaDataOnly); decoder.ParseStream(ms, metaDataOnly);
return decoder; 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;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils 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 internal static partial class LibJpegTools
{ {
/// <summary> /// <summary>
@ -57,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
this.SpectralBlocks[x, y] = new Block8x8(data); 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( var result = new ComponentData(
c.WidthInBlocks, c.WidthInBlocks,
@ -77,26 +74,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
return result; 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() public Image<Rgba32> CreateGrayScaleImage()
{ {
var result = new Image<Rgba32>(this.WidthInBlocks * 8, this.HeightInBlocks * 8); 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) public bool Equals(ComponentData other)
{ {
if (object.ReferenceEquals(null, other)) return false; if (other is null)
if (object.ReferenceEquals(this, other)) return true; {
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks
&& this.WidthInBlocks == other.WidthInBlocks; && this.WidthInBlocks == other.WidthInBlocks;
//&& this.MinVal == other.MinVal //&& this.MinVal == other.MinVal
@ -165,8 +150,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (Object.ReferenceEquals(null, obj)) return false; if (obj is null) return false;
if (Object.ReferenceEquals(this, obj)) return true; if (object.ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false; if (obj.GetType() != this.GetType()) return false;
return this.Equals((ComponentData)obj); return this.Equals((ComponentData)obj);
} }
@ -175,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
{ {
unchecked unchecked
{ {
var hashCode = this.Index; int hashCode = this.Index;
hashCode = (hashCode * 397) ^ this.HeightInBlocks; hashCode = (hashCode * 397) ^ this.HeightInBlocks;
hashCode = (hashCode * 397) ^ this.WidthInBlocks; hashCode = (hashCode * 397) ^ this.WidthInBlocks;
hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); 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.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
@ -32,17 +30,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
this.Components = components; this.Components = components;
} }
public static SpectralData LoadFromImageSharpDecoder(PdfJsJpegDecoderCore decoder) public static SpectralData LoadFromImageSharpDecoder(JpegDecoderCore decoder)
{ {
PdfJsFrameComponent[] srcComponents = decoder.Frame.Components; JpegFrameComponent[] 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;
LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray();
return new SpectralData(destComponents); return new SpectralData(destComponents);
@ -108,8 +98,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils
public bool Equals(SpectralData other) public bool Equals(SpectralData other)
{ {
if (object.ReferenceEquals(null, other)) return false; if (other is null)
if (object.ReferenceEquals(this, other)) return true; {
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (this.ComponentCount != other.ComponentCount) if (this.ComponentCount != other.ComponentCount)
{ {
return false; return false;

Loading…
Cancel
Save