Browse Source

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

# Conflicts:
#	src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs
pull/673/head
popow 8 years ago
parent
commit
2835cf40d4
  1. 5
      ImageSharp.sln.DotSettings
  2. 5
      build.ps1
  3. 5
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  4. 5
      src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
  5. 7
      src/ImageSharp.Drawing/Processing/BrushApplicator.cs
  6. 6
      src/ImageSharp.Drawing/Processing/ImageBrush{TPixel}.cs
  7. 6
      src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
  8. 8
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  9. 13
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
  10. 7
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
  11. 8
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs
  12. 6
      src/ImageSharp.Drawing/Processing/RecolorBrush{TPixel}.cs
  13. 7
      src/ImageSharp.Drawing/Processing/SolidBrush{TPixel}.cs
  14. 4
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  15. 1
      src/ImageSharp/Advanced/IPixelSource.cs
  16. 22
      src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs
  17. 13
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  18. 15
      src/ImageSharp/Common/Helpers/ParallelFor.cs
  19. 21
      src/ImageSharp/Configuration.cs
  20. 5
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  21. 1
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  22. 7
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  23. 2
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  24. 15
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  25. 11
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  26. 1
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
  27. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  28. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs
  29. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs
  30. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs
  31. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs
  32. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs
  33. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs
  34. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  35. 13
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTables.cs
  36. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs
  37. 3
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  38. 26
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  39. 1
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  40. 18
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs
  41. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  42. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  43. 2
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  44. 69
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs
  45. 1
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  46. 155
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bits.cs
  47. 255
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  48. 96
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs
  49. 23
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/EOFException.cs
  50. 253
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponent.cs
  51. 29
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangComponentScan.cs
  52. 27
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangDecoderErrorCode.cs
  53. 260
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangHuffmanTree.cs
  54. 53
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.ComputationData.cs
  55. 51
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.DataPointers.cs
  56. 705
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/GolangJpegScanDecoder.cs
  57. 392
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs
  58. 25
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/JpegScanDecoder.md
  59. 15
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/MissingFF00Exception.cs
  60. 42
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoder.cs
  61. 824
      src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs
  62. 6
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  63. 145
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  64. 56
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  65. 42
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoder.cs
  66. 9
      src/ImageSharp/Formats/Jpeg/README.md
  67. 9
      src/ImageSharp/Formats/Png/PngChunkType.cs
  68. 23
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  69. 42
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  70. 5
      src/ImageSharp/IO/DoubleBufferedStreamReader.cs
  71. 1
      src/ImageSharp/Image.Decode.cs
  72. 106
      src/ImageSharp/Image.WrapMemory.cs
  73. 1
      src/ImageSharp/ImageExtensions.Internal.cs
  74. 5
      src/ImageSharp/ImageFrameCollection.cs
  75. 16
      src/ImageSharp/ImageFrame{TPixel}.cs
  76. 14
      src/ImageSharp/ImageSharp.csproj
  77. 16
      src/ImageSharp/Image{TPixel}.cs
  78. 83
      src/ImageSharp/Memory/ArrayPoolMemoryAllocator.Buffer{T}.cs
  79. 71
      src/ImageSharp/Memory/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
  80. 140
      src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs
  81. 59
      src/ImageSharp/Memory/BasicArrayBuffer.cs
  82. 13
      src/ImageSharp/Memory/BasicByteBuffer.cs
  83. 7
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  84. 52
      src/ImageSharp/Memory/Buffer2D{T}.cs
  85. 7
      src/ImageSharp/Memory/BufferArea{T}.cs
  86. 34
      src/ImageSharp/Memory/ConsumedBuffer.cs
  87. 38
      src/ImageSharp/Memory/IBuffer{T}.cs
  88. 16
      src/ImageSharp/Memory/IManagedByteBuffer.cs
  89. 43
      src/ImageSharp/Memory/ManagedBufferBase.cs
  90. 38
      src/ImageSharp/Memory/MemoryAllocator.cs
  91. 67
      src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
  92. 38
      src/ImageSharp/Memory/MemoryOwnerExtensions.cs
  93. 103
      src/ImageSharp/Memory/MemorySource.cs
  94. 19
      src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs
  95. 15
      src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs
  96. 5
      src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
  97. 21
      src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
  98. 30
      src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs
  99. 11
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  100. 1
      src/ImageSharp/PixelFormats/Bgra32.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

5
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -37,10 +37,9 @@
<ProjectReference Include="..\ImageSharp\ImageSharp.csproj" /> <ProjectReference Include="..\ImageSharp\ImageSharp.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0005" />
<AdditionalFiles Include="..\..\stylecop.json" /> <AdditionalFiles Include="..\..\stylecop.json" />
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-dev000087" /> <PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta0007" />
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-dev000079" /> <PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0007" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta007"> <PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta007">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>

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

@ -2,6 +2,9 @@
// 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.Memory;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
using SixLabors.Shapes; using SixLabors.Shapes;
@ -45,7 +48,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);

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

@ -2,7 +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 SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -65,8 +68,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/ImageBrush{TPixel}.cs

@ -2,7 +2,9 @@
// 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.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -118,8 +120,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();

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

@ -2,8 +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.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory; using SixLabors.Memory;
@ -153,8 +155,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();

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

@ -2,8 +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.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -134,14 +136,14 @@ 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);
Parallel.For( ParallelFor.WithConfiguration(
minY, minY,
maxY, maxY,
configuration.ParallelOptions, configuration,
y => y =>
{ {
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width); Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);

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

@ -2,10 +2,11 @@
// 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.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -54,10 +55,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
// If there's no reason for blending, then avoid it. // If there's no reason for blending, then avoid it.
if (this.IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush)) if (this.IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush))
{ {
Parallel.For( ParallelFor.WithConfiguration(
minY, minY,
maxY, maxY,
configuration.ParallelOptions, configuration,
y => y =>
{ {
source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
@ -76,7 +77,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,
@ -84,10 +85,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{ {
amount.GetSpan().Fill(1f); amount.GetSpan().Fill(1f);
Parallel.For( ParallelFor.WithConfiguration(
minY, minY,
maxY, maxY,
configuration.ParallelOptions, configuration,
y => y =>
{ {
int offsetY = y - startY; int offsetY = y - startY;

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

@ -2,6 +2,9 @@
// 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.Memory;
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 +97,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;

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

@ -2,9 +2,11 @@
// 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;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Utils; using SixLabors.ImageSharp.Utils;
using SixLabors.Memory; using SixLabors.Memory;
@ -334,10 +336,10 @@ 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, true); 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;

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

@ -2,8 +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.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -138,8 +140,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();

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

@ -2,7 +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 SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -65,7 +68,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 +99,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();

4
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -3,6 +3,8 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -105,7 +107,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>

1
src/ImageSharp/Advanced/IPixelSource.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;

22
src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Threading.Tasks;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Contains extension methods for <see cref="Configuration"/>
/// </summary>
internal static class ConfigurationExtensions
{
/// <summary>
/// Creates a <see cref="ParallelOptions"/> object based on <paramref name="configuration"/>,
/// having <see cref="ParallelOptions.MaxDegreeOfParallelism"/> set to <see cref="Configuration.MaxDegreeOfParallelism"/>
/// </summary>
public static ParallelOptions GetParallelOptions(this Configuration configuration)
{
return new ParallelOptions() { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism };
}
}
}

13
src/ImageSharp/Common/Extensions/StreamExtensions.cs

@ -4,6 +4,9 @@
using System; using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
/// <summary> /// <summary>
@ -69,5 +72,15 @@ namespace SixLabors.ImageSharp
} }
} }
} }
public static void Read(this Stream stream, IManagedByteBuffer buffer)
{
stream.Read(buffer.Array, 0, buffer.Length());
}
public static void Write(this Stream stream, IManagedByteBuffer buffer)
{
stream.Write(buffer.Array, 0, buffer.Length());
}
} }
} }

