Browse Source

Merge remote-tracking branch 'upstream/master' into feature/pngExif

af/merge-core
James Jackson-South 8 years ago
parent
commit
eaf3bc42d9
  1. 5
      ImageSharp.sln.DotSettings
  2. 5
      build.ps1
  3. 3
      src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
  4. 6
      src/ImageSharp.Drawing/Processing/BrushApplicator.cs
  5. 5
      src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs
  6. 5
      src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
  7. 3
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  8. 4
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
  9. 6
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
  10. 5
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs
  11. 5
      src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs
  12. 6
      src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs
  13. 2
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  14. 9
      src/ImageSharp/Common/Helpers/ParallelFor.cs
  15. 7
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  16. 5
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  17. 11
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
  18. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs
  19. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs
  20. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs
  21. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs
  22. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs
  23. 11
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  24. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs
  25. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  26. 23
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  27. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs
  28. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  29. 3
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  30. 69
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
  31. 155
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs
  32. 255
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  33. 96
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs
  34. 23
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/EOFException.cs
  35. 253
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs
  36. 29
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponentScan.cs
  37. 27
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangDecoderErrorCode.cs
  38. 260
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangHuffmanTree.cs
  39. 53
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.ComputationData.cs
  40. 51
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.DataPointers.cs
  41. 705
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.cs
  42. 392
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs
  43. 25
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegScanDecoder.md
  44. 15
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/MissingFF00Exception.cs
  45. 42
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoder.cs
  46. 863
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs
  47. 6
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  48. 52
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  49. 42
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs
  50. 9
      src/ImageSharp/Formats/Jpeg/README.md
  51. 3
      src/ImageSharp/IO/DoubleBufferedStreamReader.cs
  52. 105
      src/ImageSharp/Image.WrapMemory.cs
  53. 4
      src/ImageSharp/ImageFrameCollection.cs
  54. 11
      src/ImageSharp/ImageFrame{TPixel}.cs
  55. 15
      src/ImageSharp/Image{TPixel}.cs
  56. 1
      src/ImageSharp/Memory/ArrayPoolMemoryAllocator.Buffer{T}.cs
  57. 2
      src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs
  58. 2
      src/ImageSharp/Memory/BasicArrayBuffer.cs
  59. 4
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  60. 48
      src/ImageSharp/Memory/Buffer2D{T}.cs
  61. 20
      src/ImageSharp/Memory/BufferExtensions.cs
  62. 34
      src/ImageSharp/Memory/ConsumedBuffer.cs
  63. 38
      src/ImageSharp/Memory/IBuffer{T}.cs
  64. 4
      src/ImageSharp/Memory/IManagedByteBuffer.cs
  65. 4
      src/ImageSharp/Memory/ManagedBufferBase.cs
  66. 6
      src/ImageSharp/Memory/MemoryAllocator.cs
  67. 20
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  68. 101
      src/ImageSharp/Memory/MemorySource.cs
  69. 6
      src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs
  70. 1
      src/ImageSharp/PixelFormats/Bgra32.cs
  71. 52
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  72. 12
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  73. 5
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
  74. 5
      src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
  75. 5
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  76. 5
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  77. 4
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  78. 45
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  79. 5
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  80. 7
      src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
  81. 20
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs
  82. 13
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegMultiple.cs
  83. 5
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  84. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
  85. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  86. 19
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  87. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave.cs
  88. 23
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs
  89. 18
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  90. 20
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  91. 19
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  92. 18
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  93. 8
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  94. 4
      tests/ImageSharp.Benchmarks/Samplers/Glow.cs
  95. 2
      tests/ImageSharp.Sandbox46/Program.cs
  96. 3
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  97. 17
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  98. 1
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  99. 3
      tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
  100. 53
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs

5
ImageSharp.sln.DotSettings

@ -343,8 +343,11 @@
<Entry DisplayName="All other members" />
 <Entry DisplayName="All other members" />
</TypePattern>
 </TypePattern>
&lt;/Patterns&gt;</s:String> &lt;/Patterns&gt;</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">// Copyright (c) Six Labors and contributors.&#xD;
// Licensed under the Apache License, Version 2.0.&#xD;
</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DC/@EntryIndexedValue">DC</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DC/@EntryIndexedValue">DC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DCT/@EntryIndexedValue">DCT</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DCT/@EntryIndexedValue">DCT</s:String>

5
build.ps1

@ -8,7 +8,7 @@ $tagRegex = '^v?(\d+\.\d+\.\d+)(-([a-zA-Z]+)\.?(\d*))?$'
# we are running on the build server # we are running on the build server
$isVersionTag = $env:APPVEYOR_REPO_TAG_NAME -match $tagRegex $isVersionTag = $env:APPVEYOR_REPO_TAG_NAME -match $tagRegex
if($isVersionTag){ if($isVersionTag) {
Write-Debug "Building commit tagged with a compatable version number" Write-Debug "Building commit tagged with a compatable version number"
@ -26,7 +26,8 @@ $isVersionTag = $env:APPVEYOR_REPO_TAG_NAME -match $tagRegex
$version = "${version}${padded}" $version = "${version}${padded}"
} }
}else { }
else {
Write-Debug "Untagged" Write-Debug "Untagged"
$lastTag = (git tag --list --sort=-taggerdate) | Out-String $lastTag = (git tag --list --sort=-taggerdate) | Out-String

3
src/ImageSharp.Drawing/Primitives/ShapeRegion.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
using SixLabors.Shapes; using SixLabors.Shapes;
@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Primitives
var start = new PointF(this.Bounds.Left - 1, y); var start = new PointF(this.Bounds.Left - 1, y);
var end = new PointF(this.Bounds.Right + 1, y); var end = new PointF(this.Bounds.Right + 1, y);
using (IBuffer<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length)) using (IMemoryOwner<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length))
{ {
Span<PointF> innerBuffer = tempBuffer.GetSpan(); Span<PointF> innerBuffer = tempBuffer.GetSpan();
int count = this.Shape.FindIntersections(start, end, innerBuffer); int count = this.Shape.FindIntersections(start, end, innerBuffer);

6
src/ImageSharp.Drawing/Processing/BrushApplicator.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -65,8 +67,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length)) using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.GetSpan(); Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan(); Span<TPixel> overlaySpan = overlay.GetSpan();

5
src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -118,8 +119,8 @@ namespace SixLabors.ImageSharp.Processing
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
// Create a span for colors // Create a span for colors
using (IBuffer<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length)) using (IMemoryOwner<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length)) using (IMemoryOwner<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.GetSpan(); Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan(); Span<TPixel> overlaySpan = overlay.GetSpan();

5
src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -153,8 +154,8 @@ namespace SixLabors.ImageSharp.Processing
int patternY = y % this.pattern.Rows; int patternY = y % this.pattern.Rows;
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length)) using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.GetSpan(); Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan(); Span<TPixel> overlaySpan = overlay.GetSpan();

3
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -134,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
using (IBuffer<float> amount = memoryAllocator.Allocate<float>(width)) using (IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(width))
{ {
amount.GetSpan().Fill(this.Opacity); amount.GetSpan().Fill(this.Opacity);

4
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs

@ -2,10 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
startY = 0; startY = 0;
} }
using (IBuffer<float> amount = source.MemoryAllocator.Allocate<float>(width)) using (IMemoryOwner<float> amount = source.MemoryAllocator.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator( using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(
source, source,
sourceRectangle, sourceRectangle,

6
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Utils; using SixLabors.ImageSharp.Utils;
@ -94,8 +96,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options)) using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{ {
int scanlineWidth = maxX - minX; int scanlineWidth = maxX - minX;
using (IBuffer<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections)) using (IMemoryOwner<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections))
using (IBuffer<float> bScanline = source.MemoryAllocator.Allocate<float>(scanlineWidth)) using (IMemoryOwner<float> bScanline = source.MemoryAllocator.Allocate<float>(scanlineWidth))
{ {
bool scanlineDirty = true; bool scanlineDirty = true;
float subpixelFraction = 1f / subpixelCount; float subpixelFraction = 1f / subpixelCount;

5
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.Fonts; using SixLabors.Fonts;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -336,8 +337,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
// take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it. // take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it.
Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean); Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean);
using (IBuffer<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections)) using (IMemoryOwner<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections))
using (IBuffer<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width)) using (IMemoryOwner<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width))
{ {
float subpixelFraction = 1f / subpixelCount; float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount;

5
src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -138,8 +139,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length)) using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.GetSpan(); Span<float> amountSpan = amountBuffer.GetSpan();
Span<TPixel> overlaySpan = overlay.GetSpan(); Span<TPixel> overlaySpan = overlay.GetSpan();

