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" />
</TypePattern>
&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: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/=DC/@EntryIndexedValue">DC</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
$isVersionTag = $env:APPVEYOR_REPO_TAG_NAME -match $tagRegex
if($isVersionTag){
if($isVersionTag) {
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}"
}
}else {
}
else {
Write-Debug "Untagged"
$lastTag = (git tag --list --sort=-taggerdate) | Out-String

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

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

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

@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
using SixLabors.Primitives;
using SixLabors.Shapes;
@ -45,7 +48,7 @@ namespace SixLabors.ImageSharp.Primitives
var start = new PointF(this.Bounds.Left - 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();
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.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -65,8 +68,8 @@ namespace SixLabors.ImageSharp.Processing
{
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.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.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -118,8 +120,8 @@ namespace SixLabors.ImageSharp.Processing
internal override void Apply(Span<float> scanline, int x, int y)
{
// Create a span for colors
using (IBuffer<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length))
using (IMemoryOwner<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.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.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory;
@ -153,8 +155,8 @@ namespace SixLabors.ImageSharp.Processing
int patternY = y % this.pattern.Rows;
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.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.
using System;
using System.Buffers;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -134,14 +136,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
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);
Parallel.For(
ParallelFor.WithConfiguration(
minY,
maxY,
configuration.ParallelOptions,
configuration,
y =>
{
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.
using System;
using System.Buffers;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -54,10 +55,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
// If there's no reason for blending, then avoid it.
if (this.IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush))
{
Parallel.For(
ParallelFor.WithConfiguration(
minY,
maxY,
configuration.ParallelOptions,
configuration,
y =>
{
source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
@ -76,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
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(
source,
sourceRectangle,
@ -84,10 +85,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{
amount.GetSpan().Fill(1f);
Parallel.For(
ParallelFor.WithConfiguration(
minY,
maxY,
configuration.ParallelOptions,
configuration,
y =>
{
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.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
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))
{
int scanlineWidth = maxX - minX;
using (IBuffer<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections))
using (IBuffer<float> bScanline = source.MemoryAllocator.Allocate<float>(scanlineWidth))
using (IMemoryOwner<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections))
using (IMemoryOwner<float> bScanline = source.MemoryAllocator.Allocate<float>(scanlineWidth))
{
bool scanlineDirty = true;
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.
using System;
using System.Buffers;
using System.Collections.Generic;
using SixLabors.Fonts;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Utils;
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.
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 (IBuffer<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width))
using (IMemoryOwner<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections))
using (IMemoryOwner<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width))
{
float subpixelFraction = 1f / 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.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -138,8 +140,8 @@ namespace SixLabors.ImageSharp.Processing
{
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.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.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -65,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets the colors.
/// </summary>
protected IBuffer<TPixel> Colors { get; }
protected IMemoryOwner<TPixel> Colors { get; }
/// <summary>
/// Gets the color for a single pixel.
@ -96,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing
}
else
{
using (IBuffer<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length))
{
Span<float> amountSpan = amountBuffer.GetSpan();

4
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

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

1
src/ImageSharp/Advanced/IPixelSource.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
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.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp
{
/// <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.Buffers;
using System.Threading.Tasks;
using SixLabors.Memory;
@ -10,11 +11,11 @@ namespace SixLabors.ImageSharp
internal static class ParallelFor
{
/// <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>
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>
@ -32,23 +33,23 @@ namespace SixLabors.ImageSharp
int toExclusive,
Configuration configuration,
int bufferLength,
Action<int, IBuffer<T>> body)
Action<int, IMemoryOwner<T>> body)
where T : struct
{
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
ParallelOptions parallelOptions = configuration.ParallelOptions;
ParallelOptions parallelOptions = configuration.GetParallelOptions();
IBuffer<T> InitBuffer()
IMemoryOwner<T> InitBuffer()
{
return memoryAllocator.Allocate<T>(bufferLength);
}
void CleanUpBuffer(IBuffer<T> buffer)
void CleanUpBuffer(IMemoryOwner<T> buffer)
{
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);
return buffer;

21
src/ImageSharp/Configuration.cs

@ -27,6 +27,8 @@ namespace SixLabors.ImageSharp
/// </summary>
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance);
private int maxDegreeOfParallelism = Environment.ProcessorCount;
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary>
@ -55,9 +57,22 @@ namespace SixLabors.ImageSharp
public static Configuration Default { get; } = Lazy.Value;
/// <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>
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>
/// Gets the currently registered <see cref="IImageFormat"/>s.
@ -114,7 +129,7 @@ namespace SixLabors.ImageSharp
{
return new Configuration
{
ParallelOptions = this.ParallelOptions,
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
ImageFormatsManager = this.ImageFormatsManager,
MemoryAllocator = this.MemoryAllocator,
ImageOperationsProvider = this.ImageOperationsProvider,

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

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

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

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

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

@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -321,11 +322,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (imageDescriptor.LocalColorTableFlag)
{
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);
}
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());
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;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, true);
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream
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.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
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.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Gif
@ -32,17 +35,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The prefix buffer.
/// </summary>
private readonly IBuffer<int> prefix;
private readonly IMemoryOwner<int> prefix;
/// <summary>
/// The suffix buffer.
/// </summary>
private readonly IBuffer<int> suffix;
private readonly IMemoryOwner<int> suffix;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly IBuffer<int> pixelStack;
private readonly IMemoryOwner<int> pixelStack;
/// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class
@ -57,9 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.stream = stream;
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, true);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, true);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, true);
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
}
/// <summary>

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

@ -2,9 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Gif
@ -66,12 +69,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The hash table.
/// </summary>
private readonly IBuffer<int> hashTable;
private readonly IMemoryOwner<int> hashTable;
/// <summary>
/// The code table.
/// </summary>
private readonly IBuffer<int> codeTable;
private readonly IMemoryOwner<int> codeTable;
/// <summary>
/// Define the storage for the packet accumulator.
@ -168,8 +171,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth)
{
this.initialCodeSize = Math.Max(2, colorDepth);
this.hashTable = memoryAllocator.Allocate<int>(HashSize, true);
this.codeTable = memoryAllocator.Allocate<int>(HashSize, true);
this.hashTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean);
this.codeTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean);
}
/// <summary>

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

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

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

@ -7,6 +7,7 @@ using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Common.Tuples;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
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.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// The collection of lookup tables used for fast AC entropy scan decoding.
@ -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>
public FastACTables(MemoryAllocator memoryAllocator)
{
this.tables = memoryAllocator.AllocateClean2D<short>(512, 4);
this.tables = memoryAllocator.Allocate2D<short>(512, 4, AllocationOptions.Clean);
}
/// <summary>
@ -35,10 +37,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
/// <summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="PdfJsFrameComponent.ACHuffmanTableId"/>
/// </summary>
/// Gets a reference to the first element of the AC table indexed by <see cref="JpegComponent.ACHuffmanTableId"/> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref short GetAcTableReference(PdfJsFrameComponent component)
public ref short GetAcTableReference(JpegComponent component)
{
return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0];
}
@ -48,11 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
/// <param name="index">The table index.</param>
/// <param name="acHuffmanTables">The collection of AC Huffman tables.</param>
public void BuildACTableLut(int index, PdfJsHuffmanTables acHuffmanTables)
public void BuildACTableLut(int index, HuffmanTables acHuffmanTables)
{
const int FastBits = ScanDecoder.FastBits;
Span<short> fastAC = this.tables.GetRowSpan(index);
ref PdfJsHuffmanTable huffman = ref acHuffmanTables[index];
ref HuffmanTable huffman = ref acHuffmanTables[index];
int i;
for (i = 0; i < (1 << FastBits); i++)

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

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

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

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

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

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

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

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

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

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

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.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represents a Huffman Table
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PdfJsHuffmanTable
internal unsafe struct HuffmanTable
{
/// <summary>
/// Gets the max code array
@ -40,15 +43,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
public FixedInt16Buffer257 Sizes;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsHuffmanTable"/> struct.
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="count">The code lengths</param>
/// <param name="values">The huffman values</param>
public PdfJsHuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> count, ReadOnlySpan<byte> values)
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> count, ReadOnlySpan<byte> values)
{
const int Length = 257;
using (IBuffer<short> huffcode = memoryAllocator.Allocate<short>(Length))
using (IMemoryOwner<short> huffcode = memoryAllocator.Allocate<short>(Length))
{
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.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Defines a 2 pairs of huffman tables
/// Defines a 2 pairs of huffman tables.
/// </summary>
internal sealed class PdfJsHuffmanTables
internal sealed class HuffmanTables
{
private readonly PdfJsHuffmanTable[] tables = new PdfJsHuffmanTable[4];
private readonly HuffmanTable[] tables = new HuffmanTable[4];
/// <summary>
/// Gets or sets the table at the given index.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The <see cref="PdfJsHuffmanTable"/></returns>
public ref PdfJsHuffmanTable this[int index]
/// <returns>The <see cref="HuffmanTable"/></returns>
public ref HuffmanTable this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this.tables[index];

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

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

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

@ -3,13 +3,14 @@
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <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>
[StructLayout(LayoutKind.Sequential)]
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.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Represents a single frame component
/// </summary>
internal class PdfJsFrameComponent : IDisposable, IJpegComponent
internal class JpegComponent : IDisposable, IJpegComponent
{
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.Frame = frame;
@ -89,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
public int ACHuffmanTableId { get; set; }
public PdfJsFrame Frame { get; }
public JpegFrame Frame { get; }
/// <inheritdoc/>
public void Dispose()
@ -125,11 +124,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
else
{
PdfJsFrameComponent c0 = this.Frame.Components[0];
JpegComponent c0 = this.Frame.Components[0];
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)]
@ -140,16 +139,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[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);
}
// 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);
ref Block8x8 blockRef = ref this.GetBlockReference(column, row);
return ref Unsafe.As<Block8x8, short>(ref blockRef);
}
}

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

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

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

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

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

@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@ -37,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary>
/// Temporal buffer to store a row of colors.
/// </summary>
private readonly IBuffer<Vector4> rgbaBuffer;
private readonly IMemoryOwner<Vector4> rgbaBuffer;
/// <summary>
/// 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");
/// <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>
/// <param name="bytesToCheck">The bytes to check</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.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Decodes the Huffman encoded spectral scan.
@ -23,13 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// LUT Bias[n] = (-1 << n) + 1
private static readonly int[] Bias = { 0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, -4095, -8191, -16383, -32767 };
private readonly PdfJsFrame frame;
private readonly PdfJsHuffmanTables dcHuffmanTables;
private readonly PdfJsHuffmanTables acHuffmanTables;
private readonly JpegFrame frame;
private readonly HuffmanTables dcHuffmanTables;
private readonly HuffmanTables acHuffmanTables;
private readonly FastACTables fastACTables;
private readonly DoubleBufferedStreamReader stream;
private readonly PdfJsFrameComponent[] components;
private readonly JpegComponent[] components;
private readonly ZigZag dctZigZag;
// The restart interval.
@ -97,9 +96,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// <param name="successiveLow">The successive approximation bit low end.</param>
public ScanDecoder(
DoubleBufferedStreamReader stream,
PdfJsFrame frame,
PdfJsHuffmanTables dcHuffmanTables,
PdfJsHuffmanTables acHuffmanTables,
JpegFrame frame,
HuffmanTables dcHuffmanTables,
HuffmanTables acHuffmanTables,
FastACTables fastACTables,
int componentIndex,
int componentsLength,
@ -177,10 +176,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++)
{
PdfJsFrameComponent component = this.components[k];
JpegComponent component = this.components[k];
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -231,13 +230,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
private void ParseBaselineDataNonInterleaved()
{
PdfJsFrameComponent component = this.components[this.componentIndex];
JpegComponent component = this.components[this.componentIndex];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int mcu = 0;
@ -296,8 +295,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// Scan an interleaved mcu... process components in order
for (int k = 0; k < this.componentsLength; k++)
{
PdfJsFrameComponent component = this.components[k];
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
JpegComponent component = this.components[k];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
int h = component.HorizontalSamplingFactor;
int v = component.VerticalSamplingFactor;
@ -345,13 +344,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
private void ParseProgressiveDataNonInterleaved()
{
PdfJsFrameComponent component = this.components[this.componentIndex];
JpegComponent component = this.components[this.componentIndex];
int w = component.WidthInBlocks;
int h = component.HeightInBlocks;
ref PdfJsHuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref PdfJsHuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId];
ref short fastACRef = ref this.fastACTables.GetAcTableReference(component);
int mcu = 0;
@ -396,11 +395,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
private void DecodeBlockBaseline(
PdfJsFrameComponent component,
JpegComponent component,
int row,
int col,
ref PdfJsHuffmanTable dcTable,
ref PdfJsHuffmanTable acTable,
ref HuffmanTable dcTable,
ref HuffmanTable acTable,
ref short fastACRef)
{
this.CheckBits();
@ -411,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
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 dc = component.DcPredictor + diff;
@ -475,10 +474,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
private void DecodeBlockProgressiveDC(
PdfJsFrameComponent component,
JpegComponent component,
int row,
int col,
ref PdfJsHuffmanTable dcTable)
ref HuffmanTable dcTable)
{
if (this.spectralEnd != 0)
{
@ -487,7 +486,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.CheckBits();
ref short blockDataRef = ref component.GetBlockDataReference(row, col);
ref short blockDataRef = ref component.GetBlockDataReference(col, row);
if (this.successiveHigh == 0)
{
@ -511,10 +510,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
private void DecodeBlockProgressiveAC(
PdfJsFrameComponent component,
JpegComponent component,
int row,
int col,
ref PdfJsHuffmanTable acTable,
ref HuffmanTable acTable,
ref short fastACRef)
{
if (this.spectralStart == 0)
@ -522,7 +521,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
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)
{
@ -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;
@ -805,7 +804,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(InliningOptions.ShortMethod)]
private int DecodeHuffman(ref PdfJsHuffmanTable table)
private int DecodeHuffman(ref HuffmanTable table)
{
this.CheckBits();
@ -830,7 +829,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
}
[MethodImpl(InliningOptions.ColdPath)]
private int DecodeHuffmanSlow(ref PdfJsHuffmanTable table)
private int DecodeHuffmanSlow(ref HuffmanTable table)
{
// Naive test is to shift the code_buffer down so k bits are
// valid, then test against MaxCode. To speed this up, we've
@ -941,7 +940,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
for (int i = 0; i < this.components.Length; i++)
{
PdfJsFrameComponent c = this.components[i];
JpegComponent c = this.components[i];
c.DcPredictor = 0;
}

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

@ -5,6 +5,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
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.
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -24,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
using (var decoder = new JpegDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
@ -35,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new PdfJsJpegDecoderCore(configuration, this))
using (var decoder = new JpegDecoderCore(configuration, this))
{
return decoder.Identify(stream);
}

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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@ -19,14 +21,14 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Performs the jpeg decoding operation.
/// Originally ported from <see href="https://github.com/mozilla/pdf.js/blob/master/src/core/jpg.js"/>
/// with additional fixes for both performance and common encoding errors.
/// </summary>
internal sealed class PdfJsJpegDecoderCore : IRawJpegData
internal sealed class JpegDecoderCore : IRawJpegData
{
/// <summary>
/// The only supported precision
@ -51,12 +53,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// The DC HUffman tables
/// </summary>
private PdfJsHuffmanTables dcHuffmanTables;
private HuffmanTables dcHuffmanTables;
/// <summary>
/// The AC HUffman tables
/// </summary>
private PdfJsHuffmanTables acHuffmanTables;
private HuffmanTables acHuffmanTables;
/// <summary>
/// The fast AC tables used for entropy decoding
@ -69,10 +71,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private ushort resetInterval;
/// <summary>
/// Whether the image has a EXIF header
/// Whether the image has an EXIF marker
/// </summary>
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>
/// Contains information about the JFIF marker
/// </summary>
@ -84,11 +101,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
private AdobeMarker adobe;
/// <summary>
/// Initializes a new instance of the <see cref="PdfJsJpegDecoderCore" /> class.
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options.</param>
public PdfJsJpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
public JpegDecoderCore(Configuration configuration, IJpegDecoderOptions options)
{
this.configuration = configuration ?? Configuration.Default;
this.IgnoreMetadata = options.IgnoreMetadata;
@ -97,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Gets the frame
/// </summary>
public PdfJsFrame Frame { get; private set; }
public JpegFrame Frame { get; private set; }
/// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; }
@ -146,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <summary>
/// Gets the components.
/// </summary>
public PdfJsFrameComponent[] Components => this.Frame.Components;
public JpegComponent[] Components => this.Frame.Components;
/// <inheritdoc/>
IEnumerable<IJpegComponent> IRawJpegData.Components => this.Components;
@ -159,14 +176,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// </summary>
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <returns>The <see cref="PdfJsFileMarker"/></returns>
public static PdfJsFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
/// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, DoubleBufferedStreamReader stream)
{
int value = stream.Read(marker, 0, 2);
if (value == 0)
{
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
if (marker[0] == JpegConstants.Markers.XFF)
@ -179,16 +196,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
int suffix = stream.ReadByte();
if (suffix == -1)
{
return new PdfJsFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2);
}
m = suffix;
}
return new PdfJsFileMarker((byte)m, stream.Position - 2);
return new JpegFileMarker((byte)m, stream.Position - 2);
}
return new PdfJsFileMarker(marker[1], stream.Position - 2, true);
return new JpegFileMarker(marker[1], stream.Position - 2, true);
}
/// <summary>
@ -201,6 +218,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
where TPixel : struct, IPixel<TPixel>
{
this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties();
return this.PostProcessIntoImage<TPixel>();
}
@ -212,6 +231,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties();
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.
this.InputStream.Read(this.markerBuffer, 0, 2);
var fileMarker = new PdfJsFileMarker(this.markerBuffer[1], 0);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
throw new ImageFormatException("Missing SOI marker.");
@ -236,14 +257,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.InputStream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2);
fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2);
// Only assign what we need
if (!metadataOnly)
{
this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new PdfJsHuffmanTables();
this.acHuffmanTables = new PdfJsHuffmanTables();
this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new HuffmanTables();
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
}
@ -403,6 +424,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
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>
/// Assigns derived metadata properties to <see cref="MetaData"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary>
@ -431,11 +478,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile);
}
}
}
if (this.MetaData.IccProfile?.CheckIsValid() == false)
{
this.MetaData.IccProfile = null;
}
/// <summary>
/// Extends the profile with additional data.
/// </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>
@ -469,7 +524,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp1Marker(int remaining)
{
if (remaining < 6 || this.IgnoreMetadata)
const int Exif00 = 6;
if (remaining < Exif00 || this.IgnoreMetadata)
{
// Skip the application header length
this.InputStream.Skip(remaining);
@ -482,7 +538,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{
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))
{
this.isIcc = true;
byte[] profile = new byte[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
{
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
@ -630,7 +697,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="remaining">The remaining bytes in the segment block.</param>
/// <param name="frameMarker">The current frame marker.</param>
/// <param name="metadataOnly">Whether to parse metadata only</param>
private void ProcessStartOfFrameMarker(int remaining, PdfJsFileMarker frameMarker, bool metadataOnly)
private void ProcessStartOfFrameMarker(int remaining, in JpegFileMarker frameMarker, bool metadataOnly)
{
if (this.Frame != null)
{
@ -645,7 +712,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
throw new ImageFormatException("Only 8-Bit precision supported.");
}
this.Frame = new PdfJsFrame
this.Frame = new JpegFrame
{
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2,
@ -667,7 +734,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
{
// No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[this.Frame.ComponentCount];
this.Frame.Components = new PdfJsFrameComponent[this.Frame.ComponentCount];
this.Frame.Components = new JpegComponent[this.Frame.ComponentCount];
this.ColorSpace = this.DeduceJpegColorSpace();
for (int i = 0; i < this.Frame.ComponentCount; i++)
@ -686,7 +753,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
maxV = v;
}
var component = new PdfJsFrameComponent(this.configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
var component = new JpegComponent(this.configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
@ -709,7 +776,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="remaining">The remaining bytes in the segment block.</param>
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());
for (int i = 2; i < remaining;)
@ -717,7 +784,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
byte huffmanTableSpec = (byte)this.InputStream.ReadByte();
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());
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);
}
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);
@ -794,7 +861,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
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();
component.DCHuffmanTableId = tableSpec >> 4;
component.ACHuffmanTableId = tableSpec & 15;
@ -831,9 +898,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
/// <param name="codeLengths">The codelengths</param>
/// <param name="values">The values</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BuildHuffmanTable(PdfJsHuffmanTables tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
tables[index] = new PdfJsHuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
}
/// <summary>

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

@ -4,9 +4,11 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@ -214,6 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker.
this.WriteApplicationHeader(image.MetaData);
// Write Exif and ICC profiles
this.WriteProfiles(image);
// Write the quantization tables.
@ -430,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="meta">The image meta data.</param>
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[1] = JpegConstants.Markers.SOI;
@ -620,27 +623,59 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void WriteExifProfile(ExifProfile exifProfile)
{
const int Max = 65533;
const int MaxBytesApp1 = 65533;
const int MaxBytesWithExifId = 65527;
byte[] data = exifProfile?.ToByteArray();
if (data == null || data.Length == 0)
{
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[1] = JpegConstants.Markers.APP1; // Application Marker
this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF);
this.buffer[2] = (byte)((app1Length >> 8) & 0xFF);
this.buffer[3] = (byte)(app1Length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
this.outputStream.Write(data, 0, data.Length);
}
/// <summary>
@ -652,7 +687,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception>
private void WriteIccProfile(IccProfile iccProfile)
{
// Just incase someone set the value to null by accident.
if (iccProfile == null)
{
return;
@ -908,7 +942,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="length">The marker length.</param>
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[1] = marker;
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
{
/// <summary>
/// Contains a list of of chunk types.
/// Contains a list of chunk types.
/// </summary>
internal enum PngChunkType : uint
{
@ -55,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </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.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -259,6 +261,15 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Text:
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;
case PngChunkType.End:
this.isEndChunkReached = true;
@ -370,7 +381,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
buffer = this.MemoryAllocator.AllocateCleanManagedByteBuffer(bytesPerScanline * 8 / bits);
buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
byte[] result = buffer.Array;
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
@ -437,8 +448,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline = this.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.scanline = this.configuration.MemoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.previousScanline = this.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -1170,7 +1181,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Decodes and assigns marker colors that identify transparent pixels in non indexed images
/// </summary>
/// <param name="alpha">The aplha tRNS array</param>
/// <param name="alpha">The alpha tRNS array</param>
private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha)
{
if (this.pngColorType == PngColorType.Rgb)
@ -1217,7 +1228,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <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)
where TPixel : struct, IPixel<TPixel>
{
@ -1445,7 +1456,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private IManagedByteBuffer ReadChunkData(int length)
{
// 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);

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

@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -231,6 +232,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
this.WriteExifChunk(stream, image);
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream);
stream.Flush();
@ -490,7 +492,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Calculates the correct number of bytes per pixel for the given color type.
/// </summary>
/// <returns>The <see cref="int"/></returns>
/// <returns>Bytes per pixel</returns>
private int CalculateBytesPerPixel()
{
switch (this.pngColorType)
@ -648,6 +650,22 @@ namespace SixLabors.ImageSharp.Formats.Png
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>
/// Writes the gamma information to the stream.
/// </summary>
@ -678,9 +696,9 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerScanline = this.width * this.bytesPerPixel;
int resultLength = this.bytesPerScanline + 1;
this.previousScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.rawScanline = this.memoryAllocator.AllocateCleanManagedByteBuffer(this.bytesPerScanline);
this.result = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.rawScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.result = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
switch (this.pngFilterMethod)
{
@ -689,29 +707,29 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngFilterMethod.Sub:
this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Up:
this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Average:
this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Paeth:
this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
break;
case PngFilterMethod.Adaptive:
this.sub = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.up = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.average = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.paeth = this.memoryAllocator.AllocateCleanManagedByteBuffer(resultLength);
this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
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;
// TODO: This could be useful elsewhere.
namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
namespace SixLabors.ImageSharp.IO
{
/// <summary>
/// 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.length = (int)stream.Length;
this.managedBuffer = memoryAllocator.AllocateCleanManagedByteBuffer(ChunkLength);
this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength, AllocationOptions.Clean);
this.bufferChunk = this.managedBuffer.Array;
}

1
src/ImageSharp/Image.Decode.cs

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

106
src/ImageSharp/Image.WrapMemory.cs

@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -13,8 +16,6 @@ namespace SixLabors.ImageSharp
/// </content>
public static partial class Image
{
// TODO: This is a WIP API, should be public when finished.
/// <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.
@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp
/// <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>
internal static Image<TPixel> WrapMemory<TPixel>(
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
Memory<TPixel> pixelMemory,
int width,
@ -34,26 +35,117 @@ namespace SixLabors.ImageSharp
ImageMetaData metaData)
where TPixel : struct, IPixel<TPixel>
{
var buffer = new ConsumedBuffer<TPixel>(pixelMemory);
return new Image<TPixel>(config, buffer, width, height, metaData);
var memorySource = new MemorySource<TPixel>(pixelMemory);
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>
/// 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 memory is being observed, the caller remains responsible for managing it's lifecycle.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <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>
internal static Image<TPixel> WrapMemory<TPixel>(
public static Image<TPixel> WrapMemory<TPixel>(
Memory<TPixel> pixelMemory,
int width,
int height)
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.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;

5
src/ImageSharp/ImageFrameCollection.cs

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

16
src/ImageSharp/ImageFrame{TPixel}.cs

@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -86,16 +88,16 @@ namespace SixLabors.ImageSharp
this.configuration = configuration;
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.Clear(configuration.ParallelOptions, backgroundColor);
this.Clear(configuration.GetParallelOptions(), backgroundColor);
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class wrapping an existing buffer.
/// </summary>
internal ImageFrame(Configuration configuration, int width, int height, IBuffer<TPixel> consumedBuffer)
: this(configuration, width, height, consumedBuffer, new ImageFrameMetaData())
internal ImageFrame(Configuration configuration, int width, int height, MemorySource<TPixel> memorySource)
: this(configuration, width, height, memorySource, new ImageFrameMetaData())
{
}
@ -106,7 +108,7 @@ namespace SixLabors.ImageSharp
Configuration configuration,
int width,
int height,
IBuffer<TPixel> consumedBuffer,
MemorySource<TPixel> memorySource,
ImageFrameMetaData metaData)
{
Guard.NotNull(configuration, nameof(configuration));
@ -116,7 +118,7 @@ namespace SixLabors.ImageSharp
this.configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = new Buffer2D<TPixel>(consumedBuffer, width, height);
this.PixelBuffer = new Buffer2D<TPixel>(memorySource, width, height);
this.MetaData = metaData;
}
@ -272,7 +274,7 @@ namespace SixLabors.ImageSharp
this.Height,
this.configuration,
this.Width,
(int y, IBuffer<Vector4> tempRowBuffer) =>
(int y, IMemoryOwner<Vector4> tempRowBuffer) =>
{
Span<TPixel> sourceRow = this.GetPixelRowSpan(y);
Span<TPixel2> targetRow = target.GetPixelRowSpan(y);

14
src/ImageSharp/ImageSharp.csproj

@ -29,27 +29,19 @@
<DebugType Condition="$(codecov) == ''">portable</DebugType>
<DebugSymbols>True</DebugSymbols>
<Features>IOperation</Features>
<LangVersion>7.3</LangVersion>
<LangVersion>Latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</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" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta007">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</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' ">
<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 SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@ -84,23 +85,14 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// consuming an external buffer instance.
/// wrapping an external <see cref="MemorySource{T}"/>
/// </summary>
internal Image(Configuration configuration, IBuffer<TPixel> consumedBuffer, int width, int height)
: 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)
internal Image(Configuration configuration, MemorySource<TPixel> memorySource, int width, int height, ImageMetaData metadata)
{
this.configuration = configuration;
this.PixelType = new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
this.MetaData = metadata;
this.frames = new ImageFrameCollection<TPixel>(this, width, height, consumedBuffer);
this.frames = new ImageFrameCollection<TPixel>(this, width, height, memorySource);
}
/// <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.Runtime.CompilerServices;
using SixLabors.Primitives;
namespace SixLabors.Memory
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// 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)
where T : struct
{
return buffer.Buffer.GetSpan();
return buffer.MemorySource.GetSpan();
}
/// <summary>
@ -61,7 +62,7 @@ namespace SixLabors.Memory
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.Buffer.Memory.Slice(y * buffer.Width, buffer.Width);
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}
/// <summary>

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

@ -3,9 +3,10 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.Primitives;
namespace SixLabors.Memory
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Represents a buffer of value type objects
@ -15,15 +16,17 @@ namespace SixLabors.Memory
internal sealed class Buffer2D<T> : IDisposable
where T : struct
{
private MemorySource<T> memorySource;
/// <summary>
/// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </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="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.Height = height;
}
@ -39,13 +42,13 @@ namespace SixLabors.Memory
public int Height { get; private set; }
/// <summary>
/// Gets the backing <see cref="IBuffer{T}"/>
/// Gets the backing <see cref="MemorySource{T}"/>
/// </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>
/// Gets a reference to the element at the specified position.
@ -59,8 +62,8 @@ namespace SixLabors.Memory
get
{
ImageSharp.DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Buffer.GetSpan();
ImageSharp.DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Span;
return ref span[(this.Width * y) + x];
}
}
@ -70,7 +73,7 @@ namespace SixLabors.Memory
/// </summary>
public void Dispose()
{
this.Buffer?.Dispose();
this.MemorySource.Dispose();
}
/// <summary>
@ -79,36 +82,15 @@ namespace SixLabors.Memory
/// </summary>
public static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{
if (source.Buffer.IsMemoryOwner && destination.Buffer.IsMemoryOwner)
{
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);
}
MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
SwapDimensionData(destination, source);
}
/// <summary>
/// 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)
private static void SwapDimensionData(Buffer2D<T> a, Buffer2D<T> b)
{
Size aSize = a.Size();
Size bSize = b.Size();
IBuffer<T> temp = a.Buffer;
a.Buffer = b.Buffer;
b.Buffer = temp;
b.Width = aSize.Width;
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.Runtime.CompilerServices;
using SixLabors.Primitives;
namespace SixLabors.Memory
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// 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)
{
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 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>
/// Extension methods for <see cref="MemoryAllocator"/>.
/// </summary>
internal static class MemoryAllocatorExtensions
{
/// <summary>
/// Allocates a <see cref="IBuffer{T}"/> of size <paramref name="length"/>.
/// Note: Depending on the implementation, the buffer may not cleared before
/// returning, so it may contain data from an earlier use.
/// </summary>
/// <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)
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
int width,
int height,
AllocationOptions options = AllocationOptions.None)
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)
where T : struct =>
Allocate2D<T>(memoryAllocator, size.Width, size.Height, false);
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)
public static Buffer2D<T> Allocate2D<T>(
this MemoryAllocator memoryAllocator,
Size size,
AllocationOptions options = AllocationOptions.None)
where T : struct =>
Allocate2D<T>(memoryAllocator, width, height, true);
Allocate2D<T>(memoryAllocator, size.Width, size.Height, options);
/// <summary>
/// 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.
using System;
using System.IO;
using System.Buffers;
using System.Runtime.CompilerServices;
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)]
public static int Length<T>(this IBuffer<T> buffer)
where T : struct => buffer.GetSpan().Length;
public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer)
=> buffer.Memory.Span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Length<T>(this IMemoryOwner<T> buffer)
=> buffer.GetSpan().Length;
/// <summary>
/// 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>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start)
where T : struct
public static Span<T> Slice<T>(this IMemoryOwner<T> buffer, int start)
{
return buffer.GetSpan().Slice(start);
}
@ -35,8 +41,7 @@ namespace SixLabors.Memory
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> Slice<T>(this IBuffer<T> buffer, int start, int length)
where T : struct
public static Span<T> Slice<T>(this IMemoryOwner<T> buffer, int start, int length)
{
return buffer.GetSpan().Slice(start, length);
}
@ -46,24 +51,13 @@ namespace SixLabors.Memory
/// </summary>
/// <param name="buffer">The buffer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clear<T>(this IBuffer<T> buffer)
where T : struct
public static void Clear<T>(this IMemoryOwner<T> buffer)
{
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 =>
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
{
public static readonly byte[] Header = {
(byte)'E',
(byte)'x',
(byte)'i',
(byte)'f',
0x00,
0x00,
public static readonly byte[] LittleEndianByteOrderMarker = {
(byte)'I',
(byte)'I',
0x2A,
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.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <summary>
/// The byte array to read the EXIF profile from.
/// </summary>
private readonly byte[] data;
private byte[] data;
/// <summary>
/// The collection of EXIF values
@ -86,7 +85,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
if (other.data != null)
{
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 uint exifOffset;
private uint gpsOffset;
private int startIndex;
public ExifReader(byte[] exifData)
{
@ -77,20 +76,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
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")
{
this.endianness = Endianness.LittleEndian;
@ -169,7 +154,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// <param name="index">The index.</param>
private void AddValues(List<ExifValue> values, int index)
{
this.position = this.startIndex + index;
this.position = index;
int count = this.ReadUInt16();
for (int i = 0; i < count; i++)
@ -353,7 +338,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
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
if (newIndex > int.MaxValue)
@ -454,7 +439,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
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)
{

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

@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </summary>
internal sealed class ExifWriter
{
/// <summary>
/// The start index.
/// </summary>
private const int StartIndex = 6;
/// <summary>
/// Which parts will be written.
/// </summary>
@ -51,6 +46,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
/// </returns>
public byte[] GetData()
{
uint startIndex = 0;
uint length;
int exifIndex = -1;
int gpsIndex = -1;
@ -86,14 +82,20 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
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];
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;
if (exifLength > 0)
@ -109,18 +111,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
i = WriteUInt32(ifdOffset, result, i);
i = this.WriteHeaders(this.ifdIndexes, 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)
{
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)
{
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);
@ -257,7 +259,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
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)
{
@ -272,7 +274,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
ExifValue value = this.values[index];
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);
}
}

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

@ -149,17 +149,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
#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>
/// Checks for signs of a corrupt profile.
/// </summary>

1
src/ImageSharp/PixelFormats/Bgra32.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <summary>
/// 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 format is binary compatible with System.Drawing.Imaging.PixelFormat.Format32bppArgb
/// <para>
/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form.
/// </para>

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

Loading…
Cancel
Save