15
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;
@ -10,11 +11,11 @@ namespace SixLabors.ImageSharp
internal static class ParallelFor internal static class ParallelFor
{ {
/// <summary> /// <summary>
/// Helper method to execute Parallel.For using the settings in <see cref="Configuration.ParallelOptions"/> /// Helper method to execute Parallel.For using the settings in <paramref name="configuration"/>
/// </summary> /// </summary>
public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action<int> body) public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action<int> body)
{ {
Parallel.For(fromInclusive, toExclusive, configuration.ParallelOptions, body); Parallel.For(fromInclusive, toExclusive, configuration.GetParallelOptions(), body);
} }
/// <summary> /// <summary>
@ -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.GetParallelOptions();
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;

21
src/ImageSharp/Configuration.cs

@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance); private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance);
private int maxDegreeOfParallelism = Environment.ProcessorCount;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary> /// </summary>
@ -55,9 +57,22 @@ namespace SixLabors.ImageSharp
public static Configuration Default { get; } = Lazy.Value; public static Configuration Default { get; } = Lazy.Value;
/// <summary> /// <summary>
/// Gets the global parallel options for processing tasks in parallel. /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms
/// configured with this <see cref="Configuration"/> instance.
/// </summary> /// </summary>
public ParallelOptions ParallelOptions { get; private set; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; public int MaxDegreeOfParallelism
{
get => this.maxDegreeOfParallelism;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism));
}
this.maxDegreeOfParallelism = value;
}
}
/// <summary> /// <summary>
/// Gets the currently registered <see cref="IImageFormat"/>s. /// Gets the currently registered <see cref="IImageFormat"/>s.
@ -114,7 +129,7 @@ namespace SixLabors.ImageSharp
{ {
return new Configuration return new Configuration
{ {
ParallelOptions = this.ParallelOptions, MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
ImageFormatsManager = this.ImageFormatsManager, ImageFormatsManager = this.ImageFormatsManager,
MemoryAllocator = this.MemoryAllocator, MemoryAllocator = this.MemoryAllocator,
ImageOperationsProvider = this.ImageOperationsProvider, ImageOperationsProvider = this.ImageOperationsProvider,

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

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -226,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
TPixel color = default; TPixel color = default;
var rgba = new Rgba32(0, 0, 0, 255); var rgba = new Rgba32(0, 0, 0, 255);
using (Buffer2D<byte> buffer = this.memoryAllocator.AllocateClean2D<byte>(width, height)) using (Buffer2D<byte> buffer = this.memoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean))
{ {
this.UncompressRle8(width, buffer.GetSpan()); this.UncompressRle8(width, buffer.GetSpan());
@ -354,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding; padding = 4 - padding;
} }
using (IManagedByteBuffer row = this.memoryAllocator.AllocateCleanManagedByteBuffer(arrayWidth + padding)) using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean))
{ {
TPixel color = default; TPixel color = default;
var rgba = new Rgba32(0, 0, 0, 255); var rgba = new Rgba32(0, 0, 0, 255);

1
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;

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

@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -321,11 +322,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (imageDescriptor.LocalColorTableFlag) if (imageDescriptor.LocalColorTableFlag)
{ {
int length = imageDescriptor.LocalColorTableSize * 3; int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, true); localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.Array, 0, length); this.stream.Read(localColorTable.Array, 0, length);
} }
indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true); indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(imageDescriptor, indices.GetSpan()); this.ReadFrameIndices(imageDescriptor, indices.GetSpan());
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan()); ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>((localColorTable ?? this.globalColorTable).GetSpan());
@ -556,7 +557,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, true); this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream // Read the global color table data from the stream
stream.Read(this.globalColorTable.Array, 0, globalColorTableLength); stream.Read(this.globalColorTable.Array, 0, globalColorTableLength);

2
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -7,6 +7,8 @@ using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;

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

@ -2,9 +2,12 @@
// 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;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
@ -32,17 +35,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
@ -57,9 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream = stream; this.stream = stream;
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, true); this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, true); this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, true); this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
} }
/// <summary> /// <summary>

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

@ -2,9 +2,12 @@
// 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;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
@ -66,12 +69,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.
@ -168,8 +171,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth) public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth)
{ {
this.initialCodeSize = Math.Max(2, colorDepth); this.initialCodeSize = Math.Max(2, colorDepth);
this.hashTable = memoryAllocator.Allocate<int>(HashSize, true); this.hashTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean);
this.codeTable = memoryAllocator.Allocate<int>(HashSize, true); this.codeTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean);
} }
/// <summary> /// <summary>

1
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs

@ -4,6 +4,7 @@
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming

1
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Common.Tuples; using SixLabors.ImageSharp.Common.Tuples;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters

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

@ -3,9 +3,11 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
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.
@ -20,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="memoryAllocator">The memory allocator used to allocate memory for image processing operations.</param> /// <param name="memoryAllocator">The memory allocator used to allocate memory for image processing operations.</param>
public FastACTables(MemoryAllocator memoryAllocator) public FastACTables(MemoryAllocator memoryAllocator)
{ {
this.tables = memoryAllocator.AllocateClean2D<short>(512, 4); this.tables = memoryAllocator.Allocate2D<short>(512, 4, AllocationOptions.Clean);
} }
/// <summary> /// <summary>
@ -35,10 +37,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 +49,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

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

@ -2,17 +2,20 @@
// 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.ImageSharp.Memory;
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 +43,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];

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;

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

@ -3,13 +3,14 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; 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

26
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs → src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -5,21 +5,20 @@ 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.Memory;
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 +88,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,11 +124,11 @@ 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);
} }
this.SpectralBlocks = this.memoryAllocator.AllocateClean2D<Block8x8>(blocksPerColumnForMcu, blocksPerLineForMcu + 1); this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(blocksPerColumnForMcu, blocksPerLineForMcu + 1, AllocationOptions.Clean);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -140,16 +139,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);
} }
} }

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

@ -3,6 +3,7 @@
using System; using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;

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

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