6
src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -65,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Gets the colors. /// Gets the colors.
/// </summary> /// </summary>
protected IBuffer<TPixel> Colors { get; } protected IMemoryOwner<TPixel> Colors { get; }
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
@ -96,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing
} }
else else
{ {
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.GetSpan(); Span<float> amountSpan = amountBuffer.GetSpan();

2
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Advanced
internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source) internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.PixelBuffer.Buffer.Memory; return source.PixelBuffer.MemorySource.Memory;
} }
/// <summary> /// <summary>

9
src/ImageSharp/Common/Helpers/ParallelFor.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.Memory; using SixLabors.Memory;
@ -32,23 +33,23 @@ namespace SixLabors.ImageSharp
int toExclusive, int toExclusive,
Configuration configuration, Configuration configuration,
int bufferLength, int bufferLength,
Action<int, IBuffer<T>> body) Action<int, IMemoryOwner<T>> body)
where T : struct where T : struct
{ {
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
ParallelOptions parallelOptions = configuration.ParallelOptions; ParallelOptions parallelOptions = configuration.ParallelOptions;
IBuffer<T> InitBuffer() IMemoryOwner<T> InitBuffer()
{ {
return memoryAllocator.Allocate<T>(bufferLength); return memoryAllocator.Allocate<T>(bufferLength);
} }
void CleanUpBuffer(IBuffer<T> buffer) void CleanUpBuffer(IMemoryOwner<T> buffer)
{ {
buffer.Dispose(); buffer.Dispose();
} }
IBuffer<T> BodyFunc(int i, ParallelLoopState state, IBuffer<T> buffer) IMemoryOwner<T> BodyFunc(int i, ParallelLoopState state, IMemoryOwner<T> buffer)
{ {
body(i, buffer); body(i, buffer);
return buffer; return buffer;

7
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -32,17 +33,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The prefix buffer. /// The prefix buffer.
/// </summary> /// </summary>
private readonly IBuffer<int> prefix; private readonly IMemoryOwner<int> prefix;
/// <summary> /// <summary>
/// The suffix buffer. /// The suffix buffer.
/// </summary> /// </summary>
private readonly IBuffer<int> suffix; private readonly IMemoryOwner<int> suffix;
/// <summary> /// <summary>
/// The pixel stack buffer. /// The pixel stack buffer.
/// </summary> /// </summary>
private readonly IBuffer<int> pixelStack; private readonly IMemoryOwner<int> pixelStack;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class /// Initializes a new instance of the <see cref="LzwDecoder"/> class

5
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -66,12 +67,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The hash table. /// The hash table.
/// </summary> /// </summary>
private readonly IBuffer<int> hashTable; private readonly IMemoryOwner<int> hashTable;
/// <summary> /// <summary>
/// The code table. /// The code table.
/// </summary> /// </summary>
private readonly IBuffer<int> codeTable; private readonly IMemoryOwner<int> codeTable;
/// <summary> /// <summary>
/// Define the storage for the packet accumulator. /// Define the storage for the packet accumulator.

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="JpegComponent.ACHuffmanTableId"/> /// </summary>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetAcTableReference(PdfJsFrameComponent component) public ref short GetAcTableReference(JpegComponent 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

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

@ -2,17 +2,18 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; 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,15 +41,15 @@ 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 (IMemoryOwner<short> huffcode = memoryAllocator.Allocate<short>(Length))
{ {
ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan()); ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan());

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];

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

@ -9,7 +9,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Encapsulates the implementation of processing "raw" <see cref="IBuffer{T}"/>-s into Jpeg image channels. /// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal struct JpegBlockPostProcessor internal struct JpegBlockPostProcessor

23
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.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 JpegComponent : 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 JpegComponent(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]; JpegComponent c0 = this.Frame.Components[0];
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
} }
@ -140,16 +138,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetBlockBufferOffset(int row, int col) public ref short GetBlockDataReference(int column, int row)
{ {
return 64 * (((this.WidthInBlocks + 1) * row) + col); ref Block8x8 blockRef = ref this.GetBlockReference(column, row);
}
// TODO: we need consistence in (row, col) VS (col, row) ordering
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetBlockDataReference(int row, int col)
{
ref Block8x8 blockRef = ref this.GetBlockReference(col, row);
return ref Unsafe.As<Block8x8, short>(ref blockRef); return ref Unsafe.As<Block8x8, short>(ref blockRef);
} }
} }

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

@ -3,32 +3,30 @@
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, position, false)
{ {
this.Marker = marker;
this.Position = position;
this.Invalid = false;
} }
/// <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 JpegComponent[] 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]; JpegComponent component = this.Components[i];
component.Init(); component.Init();
} }
} }

3
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -37,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary> /// <summary>
/// Temporal buffer to store a row of colors. /// Temporal buffer to store a row of colors.
/// </summary> /// </summary>
private readonly IBuffer<Vector4> rgbaBuffer; private readonly IMemoryOwner<Vector4> rgbaBuffer;
/// <summary> /// <summary>
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>. /// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.

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

@ -2,10 +2,9 @@
// 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.IO;
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 +22,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 JpegComponent[] components;
private readonly ZigZag dctZigZag; private readonly ZigZag dctZigZag;
// The restart interval. // The restart interval.
@ -97,9 +96,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 +176,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]; JpegComponent 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 +230,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
private void ParseBaselineDataNonInterleaved() private void ParseBaselineDataNonInterleaved()
{ {
PdfJsFrameComponent component = this.components[this.componentIndex]; JpegComponent 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 +295,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]; JpegComponent 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 +344,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary> /// </summary>
private void ParseProgressiveDataNonInterleaved() private void ParseProgressiveDataNonInterleaved()
{ {
PdfJsFrameComponent component = this.components[this.componentIndex]; JpegComponent 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 +395,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
private void DecodeBlockBaseline( private void DecodeBlockBaseline(
PdfJsFrameComponent component, JpegComponent 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();
@ -411,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
JpegThrowHelper.ThrowBadHuffmanCode(); JpegThrowHelper.ThrowBadHuffmanCode();
} }
ref short blockDataRef = ref component.GetBlockDataReference(row, col); ref short blockDataRef = ref component.GetBlockDataReference(col, row);
int diff = t != 0 ? this.ExtendReceive(t) : 0; int diff = t != 0 ? this.ExtendReceive(t) : 0;
int dc = component.DcPredictor + diff; int dc = component.DcPredictor + diff;
@ -475,10 +474,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
private void DecodeBlockProgressiveDC( private void DecodeBlockProgressiveDC(
PdfJsFrameComponent component, JpegComponent component,
int row, int row,
int col, int col,
ref PdfJsHuffmanTable dcTable) ref HuffmanTable dcTable)
{ {
if (this.spectralEnd != 0) if (this.spectralEnd != 0)
{ {
@ -487,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.CheckBits(); this.CheckBits();
ref short blockDataRef = ref component.GetBlockDataReference(row, col); ref short blockDataRef = ref component.GetBlockDataReference(col, row);
if (this.successiveHigh == 0) if (this.successiveHigh == 0)
{ {
@ -511,10 +510,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
private void DecodeBlockProgressiveAC( private void DecodeBlockProgressiveAC(
PdfJsFrameComponent component, JpegComponent 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)
@ -522,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC."); JpegThrowHelper.ThrowImageFormatException("Can't merge DC and AC.");
} }
ref short blockDataRef = ref component.GetBlockDataReference(row, col); ref short blockDataRef = ref component.GetBlockDataReference(col, row);
if (this.successiveHigh == 0) if (this.successiveHigh == 0)
{ {
@ -603,7 +602,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 +804,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 +829,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 +940,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]; JpegComponent 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);
}
}
}
}

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

@ -1,863 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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>
/// Contains exif data
/// </summary>
private byte[] exifData;
/// <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.InitExifProfile();
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>
/// Initializes the exif profile.
/// </summary>
private void InitExifProfile()
{
if (this.isExif)
{
this.MetaData.ExifProfile = new ExifProfile(this.exifData);
}
}
/// <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;
if (this.exifData == null)
{
// the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific
this.exifData = profile.Skip(6).ToArray();
}
else
{
// if the exif information exceeds 64K, it will be split over multiple APP1 markers
this.ExtendExif(profile.Skip(6).ToArray());
}
}
}
/// <summary>
/// Extends the exif profile with additional data.
/// </summary>
/// <param name="bytes">The array containing addition profile data.</param>
private void ExtendExif(byte[] bytes)
{
int currentLength = this.exifData.Length;
Array.Resize(ref this.exifData, currentLength + bytes.Length);
Buffer.BlockCopy(bytes, 0, this.exifData, currentLength, bytes.Length);
}
/// <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);
} }

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