@ -2,9 +2,11 @@
// 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;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -37,7 +39,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"/>.

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

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public static readonly byte[] AdobeMarker = Encoding.UTF8.GetBytes("Adobe"); public static readonly byte[] AdobeMarker = Encoding.UTF8.GetBytes("Adobe");
/// <summary> /// <summary>
/// Returns a value indicating whether the passed bytes are a match to the profile identifer /// Returns a value indicating whether the passed bytes are a match to the profile identifier
/// </summary> /// </summary>
/// <param name="bytesToCheck">The bytes to check</param> /// <param name="bytesToCheck">The bytes to check</param>
/// <param name="profileIdentifier">The profile identifier</param> /// <param name="profileIdentifier">The profile identifier</param>

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

1
src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs

@ -5,6 +5,7 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;

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, true);
}
/// <summary>
/// Initializes all component data except <see cref="SpectralBlocks"/>.
/// </summary>
/// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param>
public void InitializeCoreData(GolangJpegDecoderCore decoder)
{
// Section B.2.2 states that "the value of C_i shall be different from
// the values of C_1 through C_(i-1)".
int i = this.Index;
for (int j = 0; j < this.Index; j++)
{
if (this.Identifier == decoder.Components[j].Identifier)
{
throw new ImageFormatException("Repeated component identifier");
}
}
this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)];
if (this.QuantizationTableIndex > GolangJpegDecoderCore.MaxTq)
{
throw new ImageFormatException("Bad Tq value");
}
byte hv = decoder.Temp[7 + (3 * i)];
int h = hv >> 4;
int v = hv & 0x0f;
if (h < 1 || h > 4 || v < 1 || v > 4)
{
throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio");
}
if (h == 3 || v == 3)
{
throw new ImageFormatException("Lnsupported subsampling ratio");
}
switch (decoder.ComponentCount)
{
case 1:
// If a JPEG image has only one component, section A.2 says "this data
// is non-interleaved by definition" and section A.2.2 says "[in this
// case...] the order of data units within a scan shall be left-to-right
// and top-to-bottom... regardless of the values of H_1 and V_1". Section
// 4.8.2 also says "[for non-interleaved data], the MCU is defined to be
// one data unit". Similarly, section A.1.1 explains that it is the ratio
// of H_i to max_j(H_j) that matters, and similarly for V. For grayscale
// images, H_1 is the maximum H_j for all components j, so that ratio is
// always 1. The component's (h, v) is effectively always (1, 1): even if
// the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
// MCUs, not two 16x8 MCUs.
h = 1;
v = 1;
break;
case 3:
// For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
// 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
// (h, v) values for the Y component are either (1, 1), (1, 2),
// (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
// must be a multiple of the Cb and Cr component's values. We also
// assume that the two chroma components have the same subsampling
// ratio.
switch (i)
{
case 0:
{
// Y.
// We have already verified, above, that h and v are both
// either 1, 2 or 4, so invalid (h, v) combinations are those
// with v == 4.
if (v == 4)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
case 1:
{
// Cb.
Size s0 = decoder.Components[0].SamplingFactors;
if (s0.Width % h != 0 || s0.Height % v != 0)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
case 2:
{
// Cr.
Size s1 = decoder.Components[1].SamplingFactors;
if (s1.Width != h || s1.Height != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
}
break;
case 4:
// For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
// Theoretically, 4-component JPEG images could mix and match hv values
// but in practice, those two combinations are the only ones in use,
// and it simplifies the applyBlack code below if we can assume that:
// - for CMYK, the C and K channels have full samples, and if the M
// and Y channels subsample, they subsample both horizontally and
// vertically.
// - for YCbCrK, the Y and K channels have full samples.
switch (i)
{
case 0:
if (hv != 0x11 && hv != 0x22)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
case 1:
case 2:
if (hv != 0x11)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
case 3:
Size s0 = decoder.Components[0].SamplingFactors;
if (s0.Width != h || s0.Height != v)
{
throw new ImageFormatException("Unsupported subsampling ratio");
}
break;
}
break;
}
this.SamplingFactors = new Size(h, v);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Block8x8 GetBlockReference(int column, int row)
{
return ref this.SpectralBlocks[column, row];
}
public void Dispose()
{
this.SpectralBlocks?.Dispose();
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -5,12 +5,14 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; 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.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@ -19,14 +21,14 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
/// Performs the jpeg decoding operation. /// Performs the jpeg decoding operation.
/// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/> /// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// with additional fixes for both performance and common encoding errors. /// with additional fixes for both performance and common encoding errors.
/// </summary> /// </summary>
internal sealed class PdfJsJpegDecoderCore : IRawJpegData internal sealed class JpegDecoderCore : IRawJpegData
{ {
/// <summary> /// <summary>
/// The only supported precision /// The only supported precision
@ -51,12 +53,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
@ -69,10 +71,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private ushort resetInterval; private ushort resetInterval;
/// <summary> /// <summary>
/// Whether the image has a EXIF header /// Whether the image has an EXIF marker
/// </summary> /// </summary>
private bool isExif; private bool isExif;
/// <summary>
/// Contains exif data
/// </summary>
private byte[] exifData;
/// <summary>
/// Whether the image has an ICC marker
/// </summary>
private bool isIcc;
/// <summary>
/// Contains ICC data
/// </summary>
private byte[] iccData;
/// <summary> /// <summary>
/// Contains information about the JFIF marker /// Contains information about the JFIF marker
/// </summary> /// </summary>
@ -84,11 +101,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private AdobeMarker adobe; private AdobeMarker adobe;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PdfJsJpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public PdfJsJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options) public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{ {
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
this.IgnoreMetadata = options.IgnoreMetadata; this.IgnoreMetadata = options.IgnoreMetadata;
@ -97,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary> /// <summary>
/// Gets the frame /// Gets the frame
/// </summary> /// </summary>
public PdfJsFrame Frame { get; private set; } public JpegFrame Frame { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; } public Size ImageSizeInPixels { get; private set; }
@ -146,7 +163,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;
@ -159,14 +176,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary> /// </summary>
/// <param name="marker">The buffer to read file markers to</param> /// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param> /// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns> /// <returns>The <see cref="JpegFileMarker"/></returns>
public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream) public static JpegFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{ {
int value = stream.Read(marker, 0, 2); int value = stream.Read(marker, 0, 2);
if (value == 0) if (value == 0)
{ {
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
} }
if (marker[0] == JpegConstants.Markers.XFF) if (marker[0] == JpegConstants.Markers.XFF)
@ -179,16 +196,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>
@ -201,6 +218,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.ParseStream(stream); this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties(); this.InitDerivedMetaDataProperties();
return this.PostProcessIntoImage<TPixel>(); return this.PostProcessIntoImage<TPixel>();
} }
@ -212,6 +231,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public IImageInfo Identify(Stream stream) public IImageInfo Identify(Stream stream)
{ {
this.ParseStream(stream, true); this.ParseStream(stream, true);
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties(); this.InitDerivedMetaDataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
} }
@ -228,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
this.InputStream.Read(this.markerBuffer, 0, 2); this.InputStream.Read(this.markerBuffer, 0, 2);
var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI) if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
throw new ImageFormatException("Missing SOI marker."); throw new ImageFormatException("Missing SOI marker.");
@ -236,14 +257,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);
} }
@ -403,6 +424,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}"); throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}");
} }
/// <summary>
/// Initializes the EXIF profile.
/// </summary>
private void InitExifProfile()
{
if (this.isExif)
{
this.MetaData.ExifProfile = new ExifProfile(this.exifData);
}
}
/// <summary>
/// Initializes the ICC profile.
/// </summary>
private void InitIccProfile()
{
if (this.isIcc)
{
var profile = new IccProfile(this.iccData);
if (profile.CheckIsValid())
{
this.MetaData.IccProfile = profile;
}
}
}
/// <summary> /// <summary>
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header. /// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary> /// </summary>
@ -431,11 +478,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile); this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile);
} }
} }
}
if (this.MetaData.IccProfile?.CheckIsValid() == false) /// <summary>
{ /// Extends the profile with additional data.
this.MetaData.IccProfile = null; /// </summary>
} /// <param name="profile">The profile data array.</param>
/// <param name="extension">The array containing addition profile data.</param>
private void ExtendProfile(ref byte[] profile, byte[] extension)
{
int currentLength = profile.Length;
Array.Resize(ref profile, currentLength + extension.Length);
Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length);
} }
/// <summary> /// <summary>
@ -469,7 +524,8 @@ 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>
private void ProcessApp1Marker(int remaining) private void ProcessApp1Marker(int remaining)
{ {
if (remaining < 6 || this.IgnoreMetadata) const int Exif00 = 6;
if (remaining < Exif00 || this.IgnoreMetadata)
{ {
// Skip the application header length // Skip the application header length
this.InputStream.Skip(remaining); this.InputStream.Skip(remaining);
@ -482,7 +538,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{ {
this.isExif = true; this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile); if (this.exifData == null)
{
// The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific
this.exifData = profile.Skip(Exif00).ToArray();
}
else
{
// If the EXIF information exceeds 64K, it will be split over multiple APP1 markers
this.ExtendProfile(ref this.exifData, profile.Skip(Exif00).ToArray());
}
} }
} }
@ -506,16 +571,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{ {
this.isIcc = true;
byte[] profile = new byte[remaining]; byte[] profile = new byte[remaining];
this.InputStream.Read(profile, 0, remaining); this.InputStream.Read(profile, 0, remaining);
if (this.MetaData.IccProfile == null) if (this.iccData == null)
{ {
this.MetaData.IccProfile = new IccProfile(profile); this.iccData = profile;
} }
else else
{ {
this.MetaData.IccProfile.Extend(profile); // If the ICC information exceeds 64K, it will be split over multiple APP2 markers
this.ExtendProfile(ref this.iccData, profile);
} }
} }
else else
@ -630,7 +697,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, in JpegFileMarker frameMarker, bool metadataOnly)
{ {
if (this.Frame != null) if (this.Frame != null)
{ {
@ -645,7 +712,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Only 8-Bit precision supported."); throw new ImageFormatException("Only 8-Bit precision supported.");
} }
this.Frame = new PdfJsFrame this.Frame = new JpegFrame
{ {
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1, Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
@ -667,7 +734,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++)
@ -686,7 +753,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;
@ -709,7 +776,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>
private void ProcessDefineHuffmanTablesMarker(int remaining) private void ProcessDefineHuffmanTablesMarker(int remaining)
{ {
using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(256)) using (IManagedByteBuffer huffmanData = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
{ {
ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan()); ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan());
for (int i = 2; i < remaining;) for (int i = 2; i < remaining;)
@ -717,7 +784,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); byte huffmanTableSpec = (byte)this.InputStream.ReadByte();
this.InputStream.Read(huffmanData.Array, 0, 16); this.InputStream.Read(huffmanData.Array, 0, 16);
using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(17)) using (IManagedByteBuffer codeLengths = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean))
{ {
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan()); ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan());
int codeLengthSum = 0; int codeLengthSum = 0;
@ -727,7 +794,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1);
} }
using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(256)) using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
{ {
this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum);
@ -794,7 +861,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;
@ -831,9 +898,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>

56
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -4,9 +4,11 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
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.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@ -214,6 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteApplicationHeader(image.MetaData); this.WriteApplicationHeader(image.MetaData);
// Write Exif and ICC profiles
this.WriteProfiles(image); this.WriteProfiles(image);
// Write the quantization tables. // Write the quantization tables.
@ -430,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="meta">The image meta data.</param> /// <param name="meta">The image meta data.</param>
private void WriteApplicationHeader(ImageMetaData meta) private void WriteApplicationHeader(ImageMetaData meta)
{ {
// Write the start of image marker. Markers are always prefixed with with 0xff. // Write the start of image marker. Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOI; this.buffer[1] = JpegConstants.Markers.SOI;
@ -620,27 +623,59 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception> /// </exception>
private void WriteExifProfile(ExifProfile exifProfile) private void WriteExifProfile(ExifProfile exifProfile)
{ {
const int Max = 65533; const int MaxBytesApp1 = 65533;
const int MaxBytesWithExifId = 65527;
byte[] data = exifProfile?.ToByteArray(); byte[] data = exifProfile?.ToByteArray();
if (data == null || data.Length == 0) if (data == null || data.Length == 0)
{ {
return; return;
} }
if (data.Length > Max) data = ProfileResolver.ExifMarker.Concat(data).ToArray();
int remaining = data.Length;
int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining;
int app1Length = bytesToWrite + 2;
this.WriteApp1Header(app1Length);
// write the exif data
this.outputStream.Write(data, 0, bytesToWrite);
remaining -= bytesToWrite;
// if the exif data exceeds 64K, write it in multiple APP1 Markers
for (int idx = MaxBytesApp1; idx < data.Length; idx += MaxBytesWithExifId)
{ {
throw new ImageFormatException($"Exif profile size exceeds limit. nameof{Max}"); bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining;
} app1Length = bytesToWrite + 2 + 6;
int length = data.Length + 2; this.WriteApp1Header(app1Length);
// write Exif00 marker
ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan());
this.outputStream.Write(this.buffer, 0, 6);
// write the exif data
this.outputStream.Write(data, idx, bytesToWrite);
remaining -= bytesToWrite;
}
}
/// <summary>
/// Writes the App1 header.
/// </summary>
/// <param name="app1Length">The length of the data the app1 marker contains</param>
private void WriteApp1Header(int app1Length)
{
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker
this.buffer[2] = (byte)((length >> 8) & 0xFF); this.buffer[2] = (byte)((app1Length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF); this.buffer[3] = (byte)(app1Length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4); this.outputStream.Write(this.buffer, 0, 4);
this.outputStream.Write(data, 0, data.Length);
} }
/// <summary> /// <summary>
@ -652,7 +687,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception> /// </exception>
private void WriteIccProfile(IccProfile iccProfile) private void WriteIccProfile(IccProfile iccProfile)
{ {
// Just incase someone set the value to null by accident.
if (iccProfile == null) if (iccProfile == null)
{ {
return; return;
@ -908,7 +942,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="length">The marker length.</param> /// <param name="length">The marker length.</param>
private void WriteMarkerHeader(byte marker, int length) private void WriteMarkerHeader(byte marker, int length)
{ {
// Markers are always prefixed with with 0xff. // Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = marker; this.buffer[1] = marker;
this.buffer[2] = (byte)(length >> 8); this.buffer[2] = (byte)(length >> 8);

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

9
src/ImageSharp/Formats/Png/PngChunkType.cs

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Formats.Png namespace SixLabors.ImageSharp.Formats.Png
{ {
/// <summary> /// <summary>
/// Contains a list of of chunk types. /// Contains a list of chunk types.
/// </summary> /// </summary>
internal enum PngChunkType : uint internal enum PngChunkType : uint
{ {
@ -55,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary> /// </summary>
Physical = 0x70485973U // pHYs Physical = 0x70485973U, // pHYs
/// <summary>
/// The data chunk which contains the Exif profile.
/// </summary>
Exif = 0x65584966U // eXIf
} }
} }

23
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -13,7 +13,9 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -259,6 +261,15 @@ namespace SixLabors.ImageSharp.Formats.Png
break; break;
case PngChunkType.Text: case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
metadata.ExifProfile = new ExifProfile(exifData);
}
break; break;
case PngChunkType.End: case PngChunkType.End:
this.isEndChunkReached = true; this.isEndChunkReached = true;
@ -370,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return false; return false;
} }
buffer = this.MemoryAllocator.AllocateCleanManagedByteBuffer(bytesPerScanline * 8 / bits); buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
byte[] result = buffer.Array; byte[] result = buffer.Array;
int mask = 0xFF >> (8 - bits); int mask = 0xFF >> (8 - bits);
int resultOffset = 0; int resultOffset = 0;
@ -437,8 +448,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8; this.bytesPerSample = this.header.BitDepth / 8;
} }
this.previousScanline = this.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.previousScanline = this.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
} }
/// <summary> /// <summary>
@ -1170,7 +1181,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images /// Decodes and assigns marker colors that identify transparent pixels in non indexed images
/// </summary> /// </summary>
/// <param name="alpha">The aplha tRNS array</param> /// <param name="alpha">The alpha tRNS array</param>
private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha) private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha)
{ {
if (this.pngColorType == PngColorType.Rgb) if (this.pngColorType == PngColorType.Rgb)
@ -1217,7 +1228,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam> /// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <param name="scanline">The defiltered scanline</param> /// <param name="scanline">The defiltered scanline</param>
/// <param name="row">Thecurrent output image row</param> /// <param name="row">The current output image row</param>
private void ProcessScanlineFromPalette<TPixel>(ReadOnlySpan<byte> scanline, Span<TPixel> row) private void ProcessScanlineFromPalette<TPixel>(ReadOnlySpan<byte> scanline, Span<TPixel> row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -1445,7 +1456,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private IManagedByteBuffer ReadChunkData(int length) private IManagedByteBuffer ReadChunkData(int length)
{ {
// We rent the buffer here to return it afterwards in Decode() // We rent the buffer here to return it afterwards in Decode()
IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(length); IManagedByteBuffer buffer = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.currentStream.Read(buffer.Array, 0, length); this.currentStream.Read(buffer.Array, 0, length);

42
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -231,6 +232,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, image); this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream); this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, image);
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream); this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream); this.WriteEndChunk(stream);
stream.Flush(); stream.Flush();
@ -490,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Calculates the correct number of bytes per pixel for the given color type. /// Calculates the correct number of bytes per pixel for the given color type.
/// </summary> /// </summary>
/// <returns>The <see cref="int"/></returns> /// <returns>Bytes per pixel</returns>
private int CalculateBytesPerPixel() private int CalculateBytesPerPixel()
{ {
switch (this.pngColorType) switch (this.pngColorType)
@ -648,6 +650,22 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9); this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
} }
/// <summary>
/// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image.</param>
private void WriteExifChunk<TPixel>(Stream stream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
if (image.MetaData.ExifProfile?.Values.Count > 0)
{
image.MetaData.SyncProfiles();
this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray());
}
}
/// <summary> /// <summary>
/// Writes the gamma information to the stream. /// Writes the gamma information to the stream.
/// </summary> /// </summary>
@ -678,9 +696,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerScanline = this.width * this.bytesPerPixel; this.bytesPerScanline = this.width * this.bytesPerPixel;
int resultLength = this.bytesPerScanline + 1; int resultLength = this.bytesPerScanline + 1;
this.previousScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.rawScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.rawScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.result = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.result = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
switch (this.pngFilterMethod) switch (this.pngFilterMethod)
{ {
@ -689,29 +707,29 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngFilterMethod.Sub: case PngFilterMethod.Sub:
this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break; break;
case PngFilterMethod.Up: case PngFilterMethod.Up:
this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break; break;
case PngFilterMethod.Average: case PngFilterMethod.Average:
this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break; break;
case PngFilterMethod.Paeth: case PngFilterMethod.Paeth:
this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break; break;
case PngFilterMethod.Adaptive: case PngFilterMethod.Adaptive:
this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength); this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break; break;
} }

5
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
@ -44,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
this.stream = stream; this.stream = stream;
this.length = (int)stream.Length; this.length = (int)stream.Length;
this.managedBuffer = memoryAllocator.AllocateCleanManagedByteBuffer(ChunkLength); this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean);
this.bufferChunk = this.managedBuffer.Array; this.bufferChunk = this.managedBuffer.Array;
} }

1
src/ImageSharp/Image.Decode.cs

@ -4,6 +4,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;

106
src/ImageSharp/Image.WrapMemory.cs

@ -2,6 +2,9 @@
// 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.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -13,8 +16,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 +27,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 +35,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);
} }
} }
} }

1
src/ImageSharp/ImageExtensions.Internal.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;

5
src/ImageSharp/ImageFrameCollection.cs

@ -5,6 +5,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -30,14 +31,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)

16
src/ImageSharp/ImageFrame{TPixel}.cs

@ -2,10 +2,12 @@
// 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;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -86,16 +88,16 @@ namespace SixLabors.ImageSharp
this.configuration = configuration; this.configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator; this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height, false); this.PixelBuffer = this.MemoryAllocator.Allocate2D<TPixel>(width, height);
this.MetaData = metaData; this.MetaData = metaData;
this.Clear(configuration.ParallelOptions, backgroundColor); this.Clear(configuration.GetParallelOptions(), backgroundColor);
} }
/// <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 +108,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 +118,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 +274,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);