@ -11,7 +11,7 @@ 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.IO;
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;
@ -20,14 +20,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
@ -52,12 +52,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
@ -90,11 +90,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;
@ -103,7 +103,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; }
@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
public PdfJsFrameComponent[] Components => this.Frame.Components; public JpegComponent[] Components => this.Frame.Components;
/// <inheritdoc/> /// <inheritdoc/>
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components; IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
@ -165,14 +165,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)
@ -185,16 +185,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>
@ -236,7 +236,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.");
@ -244,14 +244,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);
} }
@ -670,7 +670,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)
{ {
@ -685,7 +685,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,
@ -707,7 +707,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 JpegComponent[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++)
@ -726,7 +726,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 JpegComponent(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;
@ -834,7 +834,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 JpegComponent 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;
@ -871,9 +871,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);
}
}
}
}

9
src/ImageSharp/Formats/Jpeg/README.md

@ -1,3 +1,8 @@
Encoder/Decoder adapted and extended from: Encoder adapted and extended from:
https://golang.org/src/image/jpeg/
https://golang.org/src/image/jpeg/ Decoder orchestration code is based on:
https://github.com/mozilla/pdf.js
Huffmann decoder is based on:
https://github.com/rds1983/StbSharp

3
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs → src/ImageSharp/IO/DoubleBufferedStreamReader.cs

@ -7,8 +7,7 @@ using System.Runtime.CompilerServices;
using SixLabors.Memory; using SixLabors.Memory;
// TODO: This could be useful elsewhere. namespace SixLabors.ImageSharp.IO
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
/// <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

105
src/ImageSharp/Image.WrapMemory.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -13,8 +15,6 @@ namespace SixLabors.ImageSharp
/// </content> /// </content>
public static partial class Image public static partial class Image
{ {
// TODO: This is a WIP API, should be public when finished.
/// <summary> /// <summary>
/// Wraps an existing contigous memory area of 'width'x'height' pixels, /// Wraps an existing contigous memory area of 'width'x'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance. /// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp
/// <param name="height">The height of the memory image</param> /// <param name="height">The height of the memory image</param>
/// <param name="metaData">The <see cref="ImageMetaData"/></param> /// <param name="metaData">The <see cref="ImageMetaData"/></param>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns> /// <returns>An <see cref="Image{TPixel}"/> instance</returns>
internal static Image<TPixel> WrapMemory<TPixel>( public static Image<TPixel> WrapMemory<TPixel>(
Configuration config, Configuration config,
Memory<TPixel> pixelMemory, Memory<TPixel> pixelMemory,
int width, int width,
@ -34,26 +34,117 @@ namespace SixLabors.ImageSharp
ImageMetaData metaData) ImageMetaData metaData)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var buffer = new ConsumedBuffer<TPixel>(pixelMemory); var memorySource = new MemorySource<TPixel>(pixelMemory);
return new Image<TPixel>(config, buffer, width, height, metaData); return new Image<TPixel>(config, memorySource, width, height, metaData);
}
/// <summary>
/// Wraps an existing contigous memory area of 'width'x'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="config">The <see cref="Configuration"/></param>
/// <param name="pixelMemory">The pixel memory</param>
/// <param name="width">The width of the memory image</param>
/// <param name="height">The height of the memory image</param>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
Memory<TPixel> pixelMemory,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
{
return WrapMemory(config, pixelMemory, width, height, new ImageMetaData());
} }
/// <summary> /// <summary>
/// Wraps an existing contigous memory area of 'width'x'height' pixels, /// Wraps an existing contigous memory area of 'width'x'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance. /// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam> /// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="pixelMemory">The pixel memory</param> /// <param name="pixelMemory">The pixel memory</param>
/// <param name="width">The width of the memory image</param> /// <param name="width">The width of the memory image</param>
/// <param name="height">The height of the memory image</param> /// <param name="height">The height of the memory image</param>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns> /// <returns>An <see cref="Image{TPixel}"/> instance</returns>
internal static Image<TPixel> WrapMemory<TPixel>( public static Image<TPixel> WrapMemory<TPixel>(
Memory<TPixel> pixelMemory, Memory<TPixel> pixelMemory,
int width, int width,
int height) int height)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return WrapMemory(Configuration.Default, pixelMemory, width, height, new ImageMetaData()); return WrapMemory(Configuration.Default, pixelMemory, width, height);
}
/// <summary>
/// Wraps an existing contigous memory area of 'width'x'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transfered to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
/// It will be disposed together with the result image.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="config">The <see cref="Configuration"/></param>
/// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transfered to the image</param>
/// <param name="width">The width of the memory image</param>
/// <param name="height">The height of the memory image</param>
/// <param name="metaData">The <see cref="ImageMetaData"/></param>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height,
ImageMetaData metaData)
where TPixel : struct, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemoryOwner, false);
return new Image<TPixel>(config, memorySource, width, height, metaData);
}
/// <summary>
/// Wraps an existing contigous memory area of 'width'x'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transfered to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
/// It will be disposed together with the result image.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="config">The <see cref="Configuration"/></param>
/// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transfered to the image</param>
/// <param name="width">The width of the memory image</param>
/// <param name="height">The height of the memory image</param>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
{
return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetaData());
}
/// <summary>
/// Wraps an existing contigous memory area of 'width'x'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transfered to the new <see cref="Image{TPixel}"/> instance,
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>.
/// It will be disposed together with the result image.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transfered to the image</param>
/// <param name="width">The width of the memory image</param>
/// <param name="height">The height of the memory image</param>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>(
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
{
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
} }
} }
} }

4
src/ImageSharp/ImageFrameCollection.cs

@ -30,14 +30,14 @@ namespace SixLabors.ImageSharp
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor)); this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, backgroundColor));
} }
internal ImageFrameCollection(Image<TPixel> parent, int width, int height, IBuffer<TPixel> consumedBuffer) internal ImageFrameCollection(Image<TPixel> parent, int width, int height, MemorySource<TPixel> memorySource)
{ {
Guard.NotNull(parent, nameof(parent)); Guard.NotNull(parent, nameof(parent));
this.parent = parent; this.parent = parent;
// Frames are already cloned within the caller // Frames are already cloned within the caller
this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, consumedBuffer)); this.frames.Add(new ImageFrame<TPixel>(parent.GetConfiguration(), width, height, memorySource));
} }
internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames) internal ImageFrameCollection(Image<TPixel> parent, IEnumerable<ImageFrame<TPixel>> frames)