14
src/ImageSharp/ImageSharp.csproj

@ -29,27 +29,19 @@
<DebugType Condition="$(codecov) == ''">portable</DebugType> <DebugType Condition="$(codecov) == ''">portable</DebugType>
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
<Features>IOperation</Features> <Features>IOperation</Features>
<LangVersion>7.3</LangVersion> <LangVersion>Latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" /> <Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0005" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0006" />
<AdditionalFiles Include="..\..\stylecop.json" /> <AdditionalFiles Include="..\..\stylecop.json" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta007"> <PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta007">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'netcoreapp2.1'">
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.IO.UnmanagedMemoryStream" Version="4.3.0" /> <PackageReference Include="System.IO.UnmanagedMemoryStream" Version="4.3.0" />

16
src/ImageSharp/Image{TPixel}.cs

@ -8,6 +8,7 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
@ -84,23 +85,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>

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

@ -1,83 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.InteropServices;
namespace SixLabors.Memory
{
/// <summary>
/// Contains <see cref="Buffer{T}"/> and <see cref="ManagedByteBuffer"/>
/// </summary>
public partial class ArrayPoolMemoryAllocator
{
/// <summary>
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
/// In this implementation <see cref="IBuffer{T}.Memory"/> is owned.
/// </summary>
private class Buffer<T> : ManagedBufferBase<T>
where T : struct
{
/// <summary>
/// The length of the buffer
/// </summary>
private readonly int length;
/// <summary>
/// A weak reference to the source pool.
/// </summary>
/// <remarks>
/// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed
/// after a call to <see cref="ArrayPoolMemoryAllocator.ReleaseRetainedResources"/>, regardless of having buffer instances still being in use.
/// </remarks>
private WeakReference<ArrayPool<byte>> sourcePoolReference;
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool)
{
this.Data = data;
this.length = length;
this.sourcePoolReference = new WeakReference<ArrayPool<byte>>(sourcePool);
}
/// <summary>
/// Gets the buffer as a byte array.
/// </summary>
protected byte[] Data { get; private set; }
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (!disposing || this.Data == null || this.sourcePoolReference == null)
{
return;
}
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool))
{
pool.Return(this.Data);
}
this.sourcePoolReference = null;
this.Data = null;
}
public override Span<T> GetSpan() => MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length);
protected override object GetPinnableObject() => this.Data;
}
/// <summary>
/// The <see cref="IManagedByteBuffer"/> implementation of <see cref="ArrayPoolMemoryAllocator"/>.
/// </summary>
private class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer
{
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool)
: base(data, length, sourcePool)
{
}
public byte[] Array => this.Data;
}
}
}