11
src/ImageSharp/ImageFrame{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -94,8 +95,8 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer. /// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary> /// </summary>
internal ImageFrame(Configuration configuration, int width, int height, IBuffer<TPixel> consumedBuffer) internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
: this(configuration, width, height, consumedBuffer, new ImageFrameMetaData()) : this(configuration, width, height, memorySource, new ImageFrameMetaData())
{ {
} }
@ -106,7 +107,7 @@ namespace SixLabors.ImageSharp
Configuration configuration, Configuration configuration,
int width, int width,
int height, int height,
IBuffer<TPixel> consumedBuffer, MemorySource<TPixel> memorySource,
ImageFrameMetaData metaData) ImageFrameMetaData metaData)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
@ -116,7 +117,7 @@ namespace SixLabors.ImageSharp
this.configuration = configuration; this.configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator; this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = new Buffer2D<TPixel>(consumedBuffer, width, height); this.PixelBuffer = new Buffer2D<TPixel>(memorySource, width, height);
this.MetaData = metaData; this.MetaData = metaData;
} }
@ -272,7 +273,7 @@ namespace SixLabors.ImageSharp
this.Height, this.Height,
this.configuration, this.configuration,
this.Width, this.Width,
(int y, IBuffer<Vector4> tempRowBuffer) => (int y, IMemoryOwner<Vector4> tempRowBuffer) =>
{ {
Span<TPixel> sourceRow = this.GetPixelRowSpan(y); Span<TPixel> sourceRow = this.GetPixelRowSpan(y);
Span<TPixel2> targetRow = target.GetPixelRowSpan(y); Span<TPixel2> targetRow = target.GetPixelRowSpan(y);

15
src/ImageSharp/Image{TPixel}.cs

@ -84,23 +84,14 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// consuming an external buffer instance. /// wrapping an external <see cref="MemorySource{T}"/>
/// </summary> /// </summary>
internal Image(Configuration configuration, IBuffer<TPixel> consumedBuffer, int width, int height) internal Image(Configuration configuration, MemorySource<TPixel> memorySource, int width, int height, ImageMetaData metadata)
: this(configuration, consumedBuffer, width, height, new ImageMetaData())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// consuming an external buffer instance.
/// </summary>
internal Image(Configuration configuration, IBuffer<TPixel> consumedBuffer, int width, int height, ImageMetaData metadata)
{ {
this.configuration = configuration; this.configuration = configuration;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8); this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata; this.MetaData = metadata;
this.frames = new ImageFrameCollection<TPixel>(this, width, height, consumedBuffer); this.frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
} }
/// <summary> /// <summary>

1
src/ImageSharp/Memory/ArrayPoolMemoryAllocator.Buffer{T}.cs

@ -14,7 +14,6 @@ namespace SixLabors.Memory
{ {
/// <summary> /// <summary>
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>. /// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
/// In this implementation <see cref="IBuffer{T}.Memory"/> is owned.
/// </summary> /// </summary>
private class Buffer<T> : ManagedBufferBase<T> private class Buffer<T> : ManagedBufferBase<T>
where T : struct where T : struct

2
src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs

@ -89,7 +89,7 @@ namespace SixLabors.Memory
} }
/// <inheritdoc /> /// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options) internal override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{ {
int itemSizeBytes = Unsafe.SizeOf<T>(); int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes; int bufferSizeInBytes = length * itemSizeBytes;

2
src/ImageSharp/Memory/BasicArrayBuffer.cs

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
namespace SixLabors.Memory namespace SixLabors.Memory
{ {
/// <summary> /// <summary>
/// Wraps an array as an <see cref="IBuffer{T}"/> instance. In this implementation <see cref="IBuffer{T}.Memory"/> is owned. /// Wraps an array as an <see cref="IManagedByteBuffer"/> instance.
/// </summary> /// </summary>
internal class BasicArrayBuffer<T> : ManagedBufferBase<T> internal class BasicArrayBuffer<T> : ManagedBufferBase<T>
where T : struct where T : struct

4
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -18,7 +18,7 @@ namespace SixLabors.Memory
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer) internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
where T : struct where T : struct
{ {
return buffer.Buffer.GetSpan(); return buffer.MemorySource.GetSpan();
} }
/// <summary> /// <summary>
@ -61,7 +61,7 @@ namespace SixLabors.Memory
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y) public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct where T : struct
{ {
return buffer.Buffer.Memory.Slice(y * buffer.Width, buffer.Width); return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
} }
/// <summary> /// <summary>

48
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -15,15 +16,17 @@ namespace SixLabors.Memory
internal sealed class Buffer2D<T> : IDisposable internal sealed class Buffer2D<T> : IDisposable
where T : struct where T : struct
{ {
private MemorySource<T> memorySource;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class. /// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary> /// </summary>
/// <param name="wrappedBuffer">The buffer to wrap</param> /// <param name="memorySource">The buffer to wrap</param>
/// <param name="width">The number of elements in a row</param> /// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param> /// <param name="height">The number of rows</param>
public Buffer2D(IBuffer<T> wrappedBuffer, int width, int height) public Buffer2D(MemorySource<T> memorySource, int width, int height)
{ {
this.Buffer = wrappedBuffer; this.memorySource = memorySource;
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
} }
@ -39,13 +42,13 @@ namespace SixLabors.Memory
public int Height { get; private set; } public int Height { get; private set; }
/// <summary> /// <summary>
/// Gets the backing <see cref="IBuffer{T}"/> /// Gets the backing <see cref="MemorySource{T}"/>
/// </summary> /// </summary>
public IBuffer<T> Buffer { get; private set; } public MemorySource<T> MemorySource => this.memorySource;
public Memory<T> Memory => this.Buffer.Memory; public Memory<T> Memory => this.MemorySource.Memory;
public Span<T> Span => this.Buffer.GetSpan(); public Span<T> Span => this.Memory.Span;
/// <summary> /// <summary>
/// Gets a reference to the element at the specified position. /// Gets a reference to the element at the specified position.
@ -60,7 +63,7 @@ namespace SixLabors.Memory
{ {
ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Buffer.GetSpan(); Span<T> span = this.Span;
return ref span[(this.Width * y) + x]; return ref span[(this.Width * y) + x];
} }
} }
@ -70,7 +73,7 @@ namespace SixLabors.Memory
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
this.Buffer?.Dispose(); this.MemorySource.Dispose();
} }
/// <summary> /// <summary>
@ -79,36 +82,15 @@ namespace SixLabors.Memory
/// </summary> /// </summary>
public static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source) public static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{ {
if (source.Buffer.IsMemoryOwner && destination.Buffer.IsMemoryOwner) MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
{ SwapDimensionData(destination, source);
SwapContents(destination, source);
}
else
{
if (destination.Size() != source.Size())
{
throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!");
}
source.Span.CopyTo(destination.Span);
}
} }
/// <summary> private static void SwapDimensionData(Buffer2D<T> a, Buffer2D<T> b)
/// Swap the contents (<see cref="Buffer"/>, <see cref="Width"/>, <see cref="Height"/>) of the two buffers.
/// Useful to transfer the contents of a temporary <see cref="Buffer2D{T}"/> to a persistent <see cref="SixLabors.ImageSharp.ImageFrame{T}.PixelBuffer"/>
/// </summary>
/// <param name="a">The first buffer</param>
/// <param name="b">The second buffer</param>
private static void SwapContents(Buffer2D<T> a, Buffer2D<T> b)
{ {
Size aSize = a.Size(); Size aSize = a.Size();
Size bSize = b.Size(); Size bSize = b.Size();
IBuffer<T> temp = a.Buffer;
a.Buffer = b.Buffer;
b.Buffer = temp;
b.Width = aSize.Width; b.Width = aSize.Width;
b.Height = aSize.Height; b.Height = aSize.Height;

20
src/ImageSharp/Memory/BufferExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -11,8 +12,12 @@ namespace SixLabors.Memory
internal static class BufferExtensions internal static class BufferExtensions
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Length<T>(this IBuffer<T> buffer) public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer)
where T : struct => buffer.GetSpan().Length; => buffer.Memory.Span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Length<T>(this IMemoryOwner<T> buffer)
=> buffer.GetSpan().Length;
/// <summary> /// <summary>
/// Gets a <see cref="Span{T}"/> to an offseted position inside the buffer. /// Gets a <see cref="Span{T}"/> to an offseted position inside the buffer.
@ -21,8 +26,7 @@ namespace SixLabors.Memory
/// <param name="start">The start</param> /// <param name="start">The start</param>
/// <returns>The <see cref="Span{T}"/></returns> /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start) public static Span<T> Slice<T>(this IMemoryOwner<T> buffer, int start)
where T : struct
{ {
return buffer.GetSpan().Slice(start); return buffer.GetSpan().Slice(start);
} }
@ -35,8 +39,7 @@ namespace SixLabors.Memory
/// <param name="length">The length of the slice</param> /// <param name="length">The length of the slice</param>
/// <returns>The <see cref="Span{T}"/></returns> /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start, int length) public static Span<T> Slice<T>(this IMemoryOwner<T> buffer, int start, int length)
where T : struct
{ {
return buffer.GetSpan().Slice(start, length); return buffer.GetSpan().Slice(start, length);
} }
@ -46,13 +49,12 @@ namespace SixLabors.Memory
/// </summary> /// </summary>
/// <param name="buffer">The buffer</param> /// <param name="buffer">The buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clear<T>(this IBuffer<T> buffer) public static void Clear<T>(this IMemoryOwner<T> buffer)
where T : struct
{ {
buffer.GetSpan().Clear(); buffer.GetSpan().Clear();
} }
public static ref T GetReference<T>(this IBuffer<T> buffer) public static ref T GetReference<T>(this IMemoryOwner<T> buffer)
where T : struct => where T : struct =>
ref MemoryMarshal.GetReference(buffer.GetSpan()); ref MemoryMarshal.GetReference(buffer.GetSpan());

34
src/ImageSharp/Memory/ConsumedBuffer.cs

@ -1,34 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.Memory
{
/// <summary>
/// A buffer implementation that consumes an existing <see cref="Memory{T}"/> instance.
/// The ownership of the memory remains external.
/// </summary>
/// <typeparam name="T">The value type</typeparam>
internal sealed class ConsumedBuffer<T> : IBuffer<T>
where T : struct
{
public ConsumedBuffer(Memory<T> memory)
{
this.Memory = memory;
}
public Memory<T> Memory { get; }
public bool IsMemoryOwner => false;
public Span<T> GetSpan()
{
return this.Memory.Span;
}
public void Dispose()
{
}
}
}

38
src/ImageSharp/Memory/IBuffer{T}.cs

@ -1,38 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.Memory
{
/// <summary>
/// Represents a contigous memory buffer of value-type items.
/// Depending on it's implementation, an <see cref="IBuffer{T}"/> can (1) OWN or (2) CONSUME the <see cref="Memory{T}"/> instance it wraps.
/// For a deeper understanding of the owner/consumer model, read the following docs: <br/>
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
/// TODO: We need more SOC here! For owned buffers we should use <see cref="IMemoryOwner{T}"/>.
/// For the consumption case we should not use buffers at all. We need to refactor Buffer2D{T} for this.
/// </summary>
/// <typeparam name="T">The value type</typeparam>
internal interface IBuffer<T> : IDisposable
where T : struct
{
/// <summary>
/// Gets the <see cref="Memory{T}"/> ownerd/consumed by this buffer.
/// </summary>
Memory<T> Memory { get; }
/// <summary>
/// Gets a value indicating whether this instance is owning the <see cref="Memory"/>.
/// </summary>
bool IsMemoryOwner { get; }
/// <summary>
/// Gets the span to the memory "promised" by this buffer when it's OWNED (1).
/// Gets `this.Memory.Span` when the buffer CONSUMED (2).
/// </summary>
/// <returns>The <see cref="Span{T}"/></returns>
Span<T> GetSpan();
}
}

4
src/ImageSharp/Memory/IManagedByteBuffer.cs

@ -1,12 +1,14 @@
// 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.Buffers;
namespace SixLabors.Memory namespace SixLabors.Memory
{ {
/// <summary> /// <summary>
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
/// </summary> /// </summary>
internal interface IManagedByteBuffer : IBuffer<byte> internal interface IManagedByteBuffer : IMemoryOwner<byte>
{ {
/// <summary> /// <summary>
/// Gets the managed array backing this buffer instance. /// Gets the managed array backing this buffer instance.

4
src/ImageSharp/Memory/ManagedBufferBase.cs

@ -7,9 +7,9 @@ using System.Runtime.InteropServices;
namespace SixLabors.Memory namespace SixLabors.Memory
{ {
/// <summary> /// <summary>
/// Provides a base class for <see cref="IBuffer{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption. /// Provides a base class for <see cref="IMemoryOwner{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
/// </summary> /// </summary>
internal abstract class ManagedBufferBase<T> : MemoryManager<T>, IBuffer<T> internal abstract class ManagedBufferBase<T> : MemoryManager<T>
where T : struct where T : struct
{ {
private GCHandle pinHandle; private GCHandle pinHandle;

6
src/ImageSharp/Memory/MemoryAllocator.cs

@ -1,6 +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 System.Buffers;
namespace SixLabors.Memory namespace SixLabors.Memory
{ {
/// <summary> /// <summary>
@ -9,13 +11,13 @@ namespace SixLabors.Memory
public abstract class MemoryAllocator public abstract class MemoryAllocator
{ {
/// <summary> /// <summary>
/// Allocates an <see cref="IBuffer{T}"/> of size <paramref name="length"/>. /// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="System.Memory{T}"/> of length <paramref name="length"/>.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the data stored in the buffer</typeparam> /// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="length">Size of the buffer to allocate</param> /// <param name="length">Size of the buffer to allocate</param>
/// <param name="options">The allocation options.</param> /// <param name="options">The allocation options.</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns> /// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
internal abstract IBuffer<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) internal abstract IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
where T : struct; where T : struct;
/// <summary> /// <summary>

20
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -1,4 +1,6 @@
using SixLabors.Primitives; using System.Buffers;
using SixLabors.Primitives;
namespace SixLabors.Memory namespace SixLabors.Memory
{ {
@ -7,15 +9,23 @@ namespace SixLabors.Memory
/// </summary> /// </summary>
internal static class MemoryAllocatorExtensions internal static class MemoryAllocatorExtensions
{ {
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height, AllocationOptions options = AllocationOptions.None) public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
AllocationOptions options = AllocationOptions.None)
where T : struct where T : struct
{ {
IBuffer<T> buffer = memoryAllocator.Allocate<T>(width * height, options); IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>(width * height, options);
var memorySource = new MemorySource<T>(buffer, true);
return new Buffer2D<T>(buffer, width, height); return new Buffer2D<T>(memorySource, width, height);
} }
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, Size size, AllocationOptions options = AllocationOptions.None) public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
AllocationOptions options = AllocationOptions.None)
where T : struct => where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, options); Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);

101
src/ImageSharp/Memory/MemorySource.cs

@ -0,0 +1,101 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.Memory
{
/// <summary>
/// Holds a <see cref="System.Memory{T}"/> that is either OWNED or CONSUMED.
/// When the memory is being owned, the <see cref="IMemoryOwner{T}"/> instance is also known.
/// Implements content transfer logic in <see cref="SwapOrCopyContent"/> that depends on the ownership status.
/// This is needed to transfer the contents of a temporary <see cref="Buffer2D{T}"/>
/// to a persistent <see cref="SixLabors.ImageSharp.ImageFrame{T}.PixelBuffer"/> without copying the buffer.
/// </summary>
/// <remarks>
/// For a deeper understanding of the owner/consumer model, check out the following docs: <br/>
/// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6
/// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T
/// </remarks>
internal struct MemorySource<T> : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="MemorySource{T}"/> struct
/// by wrapping an existing <see cref="IMemoryOwner{T}"/>.
/// </summary>
/// <param name="memoryOwner">The <see cref="IMemoryOwner{T}"/> to wrap</param>
/// <param name="isInternalMemorySource">
/// A value indicating whether <paramref name="memoryOwner"/> is an internal memory source managed by ImageSharp.
/// Eg. allocated by a <see cref="MemoryAllocator"/>.
/// </param>
public MemorySource(IMemoryOwner<T> memoryOwner, bool isInternalMemorySource)
{
this.MemoryOwner = memoryOwner;
this.Memory = memoryOwner.Memory;
this.HasSwappableContents = isInternalMemorySource;
}
public MemorySource(Memory<T> memory)
{
this.Memory = memory;
this.MemoryOwner = null;
this.HasSwappableContents = false;
}
public IMemoryOwner<T> MemoryOwner { get; private set; }
public Memory<T> Memory { get; private set; }
/// <summary>
/// Gets a value indicating whether we are allowed to swap the contents of this buffer
/// with an other <see cref="MemorySource{T}"/> instance.
/// The value is true only and only if <see cref="MemoryOwner"/> is present,
/// and it's coming from an internal source managed by ImageSharp (<see cref="MemoryAllocator"/>).
/// </summary>
public bool HasSwappableContents { get; }
public Span<T> GetSpan() => this.Memory.Span;
public void Clear() => this.Memory.Span.Clear();
/// <summary>
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
/// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2!
/// </summary>
public static void SwapOrCopyContent(ref MemorySource<T> destination, ref MemorySource<T> source)
{
if (source.HasSwappableContents && destination.HasSwappableContents)
{
SwapContents(ref destination, ref source);
}
else
{
if (destination.Memory.Length != source.Memory.Length)
{
throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!");
}
source.Memory.CopyTo(destination.Memory);
}
}
/// <inheritdoc />
public void Dispose()
{
this.MemoryOwner?.Dispose();
}
private static void SwapContents(ref MemorySource<T> a, ref MemorySource<T> b)
{
IMemoryOwner<T> tempOwner = a.MemoryOwner;
Memory<T> tempMemory = a.Memory;
a.MemoryOwner = b.MemoryOwner;
a.Memory = b.Memory;
b.MemoryOwner = tempOwner;
b.Memory = tempMemory;
}
}
}

6
src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs

@ -1,4 +1,6 @@
namespace SixLabors.Memory using System.Buffers;
namespace SixLabors.Memory
{ {
/// <summary> /// <summary>
/// Implements <see cref="MemoryAllocator"/> by newing up arrays by the GC on every allocation requests. /// Implements <see cref="MemoryAllocator"/> by newing up arrays by the GC on every allocation requests.
@ -6,7 +8,7 @@
public sealed class SimpleGcMemoryAllocator : MemoryAllocator public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{ {
/// <inheritdoc /> /// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, AllocationOptions options) internal override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{ {
return new BasicArrayBuffer<T>(new T[length]); return new BasicArrayBuffer<T>(new T[length]);
} }

1
src/ImageSharp/PixelFormats/Bgra32.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <summary> /// <summary>
/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255.
/// The color components are stored in blue, green, red, and alpha order (least significant to most significant byte). /// The color components are stored in blue, green, red, and alpha order (least significant to most significant byte).
/// The format is binary compatible with System.Drawing.Imaging.PixelFormat.Format32bppArgb
/// <para> /// <para>
/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form.
/// </para> /// </para>

52
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

@ -2,13 +2,13 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// <auto-generated /> // <auto-generated />
using System;
using System.Numerics;
using System.Buffers;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{ {
using System;
using System.Numerics;
using SixLabors.Memory;
/// <summary> /// <summary>
/// Collection of Porter Duff alpha blending functions applying different composition models. /// Collection of Porter Duff alpha blending functions applying different composition models.
/// </summary> /// </summary>
@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -278,7 +278,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -395,7 +395,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -434,7 +434,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -473,7 +473,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -512,7 +512,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -551,7 +551,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -590,7 +590,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -668,7 +668,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -707,7 +707,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -746,7 +746,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -785,7 +785,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -824,7 +824,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);

12
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -12,13 +12,13 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// <auto-generated /> // <auto-generated />
using System;
using System.Numerics;
using System.Buffers;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{ {
using System;
using System.Numerics;
using SixLabors.Memory;
/// <summary> /// <summary>
/// Collection of Porter Duff alpha blending functions applying different composition models. /// Collection of Porter Duff alpha blending functions applying different composition models.
/// </summary> /// </summary>
@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);

5
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -43,8 +44,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
Span<TPixel> pixels = source.GetPixelSpan(); Span<TPixel> pixels = source.GetPixelSpan();
// Build the histogram of the grayscale levels. // Build the histogram of the grayscale levels.
using (IBuffer<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) using (IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
using (IBuffer<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean)) using (IMemoryOwner<int> cdfBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean))
{ {
Span<int> histogram = histogramBuffer.GetSpan(); Span<int> histogram = histogramBuffer.GetSpan();
for (int i = 0; i < pixels.Length; i++) for (int i = 0; i < pixels.Length; i++)

5
src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -65,8 +66,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
int width = maxX - minX; int width = maxX - minX;
using (IBuffer<TPixel> colors = source.MemoryAllocator.Allocate<TPixel>(width)) using (IMemoryOwner<TPixel> colors = source.MemoryAllocator.Allocate<TPixel>(width))
using (IBuffer<float> amount = source.MemoryAllocator.Allocate<float>(width)) using (IMemoryOwner<float> amount = source.MemoryAllocator.Allocate<float>(width))
{ {
// Be careful! Do not capture colorSpan & amountSpan in the lambda below! // Be careful! Do not capture colorSpan & amountSpan in the lambda below!
Span<TPixel> colorSpan = colors.GetSpan(); Span<TPixel> colorSpan = colors.GetSpan();

5
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -111,7 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
} }
int width = maxX - minX; int width = maxX - minX;
using (IBuffer<TPixel> rowColors = source.MemoryAllocator.Allocate<TPixel>(width)) using (IMemoryOwner<TPixel> rowColors = source.MemoryAllocator.Allocate<TPixel>(width))
{ {
// Be careful! Do not capture rowColorsSpan in the lambda below! // Be careful! Do not capture rowColorsSpan in the lambda below!
Span<TPixel> rowColorsSpan = rowColors.GetSpan(); Span<TPixel> rowColorsSpan = rowColors.GetSpan();
@ -127,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
configuration.ParallelOptions, configuration.ParallelOptions,
y => y =>
{ {
using (IBuffer<float> amounts = source.MemoryAllocator.Allocate<float>(width)) using (IMemoryOwner<float> amounts = source.MemoryAllocator.Allocate<float>(width))
{ {
Span<float> amountsSpan = amounts.GetSpan(); Span<float> amountsSpan = amounts.GetSpan();
int offsetY = y - startY; int offsetY = y - startY;

5
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
} }
int width = maxX - minX; int width = maxX - minX;
using (IBuffer<TPixel> rowColors = source.MemoryAllocator.Allocate<TPixel>(width)) using (IMemoryOwner<TPixel> rowColors = source.MemoryAllocator.Allocate<TPixel>(width))
{ {
// Be careful! Do not capture rowColorsSpan in the lambda below! // Be careful! Do not capture rowColorsSpan in the lambda below!
Span<TPixel> rowColorsSpan = rowColors.GetSpan(); Span<TPixel> rowColorsSpan = rowColors.GetSpan();
@ -129,7 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
configuration.ParallelOptions, configuration.ParallelOptions,
y => y =>
{ {
using (IBuffer<float> amounts = source.MemoryAllocator.Allocate<float>(width)) using (IMemoryOwner<float> amounts = source.MemoryAllocator.Allocate<float>(width))
{ {
Span<float> amountsSpan = amounts.GetSpan(); Span<float> amountsSpan = amounts.GetSpan();
int offsetY = y - startY; int offsetY = y - startY;

4
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -15,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public class QuantizedFrame<TPixel> : IDisposable public class QuantizedFrame<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private IBuffer<byte> pixels; private IMemoryOwner<byte> pixels;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="QuantizedFrame{TPixel}"/> class. /// Initializes a new instance of the <see cref="QuantizedFrame{TPixel}"/> class.

45
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -70,37 +71,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Moment of <c>P(c)</c>. /// Moment of <c>P(c)</c>.
/// </summary> /// </summary>
private IBuffer<long> vwt; private IMemoryOwner<long> vwt;
/// <summary> /// <summary>
/// Moment of <c>r*P(c)</c>. /// Moment of <c>r*P(c)</c>.
/// </summary> /// </summary>
private IBuffer<long> vmr; private IMemoryOwner<long> vmr;
/// <summary> /// <summary>
/// Moment of <c>g*P(c)</c>. /// Moment of <c>g*P(c)</c>.
/// </summary> /// </summary>
private IBuffer<long> vmg; private IMemoryOwner<long> vmg;
/// <summary> /// <summary>
/// Moment of <c>b*P(c)</c>. /// Moment of <c>b*P(c)</c>.
/// </summary> /// </summary>
private IBuffer<long> vmb; private IMemoryOwner<long> vmb;
/// <summary> /// <summary>
/// Moment of <c>a*P(c)</c>. /// Moment of <c>a*P(c)</c>.
/// </summary> /// </summary>
private IBuffer<long> vma; private IMemoryOwner<long> vma;
/// <summary> /// <summary>
/// Moment of <c>c^2*P(c)</c>. /// Moment of <c>c^2*P(c)</c>.
/// </summary> /// </summary>
private IBuffer<float> m2; private IMemoryOwner<float> m2;
/// <summary> /// <summary>
/// Color space tag. /// Color space tag.
/// </summary> /// </summary>
private IBuffer<byte> tag; private IMemoryOwner<byte> tag;
/// <summary> /// <summary>
/// Maximum allowed color depth /// Maximum allowed color depth
@ -467,18 +468,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Span<long> vmaSpan = this.vma.GetSpan(); Span<long> vmaSpan = this.vma.GetSpan();
Span<float> m2Span = this.m2.GetSpan(); Span<float> m2Span = this.m2.GetSpan();
using (IBuffer<long> volume = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount)) using (IMemoryOwner<long> volume = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IBuffer<long> volumeR = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount)) using (IMemoryOwner<long> volumeR = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IBuffer<long> volumeG = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount)) using (IMemoryOwner<long> volumeG = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IBuffer<long> volumeB = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount)) using (IMemoryOwner<long> volumeB = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IBuffer<long> volumeA = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount)) using (IMemoryOwner<long> volumeA = memoryAllocator.Allocate<long>(IndexCount * IndexAlphaCount))
using (IBuffer<float> volume2 = memoryAllocator.Allocate<float>(IndexCount * IndexAlphaCount)) using (IMemoryOwner<float> volume2 = memoryAllocator.Allocate<float>(IndexCount * IndexAlphaCount))
using (IBuffer<long> area = memoryAllocator.Allocate<long>(IndexAlphaCount)) using (IMemoryOwner<long> area = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IBuffer<long> areaR = memoryAllocator.Allocate<long>(IndexAlphaCount)) using (IMemoryOwner<long> areaR = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IBuffer<long> areaG = memoryAllocator.Allocate<long>(IndexAlphaCount)) using (IMemoryOwner<long> areaG = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IBuffer<long> areaB = memoryAllocator.Allocate<long>(IndexAlphaCount)) using (IMemoryOwner<long> areaB = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IBuffer<long> areaA = memoryAllocator.Allocate<long>(IndexAlphaCount)) using (IMemoryOwner<long> areaA = memoryAllocator.Allocate<long>(IndexAlphaCount))
using (IBuffer<float> area2 = memoryAllocator.Allocate<float>(IndexAlphaCount)) using (IMemoryOwner<float> area2 = memoryAllocator.Allocate<float>(IndexAlphaCount))
{ {
Span<long> volumeSpan = volume.GetSpan(); Span<long> volumeSpan = volume.GetSpan();
Span<long> volumeRSpan = volumeR.GetSpan(); Span<long> volumeRSpan = volumeR.GetSpan();
@ -791,7 +792,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.colorCube = new Box[this.colors]; this.colorCube = new Box[this.colors];
float[] vv = new float[this.colors]; float[] vv = new float[this.colors];
ref var cube = ref this.colorCube[0]; ref Box cube = ref this.colorCube[0];
cube.R0 = cube.G0 = cube.B0 = cube.A0 = 0; cube.R0 = cube.G0 = cube.B0 = cube.A0 = 0;
cube.R1 = cube.G1 = cube.B1 = IndexCount - 1; cube.R1 = cube.G1 = cube.B1 = IndexCount - 1;
cube.A1 = IndexAlphaCount - 1; cube.A1 = IndexAlphaCount - 1;
@ -800,8 +801,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int i = 1; i < this.colors; i++) for (int i = 1; i < this.colors; i++)
{ {
ref var nextCube = ref this.colorCube[next]; ref Box nextCube = ref this.colorCube[next];
ref var currentCube = ref this.colorCube[i]; ref Box currentCube = ref this.colorCube[i];
if (this.Cut(ref nextCube, ref currentCube)) if (this.Cut(ref nextCube, ref currentCube))
{ {
vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F; vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F;

5
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
@ -296,14 +297,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed!
using (Buffer2D<Vector4> firstPassPixels = source.MemoryAllocator.Allocate2D<Vector4>(width, source.Height)) using (Buffer2D<Vector4> firstPassPixels = source.MemoryAllocator.Allocate2D<Vector4>(width, source.Height))
{ {
firstPassPixels.Buffer.Clear(); firstPassPixels.MemorySource.Clear();
ParallelFor.WithTemporaryBuffer( ParallelFor.WithTemporaryBuffer(
0, 0,
sourceRectangle.Bottom, sourceRectangle.Bottom,
configuration, configuration,
source.Width, source.Width,
(int y, IBuffer<Vector4> tempRowBuffer) => (int y, IMemoryOwner<Vector4> tempRowBuffer) =>
{ {
ref Vector4 firstPassRow = ref MemoryMarshal.GetReference(firstPassPixels.GetRowSpan(y)); ref Vector4 firstPassRow = ref MemoryMarshal.GetReference(firstPassPixels.GetRowSpan(y));
Span<TPixel> sourceRow = source.GetPixelRowSpan(y); Span<TPixel> sourceRow = source.GetPixelRowSpan(y);

7
src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -32,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// The buffer containing the weights values. /// The buffer containing the weights values.
/// </summary> /// </summary>
private readonly IBuffer<float> buffer; private readonly MemorySource<float> buffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WeightsWindow"/> struct. /// Initializes a new instance of the <see cref="WeightsWindow"/> struct.
@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
this.flatStartIndex = (index * buffer.Width) + left; this.flatStartIndex = (index * buffer.Width) + left;
this.Left = left; this.Left = left;
this.buffer = buffer.Buffer; this.buffer = buffer.MemorySource;
this.Length = length; this.Length = length;
} }
@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
/// <returns>The <see cref="Span{T}"/></returns> /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<float> GetWindowSpan() => this.buffer.Slice(this.flatStartIndex, this.Length); public Span<float> GetWindowSpan() => this.buffer.GetSpan().Slice(this.flatStartIndex, this.Length);
/// <summary> /// <summary>
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance. /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.

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();
} }

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

@ -4,16 +4,16 @@
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.IO;
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));

23
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs

@ -1,21 +1,24 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using SixLabors.Memory; using System.Buffers;
using SixLabors.ImageSharp.PixelFormats; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortClr))]
public abstract class PackFromVector4<TPixel> public abstract class PackFromVector4<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private IBuffer<Vector4> source; private IMemoryOwner<Vector4> source;
private IBuffer<TPixel> destination; private IMemoryOwner<TPixel> destination;
[Params(16, 128, 512)] [Params(16, 128, 512)]
public int Count { get; set; } public int Count { get; set; }

18
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs

@ -1,19 +1,21 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
using System;
using BenchmarkDotNet.Attributes; using System.Buffers;
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
public abstract class PackFromXyzw<TPixel> public abstract class PackFromXyzw<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private IBuffer<TPixel> destination; private IMemoryOwner<TPixel> destination;
private IBuffer<byte> source; private IMemoryOwner<byte> source;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }

20
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs

@ -1,20 +1,22 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes; using System.Buffers;
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
public abstract class ToVector4<TPixel> public abstract class ToVector4<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private IBuffer<TPixel> source; private IMemoryOwner<TPixel> source;
private IBuffer<Vector4> destination; private IMemoryOwner<Vector4> destination;
[Params(64, 300, 1024)] [Params(64, 300, 1024)]
public int Count { get; set; } public int Count { get; set; }

19
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs

@ -1,20 +1,21 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes; using System.Buffers;
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
public abstract class ToXyz<TPixel> public abstract class ToXyz<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private IBuffer<TPixel> source; private IMemoryOwner<TPixel> source;
private IBuffer<byte> destination; private IMemoryOwner<byte> destination;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }

18
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs

@ -1,22 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.Buffers;
using System.Linq; using BenchmarkDotNet.Attributes;
using System.Threading.Tasks;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{ {
using BenchmarkDotNet.Attributes;
using SixLabors.Memory;
using SixLabors.ImageSharp.PixelFormats;
public abstract class ToXyzw<TPixel> public abstract class ToXyzw<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private IBuffer<TPixel> source; private IMemoryOwner<TPixel> source;
private IBuffer<byte> destination; private IMemoryOwner<byte> destination;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }

8
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using System.Buffers;
namespace SixLabors.ImageSharp.Benchmarks namespace SixLabors.ImageSharp.Benchmarks
{ {
using System; using System;
@ -24,7 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IBuffer<Vector4> buffer = Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) using (IMemoryOwner<Vector4> buffer = Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{ {
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
@ -59,7 +61,7 @@ namespace SixLabors.ImageSharp.Benchmarks
{ {
using (var image = new Image<Rgba32>(800, 800)) using (var image = new Image<Rgba32>(800, 800))
{ {
using (IBuffer<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width)) using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width))
{ {
amounts.GetSpan().Fill(1); amounts.GetSpan().Fill(1);
@ -80,7 +82,7 @@ namespace SixLabors.ImageSharp.Benchmarks
{ {
using (var image = new Image<Rgba32>(800, 800)) using (var image = new Image<Rgba32>(800, 800))
{ {
using (IBuffer<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width)) using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width))
{ {
amounts.GetSpan().Fill(1); amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer(); Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();

4
tests/ImageSharp.Benchmarks/Samplers/Glow.cs

@ -3,6 +3,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
using System.Buffers;
namespace SixLabors.ImageSharp.Benchmarks namespace SixLabors.ImageSharp.Benchmarks
{ {
@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Benchmarks
} }
int width = maxX - minX; int width = maxX - minX;
using (IBuffer<TPixel> rowColors = Configuration.Default.MemoryAllocator.Allocate<TPixel>(width)) using (IMemoryOwner<TPixel> rowColors = Configuration.Default.MemoryAllocator.Allocate<TPixel>(width))
{ {
Buffer2D<TPixel> sourcePixels = source.PixelBuffer; Buffer2D<TPixel> sourcePixels = source.PixelBuffer;
rowColors.GetSpan().Fill(glowColor); rowColors.GetSpan().Fill(glowColor);

2
tests/ImageSharp.Sandbox46/Program.cs

@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Sandbox46
foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData)
{ {
string fileName = (string)data[0]; string fileName = (string)data[0];
benchmarks.DecodeJpeg_Original(fileName); benchmarks.DecodeJpeg(fileName);
} }
} }
} }

3
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced
Memory<TPixel> externalMemory = managerOfExeternalMemory.Memory; Memory<TPixel> externalMemory = managerOfExeternalMemory.Memory;
using (Image<TPixel> image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height))
{ {
Memory<TPixel> internalMemory = image1.GetPixelMemory(); Memory<TPixel> internalMemory = image1.GetPixelMemory();
Assert.Equal(targetBuffer.Length, internalMemory.Length); Assert.Equal(targetBuffer.Length, internalMemory.Length);
@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Tests.Advanced
image0.ComparePixelBufferTo(externalMemory.Span); image0.ComparePixelBufferTo(externalMemory.Span);
} }
} }
} }
[Theory] [Theory]

17
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -81,6 +81,23 @@ namespace SixLabors.ImageSharp.Tests.Drawing
provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact); provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact);
} }
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)]
public void FillRegion_WorksOnWrappedMemoryImage<TPixel>(TestImageProvider<TPixel> provider, int x0, int y0, int w, int h)
where TPixel : struct, IPixel<TPixel>
{
FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})";
var region = new RectangleF(x0, y0, w, h);
TPixel color = TestUtils.GetPixelOfNamedColor<TPixel>("Blue");
provider.RunValidatingProcessorTestOnWrappedMemoryImage(
c => c.Fill(color, region),
testDetails,
ImageComparer.Exact,
useReferenceOutputFrom: nameof(this.FillRegion));
}
public static readonly TheoryData<bool, string, float, PixelBlenderMode, float> BlendData = public static readonly TheoryData<bool, string, float, PixelBlenderMode, float> BlendData =
new TheoryData<bool, string, float, PixelBlenderMode, float>() new TheoryData<bool, string, float, PixelBlenderMode, float>()
{ {

1
tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs

@ -38,6 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
} }
} }
[Theory] [Theory]
[WithBlankImages(500, 500, PixelTypes.Rgba32)] [WithBlankImages(500, 500, PixelTypes.Rgba32)]
public void OverlayByFilledPolygonOpacity<TPixel>(TestImageProvider<TPixel> provider) public void OverlayByFilledPolygonOpacity<TPixel>(TestImageProvider<TPixel> provider)

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

@ -290,7 +290,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
} }
// no need to dispose when buffer is not array owner // no need to dispose when buffer is not array owner
buffers[i] = new Buffer2D<float>(new BasicArrayBuffer<float>(values), values.Length, 1); var source = new MemorySource<float>(new BasicArrayBuffer<float>(values), true);
buffers[i] = new Buffer2D<float>(source, values.Length, 1);
} }
return new JpegColorConverter.ComponentValues(buffers, 0); return new JpegColorConverter.ComponentValues(buffers, 0);
} }

53
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,28 +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> 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(GolangJpegDecoder)); Assert.ThrowsAny<Exception>(() => provider.GetImage(JpegDecoder));
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.CriticalEOF214, PixelTypes.Rgba32)]
public void DecodeBaselineJpeg_CriticalEOF_ShouldThrow_PdfJs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
// TODO: We need a public ImageDecoderException class in ImageSharp!
Assert.ThrowsAny<Exception>(() => provider.GetImage(PdfJsJpegDecoder));
}
[Theory(Skip = "Debug only, enable manually!")]
[WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)]
public void CompareJpegDecoders_Baseline<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
this.CompareJpegDecodersImpl(provider, DecodeBaselineJpegOutputName);
} }
} }
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save