71
src/ImageSharp/Memory/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs

@ -1,71 +0,0 @@
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.Memory
{
/// <summary>
/// Contains common factory methods and configuration constants.
/// </summary>
public partial class ArrayPoolMemoryAllocator
{
/// <summary>
/// The default value for: maximum size of pooled arrays in bytes.
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw <see cref="Rgba32"/> data.
/// </summary>
internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024;
/// <summary>
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024;
/// <summary>
/// The default bucket count for <see cref="largeArrayPool"/>.
/// </summary>
private const int DefaultLargePoolBucketCount = 6;
/// <summary>
/// The default bucket count for <see cref="normalArrayPool"/>.
/// </summary>
private const int DefaultNormalPoolBucketCount = 16;
/// <summary>
/// This is the default. Should be good for most use cases.
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryAllocator CreateDefault()
{
return new ArrayPoolMemoryAllocator(
DefaultMaxPooledBufferSizeInBytes,
DefaultBufferSelectorThresholdInBytes,
DefaultLargePoolBucketCount,
DefaultNormalPoolBucketCount);
}
/// <summary>
/// For environments with limited memory capabilities. Only small images are pooled, which can result in reduced througput.
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryAllocator CreateWithModeratePooling()
{
return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24);
}
/// <summary>
/// Only pool small buffers like image rows.
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryAllocator CreateWithMinimalPooling()
{
return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24);
}
/// <summary>
/// RAM is not an issue for me, gimme maximum througput!
/// </summary>
/// <returns>The memory manager</returns>
public static ArrayPoolMemoryAllocator CreateWithAggressivePooling()
{
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
}
}
}

140
src/ImageSharp/Memory/ArrayPoolMemoryAllocator.cs

@ -1,140 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
using System.Runtime.CompilerServices;
namespace SixLabors.Memory
{
/// <summary>
/// Implements <see cref="MemoryAllocator"/> by allocating memory from <see cref="ArrayPool{T}"/>.
/// </summary>
public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
/// </summary>
private ArrayPool<byte> normalArrayPool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
/// </summary>
private ArrayPool<byte> largeArrayPool;
private readonly int maxArraysPerBucketNormalPool;
private readonly int maxArraysPerBucketLargePool;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
public ArrayPoolMemoryAllocator()
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes)
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">Arrays over this threshold will be pooled in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes)
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool)
{
ImageSharp.Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes));
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes;
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool;
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool;
this.InitArrayPools();
}
/// <summary>
/// Gets the maximum size of pooled arrays in bytes.
/// </summary>
public int MaxPoolSizeInBytes { get; }
/// <summary>
/// Gets the threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
public int PoolSelectorThresholdInBytes { get; }
/// <inheritdoc />
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
/// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, bool clear)
{
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteArray, length, pool);
if (clear)
{
buffer.Clear();
}
return buffer;
}
/// <inheritdoc />
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
{
ArrayPool<byte> pool = this.GetArrayPool(length);
byte[] byteArray = pool.Rent(length);
var buffer = new ManagedByteBuffer(byteArray, length, pool);
if (clear)
{
buffer.Clear();
}
return buffer;
}
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes)
{
return maxPoolSizeInBytes / 4;
}
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
{
return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool;
}
private void InitArrayPools()
{
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool);
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
}
}
}

59
src/ImageSharp/Memory/BasicArrayBuffer.cs

@ -1,59 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.Memory
{
/// <summary>
/// Wraps an array as an <see cref="IBuffer{T}"/> instance. In this implementation <see cref="IBuffer{T}.Memory"/> is owned.
/// </summary>
internal class BasicArrayBuffer<T> : ManagedBufferBase<T>
where T : struct
{
public BasicArrayBuffer(T[] array, int length)
{
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length));
this.Array = array;
this.Length = length;
}
public BasicArrayBuffer(T[] array)
: this(array, array.Length)
{
}
public T[] Array { get; }
public int Length { get; }
/// <summary>
/// Returns a reference to specified element of the buffer.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The reference to the specified element</returns>
public ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
Span<T> span = this.GetSpan();
return ref span[index];
}
}
protected override void Dispose(bool disposing)
{
}
public override Span<T> GetSpan() => this.Array.AsSpan(0, this.Length);
protected override object GetPinnableObject()
{
return this.Array;
}
}
}

13
src/ImageSharp/Memory/BasicByteBuffer.cs

@ -1,13 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.Memory
{
internal sealed class BasicByteBuffer : BasicArrayBuffer<byte>, IManagedByteBuffer
{
internal BasicByteBuffer(byte[] array)
: base(array)
{
}
}
}

7
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -3,9 +3,10 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.Memory namespace SixLabors.ImageSharp.Memory
{ {
/// <summary> /// <summary>
/// Defines extension methods for <see cref="Buffer2D{T}"/>. /// Defines extension methods for <see cref="Buffer2D{T}"/>.
@ -18,7 +19,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 +62,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>

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

@ -3,9 +3,10 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.Memory namespace SixLabors.ImageSharp.Memory
{ {
/// <summary> /// <summary>
/// Represents a buffer of value type objects /// Represents a buffer of value type objects
@ -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.
@ -59,8 +62,8 @@ namespace SixLabors.Memory
get get
{ {
ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); ImageSharp.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;

7
src/ImageSharp/Memory/BufferArea{T}.cs

@ -1,8 +1,11 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.Memory namespace SixLabors.ImageSharp.Memory
{ {
/// <summary> /// <summary>
/// Represents a rectangular area inside a 2D memory buffer (<see cref="Buffer2D{T}"/>). /// Represents a rectangular area inside a 2D memory buffer (<see cref="Buffer2D{T}"/>).
@ -120,7 +123,7 @@ namespace SixLabors.Memory
public BufferArea<T> GetSubArea(Rectangle rectangle) public BufferArea<T> GetSubArea(Rectangle rectangle)
{ {
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle));
int x = this.Rectangle.X + rectangle.X; int x = this.Rectangle.X + rectangle.X;
int y = this.Rectangle.Y + rectangle.Y; int y = this.Rectangle.Y + rectangle.Y;

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

16
src/ImageSharp/Memory/IManagedByteBuffer.cs

@ -1,16 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.Memory
{
/// <summary>
/// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s.
/// </summary>
internal interface IManagedByteBuffer : IBuffer<byte>
{
/// <summary>
/// Gets the managed array backing this buffer instance.
/// </summary>
byte[] Array { get; }
}
}

43
src/ImageSharp/Memory/ManagedBufferBase.cs

@ -1,43 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
using System.Runtime.InteropServices;
namespace SixLabors.Memory
{
/// <summary>
/// Provides a base class for <see cref="IBuffer{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
/// </summary>
internal abstract class ManagedBufferBase<T> : MemoryManager<T>, IBuffer<T>
where T : struct
{
private GCHandle pinHandle;
public bool IsMemoryOwner => true;
/// <summary>
/// Gets the object that should be pinned.
/// </summary>
protected abstract object GetPinnableObject();
public override unsafe MemoryHandle Pin(int elementIndex = 0)
{
if (!this.pinHandle.IsAllocated)
{
this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned);
}
void* ptr = (void*)this.pinHandle.AddrOfPinnedObject();
return new MemoryHandle(ptr, this.pinHandle);
}
public override void Unpin()
{
if (this.pinHandle.IsAllocated)
{
this.pinHandle.Free();
}
}
}
}

38
src/ImageSharp/Memory/MemoryAllocator.cs

@ -1,38 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.Memory
{
/// <summary>
/// Memory managers are used to allocate memory for image processing operations.
/// </summary>
public abstract class MemoryAllocator
{
/// <summary>
/// Allocates an <see cref="IBuffer{T}"/> of size <paramref name="length"/>, optionally
/// clearing the buffer before it gets returned.
/// </summary>
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="length">Size of the buffer to allocate</param>
/// <param name="clear">True to clear the backing memory of the buffer</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
internal abstract IBuffer<T> Allocate<T>(int length, bool clear)
where T : struct;
/// <summary>
/// Allocates an <see cref="IManagedByteBuffer"/>
/// </summary>
/// <param name="length">The requested buffer length</param>
/// <param name="clear">A value indicating whether to clean the buffer</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear);
/// <summary>
/// Releases all retained resources not being in use.
/// Eg: by resetting array pools and letting GC to free the arrays.
/// </summary>
public virtual void ReleaseRetainedResources()
{
}
}
}

67
src/ImageSharp/Memory/MemoryAllocatorExtensions.cs

@ -1,62 +1,37 @@
using SixLabors.Primitives; // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.Memory using System.Buffers;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Memory
{ {
/// <summary> /// <summary>
/// Extension methods for <see cref="MemoryAllocator"/>. /// Extension methods for <see cref="MemoryAllocator"/>.
/// </summary> /// </summary>
internal static class MemoryAllocatorExtensions internal static class MemoryAllocatorExtensions
{ {
/// <summary> public static Buffer2D<T> Allocate2D<T>(
/// Allocates a <see cref="IBuffer{T}"/> of size <paramref name="length"/>. this MemoryAllocator memoryAllocator,
/// Note: Depending on the implementation, the buffer may not cleared before int width,
/// returning, so it may contain data from an earlier use. int height,
/// </summary> AllocationOptions options = AllocationOptions.None)
/// <typeparam name="T">Type of the data stored in the buffer</typeparam>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/></param>
/// <param name="length">Size of the buffer to allocate</param>
/// <returns>A buffer of values of type <typeparamref name="T"/>.</returns>
public static IBuffer<T> Allocate<T>(this MemoryAllocator memoryAllocator, int length)
where T : struct
{
return memoryAllocator.Allocate<T>(length, false);
}
public static IBuffer<T> AllocateClean<T>(this MemoryAllocator memoryAllocator, int length)
where T : struct
{
return memoryAllocator.Allocate<T>(length, true);
}
public static IManagedByteBuffer AllocateManagedByteBuffer(this MemoryAllocator memoryAllocator, int length)
{
return memoryAllocator.AllocateManagedByteBuffer(length, false);
}
public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryAllocator memoryAllocator, int length)
{
return memoryAllocator.AllocateManagedByteBuffer(length, true);
}
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height, bool clear)
where T : struct where T : struct
{ {
IBuffer<T> buffer = memoryAllocator.Allocate<T>(width * height, clear); 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) public static Buffer2D<T> Allocate2D<T>(
where T : struct => this MemoryAllocator memoryAllocator,
Allocate2D<T>(memoryAllocator, size.Width, size.Height, false); Size size,
AllocationOptions options = AllocationOptions.None)
public static Buffer2D<T> Allocate2D<T>(this MemoryAllocator memoryAllocator, int width, int height)
where T : struct =>
Allocate2D<T>(memoryAllocator, width, height, false);
public static Buffer2D<T> AllocateClean2D<T>(this MemoryAllocator memoryAllocator, int width, int height)
where T : struct => where T : struct =>
Allocate2D<T>(memoryAllocator, width, height, true); Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);
/// <summary> /// <summary>
/// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea)

38
src/ImageSharp/Memory/BufferExtensions.cs → src/ImageSharp/Memory/MemoryOwnerExtensions.cs

@ -2,17 +2,24 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.IO; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SixLabors.Memory namespace SixLabors.ImageSharp.Memory
{ {
internal static class BufferExtensions /// <summary>
/// Extension methods for <see cref="IMemoryOwner{T}"/>
/// </summary>
internal static class MemoryOwnerExtensions
{ {
[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 +28,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 +41,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,24 +51,13 @@ 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());
public static void Read(this Stream stream, IManagedByteBuffer buffer)
{
stream.Read(buffer.Array, 0, buffer.Length());
}
public static void Write(this Stream stream, IManagedByteBuffer buffer)
{
stream.Write(buffer.Array, 0, buffer.Length());
}
} }
} }

103
src/ImageSharp/Memory/MemorySource.cs

@ -0,0 +1,103 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.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;
}
}
}

19
src/ImageSharp/Memory/SimpleGcMemoryAllocator.cs

@ -1,19 +0,0 @@
namespace SixLabors.Memory
{
/// <summary>
/// Implements <see cref="MemoryAllocator"/> by newing up arrays by the GC on every allocation requests.
/// </summary>
public sealed class SimpleGcMemoryAllocator : MemoryAllocator
{
/// <inheritdoc />
internal override IBuffer<T> Allocate<T>(int length, bool clear)
{
return new BasicArrayBuffer<T>(new T[length]);
}
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
{
return new BasicByteBuffer(new byte[length]);
}
}
}

15
src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs

@ -5,17 +5,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{ {
internal static class ExifConstants internal static class ExifConstants
{ {
public static readonly byte[] Header = { public static readonly byte[] LittleEndianByteOrderMarker = {
(byte)'E',
(byte)'x',
(byte)'i',
(byte)'f',
0x00,
0x00,
(byte)'I', (byte)'I',
(byte)'I', (byte)'I',
0x2A, 0x2A,
0x00, 0x00,
}; };
public static readonly byte[] BigEndianByteOrderMarker = {
(byte)'M',
(byte)'M',
0x00,
0x2A
};
} }
} }

5
src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO; using System.IO;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary> /// <summary>
/// The byte array to read the EXIF profile from. /// The byte array to read the EXIF profile from.
/// </summary> /// </summary>
private readonly byte[] data; private byte[] data;
/// <summary> /// <summary>
/// The collection of EXIF values /// The collection of EXIF values
@ -86,7 +85,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (other.data != null) if (other.data != null)
{ {
this.data = new byte[other.data.Length]; this.data = new byte[other.data.Length];
Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); other.data.AsSpan().CopyTo(this.data);
} }
} }

21
src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
private Endianness endianness = Endianness.BigEndian; private Endianness endianness = Endianness.BigEndian;
private uint exifOffset; private uint exifOffset;
private uint gpsOffset; private uint gpsOffset;
private int startIndex;
public ExifReader(byte[] exifData) public ExifReader(byte[] exifData)
{ {
@ -77,20 +76,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{ {
var values = new List<ExifValue>(); var values = new List<ExifValue>();
if (this.ReadString(4) == "Exif")
{
if (this.ReadUInt16() != 0)
{
return values;
}
this.startIndex = 6;
}
else
{
this.position = 0;
}
if (this.ReadString(2) == "II") if (this.ReadString(2) == "II")
{ {
this.endianness = Endianness.LittleEndian; this.endianness = Endianness.LittleEndian;
@ -169,7 +154,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
private void AddValues(List<ExifValue> values, int index) private void AddValues(List<ExifValue> values, int index)
{ {
this.position = this.startIndex + index; this.position = index;
int count = this.ReadUInt16(); int count = this.ReadUInt16();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
@ -353,7 +338,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{ {
int oldIndex = this.position; int oldIndex = this.position;
uint newIndex = this.ConvertToUInt32(offsetBuffer) + (uint)this.startIndex; uint newIndex = this.ConvertToUInt32(offsetBuffer);
// Ensure that the new index does not overrun the data // Ensure that the new index does not overrun the data
if (newIndex > int.MaxValue) if (newIndex > int.MaxValue)
@ -454,7 +439,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{ {
if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long))
{ {
this.ThumbnailOffset = (uint)value.Value + (uint)this.startIndex; this.ThumbnailOffset = (uint)value.Value;
} }
else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long)
{ {

30
src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary> /// </summary>
internal sealed class ExifWriter internal sealed class ExifWriter
{ {
/// <summary>
/// The start index.
/// </summary>
private const int StartIndex = 6;
/// <summary> /// <summary>
/// Which parts will be written. /// Which parts will be written.
/// </summary> /// </summary>
@ -51,6 +46,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </returns> /// </returns>
public byte[] GetData() public byte[] GetData()
{ {
uint startIndex = 0;
uint length; uint length;
int exifIndex = -1; int exifIndex = -1;
int gpsIndex = -1; int gpsIndex = -1;
@ -86,14 +82,20 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return null; return null;
} }
length += 10 + 4 + 2; // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total
length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length;
length += 4 + 2;
byte[] result = new byte[length]; byte[] result = new byte[length];
ExifConstants.Header.AsSpan().CopyTo(result); // 0-9 int i = 0;
// the byte order marker for little-endian, followed by the number 42 and a 0
ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i));
i += ExifConstants.LittleEndianByteOrderMarker.Length;
int i = 10; uint ifdOffset = ((uint)i - startIndex) + 4;
uint ifdOffset = ((uint)i - StartIndex) + 4;
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength;
if (exifLength > 0) if (exifLength > 0)
@ -109,18 +111,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
i = WriteUInt32(ifdOffset, result, i); i = WriteUInt32(ifdOffset, result, i);
i = this.WriteHeaders(this.ifdIndexes, result, i); i = this.WriteHeaders(this.ifdIndexes, result, i);
i = WriteUInt32(thumbnailOffset, result, i); i = WriteUInt32(thumbnailOffset, result, i);
i = this.WriteData(this.ifdIndexes, result, i); i = this.WriteData(startIndex, this.ifdIndexes, result, i);
if (exifLength > 0) if (exifLength > 0)
{ {
i = this.WriteHeaders(this.exifIndexes, result, i); i = this.WriteHeaders(this.exifIndexes, result, i);
i = this.WriteData(this.exifIndexes, result, i); i = this.WriteData(startIndex, this.exifIndexes, result, i);
} }
if (gpsLength > 0) if (gpsLength > 0)
{ {
i = this.WriteHeaders(this.gpsIndexes, result, i); i = this.WriteHeaders(this.gpsIndexes, result, i);
i = this.WriteData(this.gpsIndexes, result, i); i = this.WriteData(startIndex, this.gpsIndexes, result, i);
} }
WriteUInt16((ushort)0, result, i); WriteUInt16((ushort)0, result, i);
@ -257,7 +259,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset; return newOffset;
} }
private int WriteData(List<int> indexes, Span<byte> destination, int offset) private int WriteData(uint startIndex, List<int> indexes, Span<byte> destination, int offset)
{ {
if (this.dataOffsets.Count == 0) if (this.dataOffsets.Count == 0)
{ {
@ -272,7 +274,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
ExifValue value = this.values[index]; ExifValue value = this.values[index];
if (value.Length > 4) if (value.Length > 4)
{ {
WriteUInt32((uint)(newOffset - StartIndex), destination, this.dataOffsets[i++]); WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]);
newOffset = this.WriteValue(value, destination, newOffset); newOffset = this.WriteValue(value, destination, newOffset);
} }
} }

11
src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs

@ -149,17 +149,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
#endif #endif
/// <summary>
/// Extends the profile with additional data.
/// </summary>
/// <param name="bytes">The array containing addition profile data.</param>
public void Extend(byte[] bytes)
{
int currentLength = this.data.Length;
Array.Resize(ref this.data, currentLength + bytes.Length);
Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length);
}
/// <summary> /// <summary>
/// Checks for signs of a corrupt profile. /// Checks for signs of a corrupt profile.
/// </summary> /// </summary>

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>

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

Loading…
Cancel
Save