Browse Source

Merge branch 'master' of https://github.com/SixLabors/ImageSharp into tiff-codec

pull/1570/head
James Jackson-South 8 years ago
parent
commit
6d4e02e9a0
  1. 4
      .travis.yml
  2. 2
      .vscode/launch.json
  3. 4
      .vscode/tasks.json
  4. 6
      ImageSharp.sln
  5. 12
      samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj
  6. 111
      samples/AvatarWithRoundedCorner/Program.cs
  7. 3
      samples/AvatarWithRoundedCorner/fb.jpg
  8. 12
      samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj
  9. 23
      samples/ChangeDefaultEncoderOptions/Program.cs
  10. 13
      src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs
  11. 15
      src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs
  12. 15
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  13. 15
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs
  14. 34
      src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs
  15. 53
      src/ImageSharp.Drawing/DrawImage.cs
  16. 2
      src/ImageSharp.Drawing/Paths/ShapePath.cs
  17. 17
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  18. 86
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  19. 24
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  20. 129
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  21. 11
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  22. 1
      src/ImageSharp/ApplyProcessors.cs
  23. 2
      src/ImageSharp/ColorSpaces/CieLab.cs
  24. 2
      src/ImageSharp/ColorSpaces/CieLch.cs
  25. 2
      src/ImageSharp/ColorSpaces/CieLchuv.cs
  26. 2
      src/ImageSharp/ColorSpaces/CieLuv.cs
  27. 6
      src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs
  28. 2
      src/ImageSharp/ColorSpaces/CieXyy.cs
  29. 2
      src/ImageSharp/ColorSpaces/CieXyz.cs
  30. 2
      src/ImageSharp/ColorSpaces/Cmyk.cs
  31. 3
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs
  32. 4
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs
  33. 2
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs
  34. 2
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs
  35. 2
      src/ImageSharp/ColorSpaces/Hsl.cs
  36. 2
      src/ImageSharp/ColorSpaces/Hsv.cs
  37. 2
      src/ImageSharp/ColorSpaces/HunterLab.cs
  38. 2
      src/ImageSharp/ColorSpaces/LinearRgb.cs
  39. 2
      src/ImageSharp/ColorSpaces/Lms.cs
  40. 2
      src/ImageSharp/ColorSpaces/Rgb.cs
  41. 2
      src/ImageSharp/ColorSpaces/YCbCr.cs
  42. 12
      src/ImageSharp/Common/Extensions/SimdUtils.cs
  43. 21
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  44. 60
      src/ImageSharp/Common/Helpers/ParallelFor.cs
  45. 406
      src/ImageSharp/Configuration.cs
  46. 15
      src/ImageSharp/DefaultInternalImageProcessorContext.cs
  47. 16
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
  48. 24
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  49. 56
      src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs
  50. 34
      src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs
  51. 36
      src/ImageSharp/Dithering/Ordered/BayerDither.cs
  52. 19
      src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs
  53. 19
      src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs
  54. 19
      src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs
  55. 7
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  56. 31
      src/ImageSharp/Dithering/Ordered/KnownDitherers.cs
  57. 50
      src/ImageSharp/Dithering/Ordered/OrderedDither.cs
  58. 19
      src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs
  59. 53
      src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs
  60. 94
      src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs
  61. 58
      src/ImageSharp/Dithering/error_diffusion.txt
  62. 6
      src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs
  63. 37
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  64. 3
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  65. 28
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  66. 2
      src/ImageSharp/Formats/Bmp/ImageExtensions.cs
  67. 6
      src/ImageSharp/Formats/Gif/GifConfigurationModule.cs
  68. 64
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  69. 4
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  70. 26
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  71. 2
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  72. 53
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  73. 58
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  74. 2
      src/ImageSharp/Formats/Gif/PackedField.cs
  75. 186
      src/ImageSharp/Formats/ImageFormatManager.cs
  76. 5
      src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs
  77. 11
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs
  78. 4
      src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs
  79. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs
  80. 9
      src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  81. 9
      src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
  82. 4
      src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs
  83. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs
  84. 2
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs
  85. 8
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs
  86. 15
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs
  87. 26
      src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs
  88. 43
      src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt
  89. 129
      src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs
  90. 18
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs
  91. 3
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs
  92. 17
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs
  93. 5
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs
  94. 192
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs
  95. 181
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs
  96. 2
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs
  97. 2
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs
  98. 34
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs
  99. 82
      src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  100. 227
      src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs

4
.travis.yml

@ -6,7 +6,7 @@ matrix:
- os: linux # Ubuntu 14.04 - os: linux # Ubuntu 14.04
dist: trusty dist: trusty
sudo: required sudo: required
dotnet: 1.0.4 dotnet: 2.1.4
mono: latest mono: latest
# - os: osx # OSX 10.11 # - os: osx # OSX 10.11
# osx_image: xcode7.3.1 # osx_image: xcode7.3.1
@ -21,7 +21,7 @@ branches:
script: script:
- git submodule -q update --init - git submodule -q update --init
- dotnet restore - dotnet restore
- dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp1.1" - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp2.0"
env: env:
global: global:

2
.vscode/launch.json

@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceRoot}/samples/AvatarWithRoundedCorner/bin/Debug/netcoreapp1.1/AvatarWithRoundedCorner.dll", "program": "${workspaceRoot}/tests/ImageSharp.Benchmarks/bin/Debug/netcoreapp2.0/ImageSharp.Benchmarks.dll",
"args": [], "args": [],
"cwd": "${workspaceRoot}/samples/AvatarWithRoundedCorner", "cwd": "${workspaceRoot}/samples/AvatarWithRoundedCorner",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window

4
.vscode/tasks.json

@ -16,13 +16,13 @@
{ {
"taskName": "build benchmark", "taskName": "build benchmark",
"suppressTaskName": true, "suppressTaskName": true,
"args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp1.1", "-c", "Release" ], "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp2.0", "-c", "Release" ],
"showOutput": "always", "showOutput": "always",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"taskName": "test", "taskName": "test",
"args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp1.1"], "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp2.0"],
"isTestCommand": true, "isTestCommand": true,
"showOutput": "always", "showOutput": "always",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

6
ImageSharp.sln

@ -42,12 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\I
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6D57E-B916-43B8-B315-A0BB92F260A2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Formats.Tiff.Tests", "tests\ImageSharp.Formats.Tiff.Tests\ImageSharp.Formats.Tiff.Tests.csproj", "{F74D25AB-1E5C-4272-9FD3-6DBBD3E207AC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Formats.Tiff.Tests", "tests\ImageSharp.Formats.Tiff.Tests\ImageSharp.Formats.Tiff.Tests.csproj", "{F74D25AB-1E5C-4272-9FD3-6DBBD3E207AC}"

12
samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

111
samples/AvatarWithRoundedCorner/Program.cs

@ -1,111 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace AvatarWithRoundedCorner
{
static class Program
{
static void Main(string[] args)
{
System.IO.Directory.CreateDirectory("output");
using (var img = Image.Load("fb.jpg"))
{
// as generate returns a new IImage make sure we dispose of it
using (Image<Rgba32> destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 20)))
{
destRound.Save("output/fb.png");
}
using (Image<Rgba32> destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 100)))
{
destRound.Save("output/fb-round.png");
}
using (Image<Rgba32> destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 150)))
{
destRound.Save("output/fb-rounder.png");
}
using (Image<Rgba32> destRound = img.CloneAndConvertToAvatarWithoutApply(new Size(200, 200), 150))
{
destRound.Save("output/fb-rounder-without-apply.png");
}
// the original `img` object has not been altered at all.
}
}
// 1. The short way:
// Implements a full image mutating pipeline operating on IImageProcessingContext<Rgba32>
// We need the dimensions of the resized image to deduce 'IPathCollection' needed to build the corners,
// so we implement an "inline" image processor by utilizing 'ImageExtensions.Apply()'
private static IImageProcessingContext<Rgba32> ConvertToAvatar(this IImageProcessingContext<Rgba32> processingContext, Size size, float cornerRadius)
{
return processingContext.Resize(new ResizeOptions
{
Size = size,
Mode = ResizeMode.Crop
}).Apply(i => ApplyRoundedCorners(i, cornerRadius));
}
// 2. A more verbose way, avoiding 'Apply()':
// First we create a resized clone of the image, then we draw the corners on that instance with Mutate().
private static Image<Rgba32> CloneAndConvertToAvatarWithoutApply(this Image<Rgba32> image, Size size, float cornerRadius)
{
Image<Rgba32> result = image.Clone(
ctx => ctx.Resize(
new ResizeOptions
{
Size = size,
Mode = ResizeMode.Crop
}));
ApplyRoundedCorners(result, cornerRadius);
return result;
}
// This method can be seen as an inline implementation of an `IImageProcessor`:
// (The combination of `IImageOperations.Apply()` + this could be replaced with an `IImageProcessor`)
public static void ApplyRoundedCorners(Image<Rgba32> img, float cornerRadius)
{
IPathCollection corners = BuildCorners(img.Width, img.Height, cornerRadius);
// mutating in here as we already have a cloned original
img.Mutate(x => x.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true)
{
BlenderMode = PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background
}));
}
public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
{
// first create a square
var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius);
// then cut out of the square a circle so we are left with a corner
IPath cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius));
// corner is now a corner shape positions top left
//lets make 3 more positioned correctly, we can do that by translating the orgional artound the center of the image
var center = new Vector2(imageWidth / 2F, imageHeight / 2F);
float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1;
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
// move it across the widthof the image - the width of the shape
IPath cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0);
IPath cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos);
IPath cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos);
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
}
}
}

3
samples/AvatarWithRoundedCorner/fb.jpg

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c
size 15787

12
samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
</Project>

23
samples/ChangeDefaultEncoderOptions/Program.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
namespace ChangeDefaultEncoderOptions
{
class Program
{
static void Main(string[] args)
{
// lets switch out the default encoder for jpeg to one
// that saves at 90 quality and ignores the matadata
Configuration.Default.SetEncoder(ImageFormats.Jpeg, new JpegEncoder()
{
Quality = 90,
IgnoreMetadata = true
});
}
}
}

13
src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs

@ -122,24 +122,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
// Create a span for colors // Create a span for colors
using (var amountBuffer = new Buffer<float>(scanline.Length)) using (IBuffer<float> amountBuffer = this.Target.MemoryManager.Allocate<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length)) using (IBuffer<TPixel> overlay = this.Target.MemoryManager.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> overlaySpan = overlay.Span;
int sourceY = (y - this.offsetY) % this.yLength; int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX; int offsetX = x - this.offsetX;
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(sourceY); Span<TPixel> sourceRow = this.source.GetPixelRowSpan(sourceY);
for (int i = 0; i < scanline.Length; i++) for (int i = 0; i < scanline.Length; i++)
{ {
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
int sourceX = (i + offsetX) % this.xLength; int sourceX = (i + offsetX) % this.xLength;
TPixel pixel = sourceRow[sourceX]; TPixel pixel = sourceRow[sourceX];
overlay[i] = pixel; overlaySpan[i] = pixel;
} }
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); this.Blender.Blend(this.source.MemoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
} }
} }
} }

15
src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs

@ -152,19 +152,24 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
int patternY = y % this.pattern.Height; int patternY = y % this.pattern.Height;
using (var amountBuffer = new Buffer<float>(scanline.Length)) MemoryManager memoryManager = this.Target.MemoryManager;
using (var overlay = new Buffer<TPixel>(scanline.Length))
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryManager.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> overlaySpan = overlay.Span;
for (int i = 0; i < scanline.Length; i++) for (int i = 0; i < scanline.Length; i++)
{ {
amountBuffer[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1);
int patternX = (x + i) % this.pattern.Width; int patternX = (x + i) % this.pattern.Width;
overlay[i] = this.pattern[patternY, patternX]; overlaySpan[i] = this.pattern[patternY, patternX];
} }
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
} }
} }
} }

15
src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs

@ -65,21 +65,26 @@ namespace SixLabors.ImageSharp.Drawing.Brushes.Processors
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks> /// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(Span<float> scanline, int x, int y) internal virtual void Apply(Span<float> scanline, int x, int y)
{ {
using (var amountBuffer = new Buffer<float>(scanline.Length)) MemoryManager memoryManager = this.Target.MemoryManager;
using (var overlay = new Buffer<TPixel>(scanline.Length))
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryManager.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> overlaySpan = overlay.Span;
for (int i = 0; i < scanline.Length; i++) for (int i = 0; i < scanline.Length; i++)
{ {
if (this.Options.BlendPercentage < 1) if (this.Options.BlendPercentage < 1)
{ {
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
} }
overlay[i] = this[x + i, y]; overlaySpan[i] = this[x + i, y];
} }
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
} }
} }
} }

15
src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs

@ -144,22 +144,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
/// <inheritdoc /> /// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
using (var amountBuffer = new Buffer<float>(scanline.Length)) MemoryManager memoryManager = this.Target.MemoryManager;
using (var overlay = new Buffer<TPixel>(scanline.Length))
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
using (IBuffer<TPixel> overlay = memoryManager.Allocate<TPixel>(scanline.Length))
{ {
Span<float> amountSpan = amountBuffer.Span;
Span<TPixel> overlaySpan = overlay.Span;
for (int i = 0; i < scanline.Length; i++) for (int i = 0; i < scanline.Length; i++)
{ {
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
int offsetX = x + i; int offsetX = x + i;
// no doubt this one can be optermised further but I can't imagine its // no doubt this one can be optermised further but I can't imagine its
// actually being used and can probably be removed/interalised for now // actually being used and can probably be removed/interalised for now
overlay[i] = this[offsetX, y]; overlaySpan[i] = this[offsetX, y];
} }
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan);
} }
} }
} }

34
src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs

@ -61,17 +61,14 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
public SolidBrushApplicator(ImageFrame<TPixel> source, TPixel color, GraphicsOptions options) public SolidBrushApplicator(ImageFrame<TPixel> source, TPixel color, GraphicsOptions options)
: base(source, options) : base(source, options)
{ {
this.Colors = new Buffer<TPixel>(source.Width); this.Colors = source.MemoryManager.Allocate<TPixel>(source.Width);
for (int i = 0; i < this.Colors.Length; i++) this.Colors.Span.Fill(color);
{
this.Colors[i] = color;
}
} }
/// <summary> /// <summary>
/// Gets the colors. /// Gets the colors.
/// </summary> /// </summary>
protected Buffer<TPixel> Colors { get; } protected IBuffer<TPixel> Colors { get; }
/// <summary> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
@ -81,7 +78,7 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
internal override TPixel this[int x, int y] => this.Colors[x]; internal override TPixel this[int x, int y] => this.Colors.Span[x];
/// <inheritdoc /> /// <inheritdoc />
public override void Dispose() public override void Dispose()
@ -92,23 +89,20 @@ namespace SixLabors.ImageSharp.Drawing.Brushes
/// <inheritdoc /> /// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
try Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
MemoryManager memoryManager = this.Target.MemoryManager;
using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
{ {
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); Span<float> amountSpan = amountBuffer.Span;
using (var amountBuffer = new Buffer<float>(scanline.Length)) for (int i = 0; i < scanline.Length; i++)
{ {
for (int i = 0; i < scanline.Length; i++) amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
{
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage;
}
this.Blender.Blend(destinationRow, destinationRow, this.Colors, amountBuffer);
} }
}
catch (Exception) this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan);
{
throw;
} }
} }
} }

53
src/ImageSharp.Drawing/DrawImage.cs

@ -18,24 +18,13 @@ namespace SixLabors.ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Size size, Point location, GraphicsOptions options) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (size == default(Size)) source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, options));
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, options));
return source; return source;
} }
@ -45,14 +34,14 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param> /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float percent) public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GraphicsOptions options = GraphicsOptions.Default; GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent; options.BlendPercentage = opacity;
return DrawImage(source, image, default(Size), default(Point), options); return DrawImage(source, image, Point.Empty, options);
} }
/// <summary> /// <summary>
@ -62,15 +51,15 @@ namespace SixLabors.ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="blender">The blending mode.</param> /// <param name="blender">The blending mode.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param> /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent) public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GraphicsOptions options = GraphicsOptions.Default; GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent; options.BlendPercentage = opacity;
options.BlenderMode = blender; options.BlenderMode = blender;
return DrawImage(source, image, default(Size), default(Point), options); return DrawImage(source, image, Point.Empty, options);
} }
/// <summary> /// <summary>
@ -79,12 +68,12 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and belnding amount.</param> /// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, GraphicsOptions options) public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return DrawImage(source, image, default(Size), default(Point), options); return DrawImage(source, image, Point.Empty, options);
} }
/// <summary> /// <summary>
@ -93,16 +82,15 @@ namespace SixLabors.ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param> /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float percent, Size size, Point location) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity, Point location)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GraphicsOptions options = GraphicsOptions.Default; GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent; options.BlendPercentage = opacity;
return source.DrawImage(image, size, location, options); return source.DrawImage(image, location, options);
} }
/// <summary> /// <summary>
@ -112,17 +100,16 @@ namespace SixLabors.ImageSharp
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The type of bending to apply.</param> /// <param name="blender">The type of bending to apply.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param> /// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent, Size size, Point location) public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float opacity, Point location)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GraphicsOptions options = GraphicsOptions.Default; GraphicsOptions options = GraphicsOptions.Default;
options.BlenderMode = blender; options.BlenderMode = blender;
options.BlendPercentage = percent; options.BlendPercentage = opacity;
return source.DrawImage(image, size, location, options); return source.DrawImage(image, location, options);
} }
} }
} }

2
src/ImageSharp.Drawing/Paths/ShapePath.cs

@ -4,6 +4,8 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.Shapes; using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Drawing namespace SixLabors.ImageSharp.Drawing

17
src/ImageSharp.Drawing/Paths/ShapeRegion.cs

@ -46,18 +46,17 @@ namespace SixLabors.ImageSharp.Drawing
{ {
var start = new PointF(this.Bounds.Left - 1, y); var start = new PointF(this.Bounds.Left - 1, y);
var end = new PointF(this.Bounds.Right + 1, y); var end = new PointF(this.Bounds.Right + 1, y);
using (var innerBuffer = new Buffer<PointF>(buffer.Length))
{
PointF[] array = innerBuffer.Array;
int count = this.Shape.FindIntersections(start, end, array, 0);
for (int i = 0; i < count; i++) // TODO: This is a temporary workaround because of the lack of Span<T> API-s on IPath. We should use MemoryManager.Allocate() here!
{ PointF[] innerBuffer = new PointF[buffer.Length];
buffer[i + offset] = array[i].X; int count = this.Shape.FindIntersections(start, end, innerBuffer, 0);
}
return count; for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerBuffer[i].X;
} }
return count;
} }
} }
} }

86
src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs

@ -19,93 +19,77 @@ namespace SixLabors.ImageSharp.Drawing.Processors
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel> internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly PixelBlender<TPixel> blender;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param> /// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The opacity of the image to blend. Between 0 and 100.</param> /// <param name="options">The opacity of the image to blend. Between 0 and 1.</param>
public DrawImageProcessor(Image<TPixel> image, Size size, Point location, GraphicsOptions options) public DrawImageProcessor(Image<TPixel> image, Point location, GraphicsOptions options)
{ {
Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage)); Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage));
this.Image = image; this.Image = image;
this.Size = size; this.Opacity = options.BlendPercentage;
this.Alpha = options.BlendPercentage; this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode);
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode);
this.Location = location; this.Location = location;
} }
/// <summary> /// <summary>
/// Gets the image to blend. /// Gets the image to blend
/// </summary> /// </summary>
public Image<TPixel> Image { get; } public Image<TPixel> Image { get; }
/// <summary> /// <summary>
/// Gets the alpha percentage value. /// Gets the opacity of the image to blend
/// </summary> /// </summary>
public float Alpha { get; } public float Opacity { get; }
/// <summary> /// <summary>
/// Gets the size to draw the blended image. /// Gets the pixel blender
/// </summary> /// </summary>
public Size Size { get; } public PixelBlender<TPixel> Blender { get; }
/// <summary> /// <summary>
/// Gets the location to draw the blended image. /// Gets the location to draw the blended image
/// </summary> /// </summary>
public Point Location { get; } public Point Location { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{ {
Image<TPixel> disposableImage = null;
Image<TPixel> targetImage = this.Image; Image<TPixel> targetImage = this.Image;
PixelBlender<TPixel> blender = this.Blender;
int locationY = this.Location.Y;
try // Align start/end positions.
{ Rectangle bounds = targetImage.Bounds();
if (targetImage.Size() != this.Size)
{
targetImage = disposableImage = this.Image.Clone(x => x.Resize(this.Size.Width, this.Size.Height));
}
// Align start/end positions. int minX = Math.Max(this.Location.X, sourceRectangle.X);
Rectangle bounds = targetImage.Bounds(); int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
int minX = Math.Max(this.Location.X, sourceRectangle.X); int targetX = minX - this.Location.X;
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
maxX = Math.Min(this.Location.X + this.Size.Width, maxX);
int targetX = minX - this.Location.X;
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
maxY = Math.Min(this.Location.Y + this.Size.Height, maxY); int width = maxX - minX;
int width = maxX - minX; MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager;
using (var amount = new Buffer<float>(width))
{
for (int i = 0; i < width; i++)
{
amount[i] = this.Alpha;
}
Parallel.For( using (IBuffer<float> amount = memoryManager.Allocate<float>(width))
minY,
maxY,
configuration.ParallelOptions,
y =>
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - this.Location.Y).Slice(targetX, width);
this.blender.Blend(background, background, foreground, amount);
});
}
}
finally
{ {
disposableImage?.Dispose(); amount.Span.Fill(this.Opacity);
Parallel.For(
minY,
maxY,
configuration.ParallelOptions,
y =>
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend(memoryManager, background, background, foreground, amount.Span);
});
} }
} }
} }

24
src/ImageSharp.Drawing/Processors/FillProcessor.cs

@ -66,25 +66,25 @@ namespace SixLabors.ImageSharp.Drawing.Processors
int width = maxX - minX; int width = maxX - minX;
using (var amount = new Buffer<float>(width)) using (IBuffer<float> amount = source.MemoryManager.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(
source,
sourceRectangle,
this.options))
{ {
for (int i = 0; i < width; i++) amount.Span.Fill(this.options.BlendPercentage);
{
amount[i] = this.options.BlendPercentage;
}
Parallel.For( Parallel.For(
minY, minY,
maxY, maxY,
configuration.ParallelOptions, configuration.ParallelOptions,
y => y =>
{ {
int offsetY = y - startY; int offsetY = y - startY;
int offsetX = minX - startX; int offsetX = minX - startX;
applicator.Apply(amount, offsetX, offsetY); applicator.Apply(amount.Span, offsetX, offsetY);
}); });
} }
} }
} }

129
src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs

@ -78,8 +78,6 @@ namespace SixLabors.ImageSharp.Drawing.Processors
return; // no effect inside image; return; // no effect inside image;
} }
ArrayPool<float> arrayPool = ArrayPool<float>.Shared;
int maxIntersections = region.MaxIntersections; int maxIntersections = region.MaxIntersections;
float subpixelCount = 4; float subpixelCount = 4;
@ -100,101 +98,94 @@ namespace SixLabors.ImageSharp.Drawing.Processors
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options)) using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{ {
float[] buffer = arrayPool.Rent(maxIntersections);
int scanlineWidth = maxX - minX; int scanlineWidth = maxX - minX;
using (var scanline = new Buffer<float>(scanlineWidth)) using (BasicArrayBuffer<float> buffer = source.MemoryManager.AllocateFake<float>(maxIntersections))
using (BasicArrayBuffer<float> scanline = source.MemoryManager.AllocateFake<float>(scanlineWidth))
{ {
try bool scanlineDirty = true;
for (int y = minY; y < maxY; y++)
{ {
bool scanlineDirty = true; if (scanlineDirty)
for (int y = minY; y < maxY; y++)
{ {
if (scanlineDirty) // clear the buffer
for (int x = 0; x < scanlineWidth; x++)
{ {
// clear the buffer scanline[x] = 0;
for (int x = 0; x < scanlineWidth; x++)
{
scanline[x] = 0;
}
scanlineDirty = false;
} }
float subpixelFraction = 1f / subpixelCount; scanlineDirty = false;
float subpixelFractionPoint = subpixelFraction / subpixelCount; }
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
int pointsFound = region.Scan(subPixel + offset, buffer, 0);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
}
QuickSort(new Span<float>(buffer, 0, pointsFound)); float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
int pointsFound = region.Scan(subPixel + offset, buffer.Array, 0);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
}
for (int point = 0; point < pointsFound; point += 2) QuickSort(new Span<float>(buffer.Array, 0, pointsFound));
{
// points will be paired up
float scanStart = buffer[point] - minX;
float scanEnd = buffer[point + 1] - minX;
int startX = (int)MathF.Floor(scanStart + offset);
int endX = (int)MathF.Floor(scanEnd + offset);
if (startX >= 0 && startX < scanline.Length) for (int point = 0; point < pointsFound; point += 2)
{ {
for (float x = scanStart; x < startX + 1; x += subpixelFraction) // points will be paired up
{ float scanStart = buffer[point] - minX;
scanline[startX] += subpixelFractionPoint; float scanEnd = buffer[point + 1] - minX;
scanlineDirty = true; int startX = (int)MathF.Floor(scanStart + offset);
} int endX = (int)MathF.Floor(scanEnd + offset);
}
if (endX >= 0 && endX < scanline.Length) if (startX >= 0 && startX < scanline.Length)
{
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
{ {
for (float x = endX; x < scanEnd; x += subpixelFraction) scanline[startX] += subpixelFractionPoint;
{ scanlineDirty = true;
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
}
} }
}
int nextX = startX + 1; if (endX >= 0 && endX < scanline.Length)
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge {
nextX = Math.Max(nextX, 0); for (float x = endX; x < scanEnd; x += subpixelFraction)
for (int x = nextX; x < endX; x++)
{ {
scanline[x] += subpixelFraction; scanline[endX] += subpixelFractionPoint;
scanlineDirty = true; scanlineDirty = true;
} }
} }
int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
nextX = Math.Max(nextX, 0);
for (int x = nextX; x < endX; x++)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
}
} }
}
if (scanlineDirty) if (scanlineDirty)
{
if (!this.Options.Antialias)
{ {
if (!this.Options.Antialias) for (int x = 0; x < scanlineWidth; x++)
{ {
for (int x = 0; x < scanlineWidth; x++) if (scanline[x] >= 0.5)
{
scanline[x] = 1;
}
else
{ {
if (scanline[x] >= 0.5) scanline[x] = 0;
{
scanline[x] = 1;
}
else
{
scanline[x] = 0;
}
} }
} }
applicator.Apply(scanline, minX, y);
} }
applicator.Apply(scanline.Span, minX, y);
} }
} }
finally
{
arrayPool.Return(buffer);
}
} }
} }
} }

11
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -88,6 +89,14 @@ namespace SixLabors.ImageSharp.Advanced
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(row); => source.Frames.RootFrame.GetPixelRowSpan(row);
/// <summary>
/// Gets the <see cref="MemoryManager"/> assigned to 'source'.
/// </summary>
/// <param name="source">The source image</param>
/// <returns>Returns the configuration.</returns>
internal static MemoryManager GetMemoryManager(this IConfigurable source)
=> GetConfiguration(source).MemoryManager;
/// <summary> /// <summary>
/// Gets the span to the backing buffer. /// Gets the span to the backing buffer.
/// </summary> /// </summary>
@ -140,6 +149,6 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>A reference to the element.</returns> /// <returns>A reference to the element.</returns>
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source) private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> ref source.PixelBuffer.Span.DangerousGetPinnableReference(); => ref MemoryMarshal.GetReference(source.PixelBuffer.Span);
} }
} }

1
src/ImageSharp/ApplyProcessors.cs

@ -4,7 +4,6 @@
using System; using System;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {

2
src/ImageSharp/ColorSpaces/CieLab.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// Represents a CIE L*a*b* 1976 color. /// Represents a CIE L*a*b* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/> /// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary> /// </summary>
internal struct CieLab : IColorVector, IEquatable<CieLab>, IAlmostEquatable<CieLab, float> internal readonly struct CieLab : IColorVector, IEquatable<CieLab>, IAlmostEquatable<CieLab, float>
{ {
/// <summary> /// <summary>
/// D50 standard illuminant. /// D50 standard illuminant.

2
src/ImageSharp/ColorSpaces/CieLch.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC"/> /// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC"/>
/// </summary> /// </summary>
internal struct CieLch : IColorVector, IEquatable<CieLch>, IAlmostEquatable<CieLch, float> internal readonly struct CieLch : IColorVector, IEquatable<CieLch>, IAlmostEquatable<CieLch, float>
{ {
/// <summary> /// <summary>
/// D50 standard illuminant. /// D50 standard illuminant.

2
src/ImageSharp/ColorSpaces/CieLchuv.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CieLchuv_or_CIEHLC"/> /// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CieLchuv_or_CIEHLC"/>
/// </summary> /// </summary>
internal struct CieLchuv : IColorVector, IEquatable<CieLchuv>, IAlmostEquatable<CieLchuv, float> internal readonly struct CieLchuv : IColorVector, IEquatable<CieLchuv>, IAlmostEquatable<CieLchuv, float>
{ {
/// <summary> /// <summary>
/// D50 standard illuminant. /// D50 standard illuminant.

2
src/ImageSharp/ColorSpaces/CieLuv.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// attempted perceptual uniformity /// attempted perceptual uniformity
/// <see href="https://en.wikipedia.org/wiki/CIELUV"/> /// <see href="https://en.wikipedia.org/wiki/CIELUV"/>
/// </summary> /// </summary>
internal struct CieLuv : IColorVector, IEquatable<CieLuv>, IAlmostEquatable<CieLuv, float> internal readonly struct CieLuv : IColorVector, IEquatable<CieLuv>, IAlmostEquatable<CieLuv, float>
{ {
/// <summary> /// <summary>
/// D65 standard illuminant. /// D65 standard illuminant.

6
src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs

@ -6,12 +6,13 @@ using System.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
// ReSharper disable CompareOfFloatsByEqualityOperator
namespace SixLabors.ImageSharp.ColorSpaces namespace SixLabors.ImageSharp.ColorSpaces
{ {
/// <summary> /// <summary>
/// Represents the coordinates of CIEXY chromaticity space /// Represents the coordinates of CIEXY chromaticity space
/// </summary> /// </summary>
internal struct CieXyChromaticityCoordinates : IEquatable<CieXyChromaticityCoordinates>, IAlmostEquatable<CieXyChromaticityCoordinates, float> internal readonly struct CieXyChromaticityCoordinates : IEquatable<CieXyChromaticityCoordinates>, IAlmostEquatable<CieXyChromaticityCoordinates, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="CieXyChromaticityCoordinates"/> that has X, Y values set to zero. /// Represents a <see cref="CieXyChromaticityCoordinates"/> that has X, Y values set to zero.
@ -143,7 +144,8 @@ namespace SixLabors.ImageSharp.ColorSpaces
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyChromaticityCoordinates other) public bool Equals(CieXyChromaticityCoordinates other)
{ {
return this.backingVector.Equals(other.backingVector); // The memberwise comparison here is a workaround for https://github.com/dotnet/coreclr/issues/16443
return this.X == other.X && this.Y == other.Y;
} }
/// <inheritdoc/> /// <inheritdoc/>

2
src/ImageSharp/ColorSpaces/CieXyy.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// Represents an CIE xyY 1931 color /// Represents an CIE xyY 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space"/> /// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space"/>
/// </summary> /// </summary>
internal struct CieXyy : IColorVector, IEquatable<CieXyy>, IAlmostEquatable<CieXyy, float> internal readonly struct CieXyy : IColorVector, IEquatable<CieXyy>, IAlmostEquatable<CieXyy, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="CieXyy"/> that has X, Y, and Y values set to zero. /// Represents a <see cref="CieXyy"/> that has X, Y, and Y values set to zero.

2
src/ImageSharp/ColorSpaces/CieXyz.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// Represents an CIE XYZ 1931 color /// Represents an CIE XYZ 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space"/> /// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space"/>
/// </summary> /// </summary>
internal struct CieXyz : IColorVector, IEquatable<CieXyz>, IAlmostEquatable<CieXyz, float> internal readonly struct CieXyz : IColorVector, IEquatable<CieXyz>, IAlmostEquatable<CieXyz, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="CieXyz"/> that has X, Y, and Z values set to zero. /// Represents a <see cref="CieXyz"/> that has X, Y, and Z values set to zero.

2
src/ImageSharp/ColorSpaces/Cmyk.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// <summary> /// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color. /// Represents an CMYK (cyan, magenta, yellow, keyline) color.
/// </summary> /// </summary>
internal struct Cmyk : IEquatable<Cmyk>, IAlmostEquatable<Cmyk, float> internal readonly struct Cmyk : IEquatable<Cmyk>, IAlmostEquatable<Cmyk, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Cmyk"/> that has C, M, Y, and K values set to zero. /// Represents a <see cref="Cmyk"/> that has C, M, Y, and K values set to zero.

3
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs

@ -184,8 +184,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
Rgb rgb = YCbCrAndRgbConverter.Convert(color); Rgb rgb = YCbCrAndRgbConverter.Convert(color);
// Adaptation // Adaptation
// TODO: Check this! return this.Adapt(rgb);
return rgb.WorkingSpace.Equals(this.TargetRgbWorkingSpace) ? rgb : this.Adapt(rgb);
} }
} }
} }

4
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap
internal abstract class LinearRgbAndCieXyzConverterBase internal abstract class LinearRgbAndCieXyzConverterBase
{ {
/// <summary> /// <summary>
/// Geturns the correct matrix to convert between the Rgb and CieXyz color space. /// Returns the correct matrix to convert between the Rgb and CieXyz color space.
/// </summary> /// </summary>
/// <param name="workingSpace">The Rgb working space.</param> /// <param name="workingSpace">The Rgb working space.</param>
/// <returns>The <see cref="Matrix4x4"/> based on the chromaticity and working space.</returns> /// <returns>The <see cref="Matrix4x4"/> based on the chromaticity and working space.</returns>
@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap
Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix); Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix);
// Use transposed Rows/Coloumns // Use transposed Rows/Columns
// TODO: Is there a built in method for this multiplication? // TODO: Is there a built in method for this multiplication?
return new Matrix4x4 return new Matrix4x4
{ {

2
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap
public CieXyz Convert(LinearRgb input) public CieXyz Convert(LinearRgb input)
{ {
DebugGuard.NotNull(input, nameof(input)); DebugGuard.NotNull(input, nameof(input));
Guard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal."); DebugGuard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal.");
Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix); Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix);
return new CieXyz(vector); return new CieXyz(vector);

2
src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap
/// <summary> /// <summary>
/// Trivial implementation of <see cref="IRgbWorkingSpace"/> /// Trivial implementation of <see cref="IRgbWorkingSpace"/>
/// </summary> /// </summary>
internal struct RgbWorkingSpace : IRgbWorkingSpace internal readonly struct RgbWorkingSpace : IRgbWorkingSpace
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RgbWorkingSpace"/> struct. /// Initializes a new instance of the <see cref="RgbWorkingSpace"/> struct.

2
src/ImageSharp/ColorSpaces/Hsl.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// <summary> /// <summary>
/// Represents a Hsl (hue, saturation, lightness) color. /// Represents a Hsl (hue, saturation, lightness) color.
/// </summary> /// </summary>
internal struct Hsl : IColorVector, IEquatable<Hsl>, IAlmostEquatable<Hsl, float> internal readonly struct Hsl : IColorVector, IEquatable<Hsl>, IAlmostEquatable<Hsl, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Hsl"/> that has H, S, and L values set to zero. /// Represents a <see cref="Hsl"/> that has H, S, and L values set to zero.

2
src/ImageSharp/ColorSpaces/Hsv.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// <summary> /// <summary>
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
/// </summary> /// </summary>
internal struct Hsv : IColorVector, IEquatable<Hsv>, IAlmostEquatable<Hsv, float> internal readonly struct Hsv : IColorVector, IEquatable<Hsv>, IAlmostEquatable<Hsv, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Hsv"/> that has H, S, and V values set to zero. /// Represents a <see cref="Hsv"/> that has H, S, and V values set to zero.

2
src/ImageSharp/ColorSpaces/HunterLab.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// Represents an Hunter LAB color. /// Represents an Hunter LAB color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/> /// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary> /// </summary>
internal struct HunterLab : IColorVector, IEquatable<HunterLab>, IAlmostEquatable<HunterLab, float> internal readonly struct HunterLab : IColorVector, IEquatable<HunterLab>, IAlmostEquatable<HunterLab, float>
{ {
/// <summary> /// <summary>
/// D50 standard illuminant. /// D50 standard illuminant.

2
src/ImageSharp/ColorSpaces/LinearRgb.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// <summary> /// <summary>
/// Represents an linear Rgb color with specified <see cref="IRgbWorkingSpace"/> working space /// Represents an linear Rgb color with specified <see cref="IRgbWorkingSpace"/> working space
/// </summary> /// </summary>
internal struct LinearRgb : IColorVector, IEquatable<LinearRgb>, IAlmostEquatable<LinearRgb, float> internal readonly struct LinearRgb : IColorVector, IEquatable<LinearRgb>, IAlmostEquatable<LinearRgb, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="LinearRgb"/> that has R, G, and B values set to zero. /// Represents a <see cref="LinearRgb"/> that has R, G, and B values set to zero.

2
src/ImageSharp/ColorSpaces/Lms.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// named after their responsivity (sensitivity) at long, medium and short wavelengths. /// named after their responsivity (sensitivity) at long, medium and short wavelengths.
/// <see href="https://en.wikipedia.org/wiki/LMS_color_space"/> /// <see href="https://en.wikipedia.org/wiki/LMS_color_space"/>
/// </summary> /// </summary>
internal struct Lms : IColorVector, IEquatable<Lms>, IAlmostEquatable<Lms, float> internal readonly struct Lms : IColorVector, IEquatable<Lms>, IAlmostEquatable<Lms, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Lms"/> that has L, M, and S values set to zero. /// Represents a <see cref="Lms"/> that has L, M, and S values set to zero.

2
src/ImageSharp/ColorSpaces/Rgb.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// <summary> /// <summary>
/// Represents an RGB color with specified <see cref="IRgbWorkingSpace"/> working space /// Represents an RGB color with specified <see cref="IRgbWorkingSpace"/> working space
/// </summary> /// </summary>
internal struct Rgb : IColorVector, IEquatable<Rgb>, IAlmostEquatable<Rgb, float> internal readonly struct Rgb : IColorVector, IEquatable<Rgb>, IAlmostEquatable<Rgb, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Rgb"/> that has R, G, and B values set to zero. /// Represents a <see cref="Rgb"/> that has R, G, and B values set to zero.

2
src/ImageSharp/ColorSpaces/YCbCr.cs

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/> /// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
/// <see href="http://www.ijg.org/files/T-REC-T.871-201105-I!!PDF-E.pdf"/> /// <see href="http://www.ijg.org/files/T-REC-T.871-201105-I!!PDF-E.pdf"/>
/// </summary> /// </summary>
internal struct YCbCr : IColorVector, IEquatable<YCbCr>, IAlmostEquatable<YCbCr, float> internal readonly struct YCbCr : IColorVector, IEquatable<YCbCr>, IAlmostEquatable<YCbCr, float>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="YCbCr"/> that has Y, Cb, and Cr values set to zero. /// Represents a <see cref="YCbCr"/> that has Y, Cb, and Cr values set to zero.

12
src/ImageSharp/Common/Extensions/SimdUtils.cs

@ -76,14 +76,14 @@ namespace SixLabors.ImageSharp
return; return;
} }
ref Vector<float> srcBase = ref Unsafe.As<float, Vector<float>>(ref source.DangerousGetPinnableReference()); ref Vector<float> srcBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Octet.OfByte destBase = ref Unsafe.As<byte, Octet.OfByte>(ref dest.DangerousGetPinnableReference()); ref Octet.OfByte destBase = ref Unsafe.As<byte, Octet.OfByte>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 8; int n = source.Length / 8;
Vector<float> magick = new Vector<float>(32768.0f); Vector<float> magick = new Vector<float>(32768.0f);
Vector<float> scale = new Vector<float>(255f) / new Vector<float>(256f); Vector<float> scale = new Vector<float>(255f) / new Vector<float>(256f);
// need to copy to a temporal struct, because // need to copy to a temporary struct, because
// SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x) // SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report // does not work. TODO: This might be a CoreClr bug, need to ask/report
var temp = default(Octet.OfUInt32); var temp = default(Octet.OfUInt32);
@ -117,14 +117,14 @@ namespace SixLabors.ImageSharp
return; return;
} }
ref Vector<float> srcBase = ref Unsafe.As<float, Vector<float>>(ref source.DangerousGetPinnableReference()); ref Vector<float> srcBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Octet.OfByte destBase = ref Unsafe.As<byte, Octet.OfByte>(ref dest.DangerousGetPinnableReference()); ref Octet.OfByte destBase = ref Unsafe.As<byte, Octet.OfByte>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 8; int n = source.Length / 8;
Vector<float> magick = new Vector<float>(32768.0f); Vector<float> magick = new Vector<float>(32768.0f);
Vector<float> scale = new Vector<float>(255f) / new Vector<float>(256f); Vector<float> scale = new Vector<float>(255f) / new Vector<float>(256f);
// need to copy to a temporal struct, because // need to copy to a temporary struct, because
// SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x) // SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report // does not work. TODO: This might be a CoreClr bug, need to ask/report
var temp = default(Octet.OfUInt32); var temp = default(Octet.OfUInt32);

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

@ -29,23 +29,16 @@ namespace SixLabors.ImageSharp
} }
else else
{ {
byte[] foo = ArrayPool<byte>.Shared.Rent(count); byte[] foo = new byte[count];
try while (count > 0)
{ {
while (count > 0) int bytesRead = stream.Read(foo, 0, count);
if (bytesRead == 0)
{ {
int bytesRead = stream.Read(foo, 0, count); break;
if (bytesRead == 0)
{
break;
}
count -= bytesRead;
} }
}
finally count -= bytesRead;
{
ArrayPool<byte>.Shared.Return(foo);
} }
} }
} }

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

@ -0,0 +1,60 @@
using System;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Utility methods for Parallel.For() execution. Use this instead of raw <see cref="Parallel"/> calls!
/// </summary>
internal static class ParallelFor
{
/// <summary>
/// Helper method to execute Parallel.For using the settings in <see cref="Configuration.ParallelOptions"/>
/// </summary>
public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action<int> body)
{
Parallel.For(fromInclusive, toExclusive, configuration.ParallelOptions, body);
}
/// <summary>
/// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks.
/// The buffer is not guaranteed to be clean!
/// </summary>
/// <typeparam name="T">The value type of the buffer</typeparam>
/// <param name="fromInclusive">The start index, inclusive.</param>
/// <param name="toExclusive">The end index, exclusive.</param>
/// <param name="configuration">The <see cref="Configuration"/> used for getting the <see cref="MemoryManager"/> and <see cref="ParallelOptions"/></param>
/// <param name="bufferLength">The length of the requested parallel buffer</param>
/// <param name="body">The delegate that is invoked once per iteration.</param>
public static void WithTemporaryBuffer<T>(
int fromInclusive,
int toExclusive,
Configuration configuration,
int bufferLength,
Action<int, IBuffer<T>> body)
where T : struct
{
MemoryManager memoryManager = configuration.MemoryManager;
ParallelOptions parallelOptions = configuration.ParallelOptions;
IBuffer<T> InitBuffer()
{
return memoryManager.Allocate<T>(bufferLength);
}
void CleanUpBuffer(IBuffer<T> buffer)
{
buffer.Dispose();
}
IBuffer<T> BodyFunc(int i, ParallelLoopState state, IBuffer<T> buffer)
{
body(i, buffer);
return buffer;
}
Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer);
}
}
}

406
src/ImageSharp/Configuration.cs

@ -1,261 +1,145 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{ namespace SixLabors.ImageSharp
/// <summary> {
/// Provides initialization code which allows extending the library. /// <summary>
/// </summary> /// Provides initialization code which allows extending the library.
public sealed class Configuration /// </summary>
{ public sealed class Configuration
/// <summary> {
/// A lazily initialized configuration default instance. /// <summary>
/// </summary> /// A lazily initialized configuration default instance.
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance); /// </summary>
private static readonly Lazy<Configuration> Lazy = new Lazy<Configuration>(CreateDefaultInstance);
/// <summary>
/// The list of supported <see cref="IImageEncoder"/> keyed to mime types. /// <summary>
/// </summary> /// Initializes a new instance of the <see cref="Configuration" /> class.
private readonly ConcurrentDictionary<IImageFormat, IImageEncoder> mimeTypeEncoders = new ConcurrentDictionary<IImageFormat, IImageEncoder>(); /// </summary>
public Configuration()
/// <summary> {
/// The list of supported <see cref="IImageEncoder"/> keyed to mime types. }
/// </summary>
private readonly ConcurrentDictionary<IImageFormat, IImageDecoder> mimeTypeDecoders = new ConcurrentDictionary<IImageFormat, IImageDecoder>(); /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// <summary> /// </summary>
/// The list of supported <see cref="IImageFormat"/>s. /// <param name="configurationModules">A collection of configuration modules to register</param>
/// </summary> public Configuration(params IConfigurationModule[] configurationModules)
private readonly ConcurrentBag<IImageFormat> imageFormats = new ConcurrentBag<IImageFormat>(); {
if (configurationModules != null)
/// <summary> {
/// The list of supported <see cref="IImageFormatDetector"/>s. foreach (IConfigurationModule p in configurationModules)
/// </summary> {
private ConcurrentBag<IImageFormatDetector> imageFormatDetectors = new ConcurrentBag<IImageFormatDetector>(); p.Configure(this);
}
/// <summary> }
/// Initializes a new instance of the <see cref="Configuration" /> class. }
/// </summary>
public Configuration() /// <summary>
{ /// Gets the default <see cref="Configuration"/> instance.
} /// </summary>
public static Configuration Default { get; } = Lazy.Value;
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// <summary>
/// </summary> /// Gets the global parallel options for processing tasks in parallel.
/// <param name="configurationModules">A collection of configuration modules to register</param> /// </summary>
public Configuration(params IConfigurationModule[] configurationModules) public ParallelOptions ParallelOptions { get; private set; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
{
if (configurationModules != null) /// <summary>
{ /// Gets the currently registered <see cref="IImageFormat"/>s.
foreach (IConfigurationModule p in configurationModules) /// </summary>
{ public IEnumerable<IImageFormat> ImageFormats => this.ImageFormatsManager.ImageFormats;
p.Configure(this);
} /// <summary>
} /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source.
} /// </summary>
public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current;
/// <summary>
/// Gets the default <see cref="Configuration"/> instance. /// <summary>
/// </summary> /// Gets or sets the <see cref="ImageFormatManager"/> that is currently in use.
public static Configuration Default { get; } = Lazy.Value; /// </summary>
public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager();
/// <summary>
/// Gets the global parallel options for processing tasks in parallel. /// <summary>
/// </summary> /// Gets or sets the <see cref="MemoryManager"/> that is currently in use.
public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; /// </summary>
public MemoryManager MemoryManager { get; set; } = ArrayPoolMemoryManager.CreateDefault();
/// <summary>
/// Gets the currently registered <see cref="IImageFormat"/>s. /// <summary>
/// </summary> /// Gets the maximum header size of all the formats.
public IEnumerable<IImageFormat> ImageFormats => this.imageFormats; /// </summary>
internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize;
/// <summary>
/// Gets the maximum header size of all the formats. #if !NETSTANDARD1_1
/// </summary> /// <summary>
internal int MaxHeaderSize { get; private set; } /// Gets or sets the filesystem helper for accessing the local file system.
/// </summary>
/// <summary> internal IFileSystem FileSystem { get; set; } = new LocalFileSystem();
/// Gets the currently registered <see cref="IImageFormatDetector"/>s. #endif
/// </summary>
internal IEnumerable<IImageFormatDetector> FormatDetectors => this.imageFormatDetectors; /// <summary>
/// Gets or sets the image operations provider factory.
/// <summary> /// </summary>
/// Gets the currently registered <see cref="IImageDecoder"/>s. internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory();
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, IImageDecoder>> ImageDecoders => this.mimeTypeDecoders; /// <summary>
/// Registers a new format provider.
/// <summary> /// </summary>
/// Gets the currently registered <see cref="IImageEncoder"/>s. /// <param name="configuration">The configuration provider to call configure on.</param>
/// </summary> public void Configure(IConfigurationModule configuration)
internal IEnumerable<KeyValuePair<IImageFormat, IImageEncoder>> ImageEncoders => this.mimeTypeEncoders; {
Guard.NotNull(configuration, nameof(configuration));
#if !NETSTANDARD1_1 configuration.Configure(this);
/// <summary> }
/// Gets or sets the filesystem helper for accessing the local file system.
/// </summary> /// <summary>
internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); /// Creates a shallow copy of the <see cref="Configuration"/>
#endif /// </summary>
/// <returns>A new configuration instance</returns>
/// <summary> public Configuration ShallowCopy()
/// Gets or sets the image operations provider factory. {
/// </summary> return new Configuration
internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); {
ParallelOptions = this.ParallelOptions,
/// <summary> ImageFormatsManager = this.ImageFormatsManager,
/// Registers a new format provider. MemoryManager = this.MemoryManager,
/// </summary> ImageOperationsProvider = this.ImageOperationsProvider,
/// <param name="configuration">The configuration provider to call configure on.</param> ReadOrigin = this.ReadOrigin,
public void Configure(IConfigurationModule configuration)
{ #if !NETSTANDARD1_1
Guard.NotNull(configuration, nameof(configuration)); FileSystem = this.FileSystem
configuration.Configure(this); #endif
} };
}
/// <summary>
/// Registers a new format provider. /// <summary>
/// </summary> /// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
/// <param name="format">The format to register as a known format.</param> /// <para><see cref="PngConfigurationModule"/></para>
public void AddImageFormat(IImageFormat format) /// <para><see cref="JpegConfigurationModule"/></para>
{ /// <para><see cref="GifConfigurationModule"/></para>
Guard.NotNull(format, nameof(format)); /// <para><see cref="BmpConfigurationModule"/></para>
Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); /// </summary>
Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); /// <returns>The default configuration of <see cref="Configuration"/></returns>
this.imageFormats.Add(format); internal static Configuration CreateDefaultInstance()
} {
return new Configuration(
/// <summary> new PngConfigurationModule(),
/// For the specified file extensions type find the e <see cref="IImageFormat"/>. new JpegConfigurationModule(),
/// </summary> new GifConfigurationModule(),
/// <param name="extension">The extension to discover</param> new BmpConfigurationModule());
/// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns> }
public IImageFormat FindFormatByFileExtension(string extension) }
{ }
return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// For the specified mime type find the <see cref="IImageFormat"/>.
/// </summary>
/// <param name="mimeType">The mime-type to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found; otherwise null</returns>
public IImageFormat FindFormatByMimeType(string mimeType)
{
return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// Sets a specific image encoder as the encoder for a specific image format.
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="encoder">The encoder to use,</param>
public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(encoder, nameof(encoder));
this.AddImageFormat(imageFormat);
this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder);
}
/// <summary>
/// Sets a specific image decoder as the decoder for a specific image format.
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="decoder">The decoder to use,</param>
public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(decoder, nameof(decoder));
this.AddImageFormat(imageFormat);
this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder);
}
/// <summary>
/// Removes all the registered image format detectors.
/// </summary>
public void ClearImageFormatDetectors()
{
this.imageFormatDetectors = new ConcurrentBag<IImageFormatDetector>();
}
/// <summary>
/// Adds a new detector for detecting mime types.
/// </summary>
/// <param name="detector">The detector to add</param>
public void AddImageFormatDetector(IImageFormatDetector detector)
{
Guard.NotNull(detector, nameof(detector));
this.imageFormatDetectors.Add(detector);
this.SetMaxHeaderSize();
}
/// <summary>
/// For the specified mime type find the decoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
public IImageDecoder FindDecoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder))
{
return decoder;
}
return null;
}
/// <summary>
/// For the specified mime type find the encoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageEncoder"/> if found otherwise null</returns>
public IImageEncoder FindEncoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder))
{
return encoder;
}
return null;
}
/// <summary>
/// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
/// <para><see cref="PngConfigurationModule"/></para>
/// <para><see cref="JpegConfigurationModule"/></para>
/// <para><see cref="GifConfigurationModule"/></para>
/// <para><see cref="BmpConfigurationModule"/></para>
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/></returns>
internal static Configuration CreateDefaultInstance()
{
return new Configuration(
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule());
}
/// <summary>
/// Sets the max header size.
/// </summary>
private void SetMaxHeaderSize()
{
this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize);
}
}
}

15
src/ImageSharp/DefaultInternalImageProcessorContext.cs

@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -34,6 +36,9 @@ namespace SixLabors.ImageSharp
} }
} }
/// <inheritdoc/>
public MemoryManager MemoryManager => this.source.GetConfiguration().MemoryManager;
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Apply() public Image<TPixel> Apply()
{ {
@ -46,6 +51,9 @@ namespace SixLabors.ImageSharp
return this.destination; return this.destination;
} }
/// <inheritdoc/>
public Size GetCurrentSize() => this.GetCurrentBounds().Size;
/// <inheritdoc/> /// <inheritdoc/>
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle) public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle)
{ {
@ -70,7 +78,12 @@ namespace SixLabors.ImageSharp
/// <inheritdoc/> /// <inheritdoc/>
public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor) public IImageProcessingContext<TPixel> ApplyProcessor(IImageProcessor<TPixel> processor)
{ {
return this.ApplyProcessor(processor, this.source.Bounds()); return this.ApplyProcessor(processor, this.GetCurrentBounds());
}
private Rectangle GetCurrentBounds()
{
return this.destination?.Bounds() ?? this.source.Bounds();
} }
} }
} }

16
src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs

@ -70,22 +70,10 @@ namespace SixLabors.ImageSharp.Dithering.Base
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.Dither(pixels, source, transformed, x, y, minX, minY, maxX, maxY, true); image[x, y] = transformed;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
where TPixel : struct, IPixel<TPixel>
{
if (replacePixel)
{
// Assign the transformed pixel to the array.
image[x, y] = transformed;
}
// Calculate the error // Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4(); Vector4 error = source.ToVector4() - transformed.ToVector4();

24
src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering namespace SixLabors.ImageSharp.Dithering
{ {
/// <summary> /// <summary>
/// Encapsulates properties and methods required to perfom diffused error dithering on an image. /// Encapsulates properties and methods required to perform diffused error dithering on an image.
/// </summary> /// </summary>
public interface IErrorDiffuser public interface IErrorDiffuser
{ {
@ -25,25 +25,5 @@ namespace SixLabors.ImageSharp.Dithering
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="image">The image</param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="minX">The minimum column value.</param>
/// <param name="minY">The minimum row value.</param>
/// <param name="maxX">The maximum column value.</param>
/// <param name="maxY">The maximum row value.</param>
/// <param name="replacePixel">
/// Whether to replace the pixel at the given coordinates with the transformed value.
/// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false.
/// </param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
where TPixel : struct, IPixel<TPixel>;
} }
} }

56
src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Contains reusable static instances of known error diffusion algorithms
/// </summary>
public static class KnownDiffusers
{
/// <summary>
/// Gets the error diffuser that implements the Atkinson algorithm.
/// </summary>
public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Burks algorithm.
/// </summary>
public static IErrorDiffuser Burks { get; } = new BurksDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Floyd-Steinberg algorithm.
/// </summary>
public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm.
/// </summary>
public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-2 algorithm.
/// </summary>
public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-3 algorithm.
/// </summary>
public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-Lite algorithm.
/// </summary>
public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Stevenson-Arce algorithm.
/// </summary>
public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Stucki algorithm.
/// </summary>
public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser();
}
}

34
src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering.Base;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm.
/// </summary>
public sealed class StevensonArceDiffuser : ErrorDiffuserBase
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly Fast2DArray<float> StevensonArceMatrix =
new float[,]
{
{ 0, 0, 0, 0, 0, 32, 0 },
{ 12, 0, 26, 0, 30, 0, 16 },
{ 0, 12, 0, 26, 0, 12, 0 },
{ 5, 0, 12, 0, 12, 0, 5 }
};
/// <summary>
/// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class.
/// </summary>
public StevensonArceDiffuser()
: base(StevensonArceMatrix, 200)
{
}
}
}

36
src/ImageSharp/Dithering/Ordered/BayerDither.cs

@ -1,36 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering.Base;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class BayerDither : OrderedDitherBase
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
{
{ 15, 143, 47, 175 },
{ 207, 79, 239, 111 },
{ 63, 191, 31, 159 },
{ 255, 127, 223, 95 }
};
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither"/> class.
/// </summary>
public BayerDither()
: base(ThresholdMatrix)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 2x2 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither2x2 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither2x2"/> class.
/// </summary>
public BayerDither2x2()
: base(2)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 4x4 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither4x4 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither4x4"/> class.
/// </summary>
public BayerDither4x4()
: base(4)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 8x8 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither8x8 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither8x8"/> class.
/// </summary>
public BayerDither8x8()
: base(8)
{
}
}
}

7
src/ImageSharp/Dithering/Ordered/IOrderedDither.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering namespace SixLabors.ImageSharp.Dithering
{ {
/// <summary> /// <summary>
/// Encapsulates properties and methods required to perfom ordered dithering on an image. /// Encapsulates properties and methods required to perform ordered dithering on an image.
/// </summary> /// </summary>
public interface IOrderedDither public interface IOrderedDither
{ {
@ -17,12 +17,11 @@ namespace SixLabors.ImageSharp.Dithering
/// <param name="source">The source pixel</param> /// <param name="source">The source pixel</param>
/// <param name="upper">The color to apply to the pixels above the threshold.</param> /// <param name="upper">The color to apply to the pixels above the threshold.</param>
/// <param name="lower">The color to apply to the pixels below the threshold.</param> /// <param name="lower">The color to apply to the pixels below the threshold.</param>
/// <param name="rgba">The <see cref="Rgba32"/> to pack/unpack to.</param> /// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <param name="x">The column index.</param> /// <param name="x">The column index.</param>
/// <param name="y">The row index.</param> /// <param name="y">The row index.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y) void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

31
src/ImageSharp/Dithering/Ordered/KnownDitherers.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Contains reusable static instances of known ordered dither matrices
/// </summary>
public class KnownDitherers
{
/// <summary>
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2();
/// <summary>
/// Gets the order ditherer using the 3x3 dithering matrix
/// </summary>
public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3();
/// <summary>
/// Gets the order ditherer using the 4x4 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4();
/// <summary>
/// Gets the order ditherer using the 8x8 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8();
}
}

50
src/ImageSharp/Dithering/Ordered/OrderedDither.cs

@ -1,36 +1,50 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering.Base;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering namespace SixLabors.ImageSharp.Dithering
{ {
/// <summary> /// <summary>
/// Applies error diffusion based dithering using the 4x4 ordered dithering matrix. /// An ordered dithering matrix with equal sides of arbitrary length
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary> /// </summary>
public sealed class OrderedDither : OrderedDitherBase public class OrderedDither : IOrderedDither
{ {
/// <summary> private readonly Fast2DArray<uint> thresholdMatrix;
/// The threshold matrix. private readonly int modulusX;
/// This is calculated by multiplying each value in the original matrix by 16 private readonly int modulusY;
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
{
{ 0, 128, 32, 160 },
{ 192, 64, 224, 96 },
{ 48, 176, 16, 144 },
{ 240, 112, 208, 80 }
};
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OrderedDither"/> class. /// Initializes a new instance of the <see cref="OrderedDither"/> class.
/// </summary> /// </summary>
public OrderedDither() /// <param name="length">The length of the matrix sides</param>
: base(ThresholdMatrix) public OrderedDither(uint length)
{
Fast2DArray<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length);
this.modulusX = ditherMatrix.Width;
this.modulusY = ditherMatrix.Height;
// Adjust the matrix range for 0-255
// It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2
// https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg
int multiplier = 256 / ditherMatrix.Count;
for (int y = 0; y < ditherMatrix.Height; y++)
{
for (int x = 0; x < ditherMatrix.Width; x++)
{
ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1;
}
}
this.thresholdMatrix = ditherMatrix;
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>
{ {
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper;
} }
} }
} }

19
src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 3x3 dithering matrix.
/// </summary>
public sealed class OrderedDither3x3 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither3x3"/> class.
/// </summary>
public OrderedDither3x3()
: base(3)
{
}
}
}

53
src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering.Base
{
/// <summary>
/// The base class for performing ordered dithering using a 4x4 matrix.
/// </summary>
public abstract class OrderedDitherBase : IOrderedDither
{
/// <summary>
/// The dithering matrix
/// </summary>
private Fast2DArray<byte> matrix;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
/// </summary>
/// <param name="matrix">The thresholding matrix. </param>
internal OrderedDitherBase(Fast2DArray<byte> matrix)
{
this.matrix = matrix;
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
where TPixel : struct, IPixel<TPixel>
{
source.ToRgba32(ref rgba);
switch (index)
{
case 0:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.R ? lower : upper;
return;
case 1:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.G ? lower : upper;
return;
case 2:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.B ? lower : upper;
return;
case 3:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.A ? lower : upper;
return;
}
throw new ArgumentOutOfRangeException(nameof(index), "Index should be between 0 and 3 inclusive.");
}
}
}

94
src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs

@ -0,0 +1,94 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// A factory for creating ordered dither matrices.
/// </summary>
internal static class OrderedDitherFactory
{
/// <summary>
/// Creates an ordered dithering matrix with equal sides of arbitrary length.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary>
/// <param name="length">The length of the matrix sides</param>
/// <returns>The <see cref="Fast2DArray{T}"/></returns>
public static Fast2DArray<uint> CreateDitherMatrix(uint length)
{
// Calculate the the logarithm of length to the base 2
uint exponent = 0;
uint bayerLength = 0;
do
{
exponent++;
bayerLength = (uint)(1 << (int)exponent);
}
while (length > bayerLength);
// Create our Bayer matrix that matches the given exponent and dimensions
var matrix = new Fast2DArray<uint>((int)length);
uint i = 0;
for (int y = 0; y < length; y++)
{
for (int x = 0; x < length; x++)
{
matrix[y, x] = Bayer(i / length, i % length, exponent);
i++;
}
}
// If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm,
// we need to convert the numbers so that the resulting range is un-gapped.
// We generated: We saved: We compress the number range:
// 0 8 2 10 0 8 2 0 5 2
// 12 4 14 6 12 4 14 7 4 8
// 3 11 1 9 3 11 1 3 6 1
// 15 7 13 5
uint maxValue = bayerLength * bayerLength;
uint missing = 0;
for (uint v = 0; v < maxValue; ++v)
{
bool found = false;
for (int y = 0; y < length; ++y)
{
for (int x = 0; x < length; x++)
{
if (matrix[y, x] == v)
{
matrix[y, x] -= missing;
found = true;
break;
}
}
}
if (!found)
{
++missing;
}
}
return matrix;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint Bayer(uint x, uint y, uint order)
{
uint result = 0;
for (uint i = 0; i < order; ++i)
{
uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1);
uint xOdd = x & 1;
result = ((result << 1 | xOdd_XOR_yOdd) << 1) | xOdd;
x >>= 1;
y >>= 1;
}
return result;
}
}
}

58
src/ImageSharp/Dithering/error_diffusion.txt

@ -0,0 +1,58 @@
List of error diffusion schemes.
Quantization error of *current* pixel is added to the pixels
on the right and below according to the formulas below.
This works nicely for most static pictures, but causes
an avalanche of jittering artifacts if used in animation.
Floyd-Steinberg:
* 7
3 5 1 / 16
Jarvis-Judice-Ninke:
* 7 5
3 5 7 5 3
1 3 5 3 1 / 48
Stucki:
* 8 4
2 4 8 4 2
1 2 4 2 1 / 42
Burkes:
* 8 4
2 4 8 4 2 / 32
Sierra3:
* 5 3
2 4 5 4 2
2 3 2 / 32
Sierra2:
* 4 3
1 2 3 2 1 / 16
Sierra-2-4A:
* 2
1 1 / 4
Stevenson-Arce:
* . 32
12 . 26 . 30 . 16
. 12 . 26 . 12 .
5 . 12 . 12 . 5 / 200
Atkinson:
* 1 1 / 8
1 1 1
1

6
src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs

@ -11,9 +11,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <inheritdoc/> /// <inheritdoc/>
public void Configure(Configuration config) public void Configure(Configuration config)
{ {
config.SetEncoder(ImageFormats.Bmp, new BmpEncoder()); config.ImageFormatsManager.SetEncoder(ImageFormats.Bmp, new BmpEncoder());
config.SetDecoder(ImageFormats.Bmp, new BmpDecoder()); config.ImageFormatsManager.SetDecoder(ImageFormats.Bmp, new BmpDecoder());
config.AddImageFormatDetector(new BmpImageFormatDetector()); config.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector());
} }
} }
} }

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

@ -69,7 +69,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary> /// </summary>
private BmpInfoHeader infoHeader; private BmpInfoHeader infoHeader;
private Configuration configuration; private readonly Configuration configuration;
private readonly MemoryManager memoryManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class. /// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
@ -79,6 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
{ {
this.configuration = configuration; this.configuration = configuration;
this.memoryManager = configuration.MemoryManager;
} }
/// <summary> /// <summary>
@ -221,9 +224,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
var color = default(TPixel); var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255); var rgba = new Rgba32(0, 0, 0, 255);
using (var buffer = Buffer2D<byte>.CreateClean(width, height)) using (var buffer = this.memoryManager.AllocateClean2D<byte>(width, height))
{ {
this.UncompressRle8(width, buffer); this.UncompressRle8(width, buffer.Span);
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -343,15 +346,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding; padding = 4 - padding;
} }
using (var row = Buffer<byte>.CreateClean(arrayWidth + padding)) using (IManagedByteBuffer row = this.memoryManager.AllocateCleanManagedByteBuffer(arrayWidth + padding))
{ {
var color = default(TPixel); var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255); var rgba = new Rgba32(0, 0, 0, 255);
Span<byte> rowSpan = row.Span;
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
this.currentStream.Read(row.Array, 0, row.Length); this.currentStream.Read(row.Array, 0, row.Length());
int offset = 0; int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
@ -362,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int shift = 0; shift < ppb && (x + shift) < width; shift++) for (int shift = 0; shift < ppb && (x + shift) < width; shift++)
{ {
int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4; int colorIndex = ((rowSpan[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int newX = colOffset + shift; int newX = colOffset + shift;
// Stored in b-> g-> r order. // Stored in b-> g-> r order.
@ -393,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
var color = default(TPixel); var color = default(TPixel);
var rgba = new Rgba32(0, 0, 0, 255); var rgba = new Rgba32(0, 0, 0, 255);
using (var buffer = new Buffer<byte>(stride)) using (var buffer = this.memoryManager.AllocateManagedByteBuffer(stride))
{ {
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
@ -430,14 +435,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
int padding = CalculatePadding(width, 3); int padding = CalculatePadding(width, 3);
using (var row = new PixelArea<TPixel>(width, ComponentOrder.Zyx, padding))
using (IManagedByteBuffer row = this.memoryManager.AllocatePaddedPixelRowBuffer(width, 3, padding))
{ {
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
row.Read(this.currentStream); this.currentStream.Read(row);
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
pixels.CopyFrom(row, newY); Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.PackFromBgr24Bytes(row.Span, pixelSpan, width);
} }
} }
} }
@ -454,14 +460,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
int padding = CalculatePadding(width, 4); int padding = CalculatePadding(width, 4);
using (var row = new PixelArea<TPixel>(width, ComponentOrder.Zyxw, padding))
using (IManagedByteBuffer row = this.memoryManager.AllocatePaddedPixelRowBuffer(width, 4, padding))
{ {
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
row.Read(this.currentStream); this.currentStream.Read(row);
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
pixels.CopyFrom(row, newY); Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.PackFromBgra32Bytes(row.Span, pixelSpan, width);
} }
} }
} }

3
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp namespace SixLabors.ImageSharp.Formats.Bmp
@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public void Encode<TPixel>(Image<TPixel> image, Stream stream) public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var encoder = new BmpEncoderCore(this); var encoder = new BmpEncoderCore(this, image.GetMemoryManager());
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
} }

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

@ -4,6 +4,7 @@
using System; using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp namespace SixLabors.ImageSharp.Formats.Bmp
@ -21,14 +22,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary> /// <summary>
/// Gets or sets the number of bits per pixel. /// Gets or sets the number of bits per pixel.
/// </summary> /// </summary>
private BmpBitsPerPixel bitsPerPixel; private readonly BmpBitsPerPixel bitsPerPixel;
private readonly MemoryManager memoryManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class. /// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The encoder options</param> /// <param name="options">The encoder options</param>
public BmpEncoderCore(IBmpEncoderOptions options) /// <param name="memoryManager">The memory manager</param>
public BmpEncoderCore(IBmpEncoderOptions options, MemoryManager memoryManager)
{ {
this.memoryManager = memoryManager;
this.bitsPerPixel = options.BitsPerPixel; this.bitsPerPixel = options.BitsPerPixel;
} }
@ -145,6 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
} }
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel)
{
return this.memoryManager.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
}
/// <summary> /// <summary>
/// Writes the 32bit color palette to the stream. /// Writes the 32bit color palette to the stream.
/// </summary> /// </summary>
@ -154,12 +164,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write32Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels) private void Write32Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (PixelArea<TPixel> row = new PixelArea<TPixel>(pixels.Width, ComponentOrder.Zyxw, this.padding)) using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
{ {
for (int y = pixels.Height - 1; y >= 0; y--) for (int y = pixels.Height - 1; y >= 0; y--)
{ {
pixels.CopyTo(row, y); Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
writer.Write(row.Bytes, 0, row.Length); PixelOperations<TPixel>.Instance.ToBgra32Bytes(pixelSpan, row.Span, pixelSpan.Length);
writer.Write(row.Array, 0, row.Length());
} }
} }
} }
@ -173,12 +184,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write24Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels) private void Write24Bit<TPixel>(EndianBinaryWriter writer, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (PixelArea<TPixel> row = new PixelArea<TPixel>(pixels.Width, ComponentOrder.Zyx, this.padding)) using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
{ {
for (int y = pixels.Height - 1; y >= 0; y--) for (int y = pixels.Height - 1; y >= 0; y--)
{ {
pixels.CopyTo(row, y); Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
writer.Write(row.Bytes, 0, row.Length); PixelOperations<TPixel>.Instance.ToBgr24Bytes(pixelSpan, row.Span, pixelSpan.Length);
writer.Write(row.Array, 0, row.Length());
} }
} }
} }

2
src/ImageSharp/Formats/Bmp/ImageExtensions.cs

@ -36,6 +36,6 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream, BmpEncoder encoder) public static void SaveAsBmp<TPixel>(this Image<TPixel> source, Stream stream, BmpEncoder encoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().FindEncoder(ImageFormats.Bmp)); => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Bmp));
} }
} }

6
src/ImageSharp/Formats/Gif/GifConfigurationModule.cs

@ -11,10 +11,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <inheritdoc/> /// <inheritdoc/>
public void Configure(Configuration config) public void Configure(Configuration config)
{ {
config.SetEncoder(ImageFormats.Gif, new GifEncoder()); config.ImageFormatsManager.SetEncoder(ImageFormats.Gif, new GifEncoder());
config.SetDecoder(ImageFormats.Gif, new GifDecoder()); config.ImageFormatsManager.SetDecoder(ImageFormats.Gif, new GifDecoder());
config.AddImageFormatDetector(new GifImageFormatDetector()); config.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector());
} }
} }
} }

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

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The global color table. /// The global color table.
/// </summary> /// </summary>
private Buffer<byte> globalColorTable; private IManagedByteBuffer globalColorTable;
/// <summary> /// <summary>
/// The global color table length /// The global color table length
@ -92,6 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
public FrameDecodingMode DecodingMode { get; } public FrameDecodingMode DecodingMode { get; }
private MemoryManager MemoryManager => this.configuration.MemoryManager;
/// <summary> /// <summary>
/// Decodes the stream to the image. /// Decodes the stream to the image.
/// </summary> /// </summary>
@ -333,18 +335,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
continue; continue;
} }
byte[] commentsBuffer = ArrayPool<byte>.Shared.Rent(length); using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length))
try
{ {
this.currentStream.Read(commentsBuffer, 0, length); this.currentStream.Read(commentsBuffer.Array, 0, length);
string comments = this.TextEncoding.GetString(commentsBuffer, 0, length); string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length);
this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
} }
finally
{
ArrayPool<byte>.Shared.Return(commentsBuffer);
}
} }
} }
@ -359,22 +355,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
Buffer<byte> localColorTable = null; IManagedByteBuffer localColorTable = null;
Buffer<byte> indices = null; IManagedByteBuffer indices = null;
try try
{ {
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
if (imageDescriptor.LocalColorTableFlag) if (imageDescriptor.LocalColorTableFlag)
{ {
int length = imageDescriptor.LocalColorTableSize * 3; int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = Buffer<byte>.CreateClean(length); localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true);
this.currentStream.Read(localColorTable.Array, 0, length); this.currentStream.Read(localColorTable.Array, 0, length);
} }
indices = Buffer<byte>.CreateClean(imageDescriptor.Width * imageDescriptor.Height); indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
this.ReadFrameIndices(imageDescriptor, indices); this.ReadFrameIndices(imageDescriptor, indices.Span);
this.ReadFrameColors(ref image, ref previousFrame, indices, localColorTable ?? this.globalColorTable, imageDescriptor); IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable;
this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor);
// Skip any remaining blocks // Skip any remaining blocks
this.Skip(0); this.Skip(0);
@ -395,7 +392,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, Span<byte> indices) private void ReadFrameIndices(GifImageDescriptor imageDescriptor, Span<byte> indices)
{ {
int dataSize = this.currentStream.ReadByte(); int dataSize = this.currentStream.ReadByte();
using (var lzwDecoder = new LzwDecoder(this.currentStream)) using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.currentStream))
{ {
lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
} }
@ -445,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
imageFrame = currentFrame; imageFrame = currentFrame;
this.RestoreToBackground(imageFrame, image.Width, image.Height); this.RestoreToBackground(imageFrame);
} }
int i = 0; int i = 0;
@ -535,9 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The frame.</param> /// <param name="frame">The frame.</param>
/// <param name="imageWidth">Width of the image.</param> private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
/// <param name="imageHeight">Height of the image.</param>
private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame, int imageWidth, int imageHeight)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.restoreArea == null) if (this.restoreArea == null)
@ -545,28 +540,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
return; return;
} }
// Optimization for when the size of the frame is the same as the image size. BufferArea<TPixel> pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value);
if (this.restoreArea.Value.Width == imageWidth && pixelArea.Clear();
this.restoreArea.Value.Height == imageHeight)
{
using (PixelAccessor<TPixel> pixelAccessor = frame.Lock())
{
pixelAccessor.Reset();
}
}
else
{
using (var emptyRow = new PixelArea<TPixel>(this.restoreArea.Value.Width, ComponentOrder.Xyzw))
{
using (PixelAccessor<TPixel> pixelAccessor = frame.Lock())
{
for (int y = this.restoreArea.Value.Top; y < this.restoreArea.Value.Top + this.restoreArea.Value.Height; y++)
{
pixelAccessor.CopyFrom(emptyRow, y, this.restoreArea.Value.Left);
}
}
}
}
this.restoreArea = null; this.restoreArea = null;
} }
@ -606,7 +581,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.logicalScreenDescriptor.GlobalColorTableFlag) if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{ {
this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.globalColorTable = Buffer<byte>.CreateClean(this.globalColorTableLength);
this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true);
// Read the global color table from the stream // Read the global color table from the stream
stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength);

4
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Quantizers; using SixLabors.ImageSharp.Quantizers;
@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
public void Encode<TPixel>(Image<TPixel> image, Stream stream) public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var encoder = new GifEncoderCore(this); var encoder = new GifEncoderCore(image.GetConfiguration().MemoryManager, this);
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
} }

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

@ -7,6 +7,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Quantizers; using SixLabors.ImageSharp.Quantizers;
@ -18,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
internal sealed class GifEncoderCore internal sealed class GifEncoderCore
{ {
private readonly MemoryManager memoryManager;
/// <summary> /// <summary>
/// The temp buffer used to reduce allocations. /// The temp buffer used to reduce allocations.
/// </summary> /// </summary>
@ -61,9 +64,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="options">The options for the encoder.</param> /// <param name="options">The options for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options) public GifEncoderCore(MemoryManager memoryManager, IGifEncoderOptions options)
{ {
this.memoryManager = memoryManager;
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.quantizer = options.Quantizer; this.quantizer = options.Quantizer;
@ -350,24 +355,21 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Get max colors for bit depth. // Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3;
byte[] colorTable = ArrayPool<byte>.Shared.Rent(colorTableLength);
var rgb = default(Rgb24); var rgb = default(Rgb24);
try using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
{ {
Span<byte> colorTableSpan = colorTable.Span;
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 3; int offset = i * 3;
image.Palette[i].ToRgb24(ref rgb); image.Palette[i].ToRgb24(ref rgb);
colorTable[offset] = rgb.R; colorTableSpan[offset] = rgb.R;
colorTable[offset + 1] = rgb.G; colorTableSpan[offset + 1] = rgb.G;
colorTable[offset + 2] = rgb.B; colorTableSpan[offset + 2] = rgb.B;
} }
writer.Write(colorTable, 0, colorTableLength); writer.Write(colorTable.Array, 0, colorTableLength);
}
finally
{
ArrayPool<byte>.Shared.Return(colorTable);
} }
} }
@ -380,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteImageData<TPixel>(QuantizedImage<TPixel> image, EndianBinaryWriter writer) private void WriteImageData<TPixel>(QuantizedImage<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) using (var encoder = new LzwEncoder(this.memoryManager, image.Pixels, (byte)this.bitDepth))
{ {
encoder.Encode(writer.BaseStream); encoder.Encode(writer.BaseStream);
} }

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

@ -36,6 +36,6 @@ namespace SixLabors.ImageSharp
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, GifEncoder encoder) public static void SaveAsGif<TPixel>(this Image<TPixel> source, Stream stream, GifEncoder encoder)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.Save(stream, encoder ?? source.GetConfiguration().FindEncoder(ImageFormats.Gif)); => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Gif));
} }
} }

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

@ -5,6 +5,8 @@ using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
{ {
/// <summary> /// <summary>
@ -30,17 +32,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The prefix buffer. /// The prefix buffer.
/// </summary> /// </summary>
private readonly int[] prefix; private readonly IBuffer<int> prefix;
/// <summary> /// <summary>
/// The suffix buffer. /// The suffix buffer.
/// </summary> /// </summary>
private readonly int[] suffix; private readonly IBuffer<int> suffix;
/// <summary> /// <summary>
/// The pixel stack buffer. /// The pixel stack buffer.
/// </summary> /// </summary>
private readonly int[] pixelStack; private readonly IBuffer<int> pixelStack;
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
@ -59,21 +61,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Initializes a new instance of the <see cref="LzwDecoder"/> class /// Initializes a new instance of the <see cref="LzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from. /// and sets the stream, where the compressed data should be read from.
/// </summary> /// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="stream">The stream to read from.</param> /// <param name="stream">The stream to read from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception> /// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
public LzwDecoder(Stream stream) public LzwDecoder(MemoryManager memoryManager, Stream stream)
{ {
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.stream = stream; this.stream = stream;
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize); this.prefix = memoryManager.Allocate<int>(MaxStackSize, true);
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize); this.suffix = memoryManager.Allocate<int>(MaxStackSize, true);
this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1); this.pixelStack = memoryManager.Allocate<int>(MaxStackSize + 1, true);
Array.Clear(this.prefix, 0, MaxStackSize);
Array.Clear(this.suffix, 0, MaxStackSize);
Array.Clear(this.pixelStack, 0, MaxStackSize + 1);
} }
/// <summary> /// <summary>
@ -116,10 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
int data = 0; int data = 0;
int first = 0; int first = 0;
Span<int> prefixSpan = this.prefix.Span;
Span<int> suffixSpan = this.suffix.Span;
Span<int> pixelStackSpan = this.pixelStack.Span;
for (code = 0; code < clearCode; code++) for (code = 0; code < clearCode; code++)
{ {
this.prefix[code] = 0; prefixSpan[code] = 0;
this.suffix[code] = (byte)code; suffixSpan[code] = (byte)code;
} }
byte[] buffer = new byte[255]; byte[] buffer = new byte[255];
@ -173,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (oldCode == NullCode) if (oldCode == NullCode)
{ {
this.pixelStack[top++] = this.suffix[code]; pixelStackSpan[top++] = suffixSpan[code];
oldCode = code; oldCode = code;
first = code; first = code;
continue; continue;
@ -182,27 +185,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
int inCode = code; int inCode = code;
if (code == availableCode) if (code == availableCode)
{ {
this.pixelStack[top++] = (byte)first; pixelStackSpan[top++] = (byte)first;
code = oldCode; code = oldCode;
} }
while (code > clearCode) while (code > clearCode)
{ {
this.pixelStack[top++] = this.suffix[code]; pixelStackSpan[top++] = suffixSpan[code];
code = this.prefix[code]; code = prefixSpan[code];
} }
first = this.suffix[code]; first = suffixSpan[code];
this.pixelStack[top++] = this.suffix[code]; pixelStackSpan[top++] = suffixSpan[code];
// Fix for Gifs that have "deferred clear code" as per here : // Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918 // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize) if (availableCode < MaxStackSize)
{ {
this.prefix[availableCode] = oldCode; prefixSpan[availableCode] = oldCode;
this.suffix[availableCode] = first; suffixSpan[availableCode] = first;
availableCode++; availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize) if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{ {
@ -218,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
top--; top--;
// Clear missing pixels // Clear missing pixels
pixels[xyz++] = (byte)this.pixelStack[top]; pixels[xyz++] = (byte)pixelStackSpan[top];
} }
} }
@ -262,9 +265,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (disposing) if (disposing)
{ {
ArrayPool<int>.Shared.Return(this.prefix); this.prefix?.Dispose();
ArrayPool<int>.Shared.Return(this.suffix); this.suffix?.Dispose();
ArrayPool<int>.Shared.Return(this.pixelStack); this.pixelStack?.Dispose();
} }
this.isDisposed = true; this.isDisposed = true;

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

@ -5,6 +5,8 @@ using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
{ {
/// <summary> /// <summary>
@ -69,12 +71,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The hash table. /// The hash table.
/// </summary> /// </summary>
private readonly int[] hashTable; private readonly IBuffer<int> hashTable;
/// <summary> /// <summary>
/// The code table. /// The code table.
/// </summary> /// </summary>
private readonly int[] codeTable; private readonly IBuffer<int> codeTable;
/// <summary> /// <summary>
/// Define the storage for the packet accumulator. /// Define the storage for the packet accumulator.
@ -189,17 +191,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LzwEncoder"/> class. /// Initializes a new instance of the <see cref="LzwEncoder"/> class.
/// </summary> /// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="indexedPixels">The array of indexed pixels.</param> /// <param name="indexedPixels">The array of indexed pixels.</param>
/// <param name="colorDepth">The color depth in bits.</param> /// <param name="colorDepth">The color depth in bits.</param>
public LzwEncoder(byte[] indexedPixels, int colorDepth) public LzwEncoder(MemoryManager memoryManager, byte[] indexedPixels, int colorDepth)
{ {
this.pixelArray = indexedPixels; this.pixelArray = indexedPixels;
this.initialCodeSize = Math.Max(2, colorDepth); this.initialCodeSize = Math.Max(2, colorDepth);
this.hashTable = ArrayPool<int>.Shared.Rent(HashSize); this.hashTable = memoryManager.Allocate<int>(HashSize, true);
this.codeTable = ArrayPool<int>.Shared.Rent(HashSize); this.codeTable = memoryManager.Allocate<int>(HashSize, true);
Array.Clear(this.hashTable, 0, HashSize);
Array.Clear(this.codeTable, 0, HashSize);
} }
/// <summary> /// <summary>
@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="stream">The output stream.</param> /// <param name="stream">The output stream.</param>
private void ClearBlock(Stream stream) private void ClearBlock(Stream stream)
{ {
this.ResetCodeTable(this.hsize); this.ResetCodeTable();
this.freeEntry = this.clearCode + 2; this.freeEntry = this.clearCode + 2;
this.clearFlag = true; this.clearFlag = true;
@ -268,13 +269,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Reset the code table. /// Reset the code table.
/// </summary> /// </summary>
/// <param name="size">The hash size.</param> private void ResetCodeTable()
private void ResetCodeTable(int size)
{ {
for (int i = 0; i < size; ++i) this.hashTable.Span.Fill(-1);
{
this.hashTable[i] = -1; // Original code:
} // for (int i = 0; i < size; ++i)
// {
// this.hashTable[i] = -1;
// }
} }
/// <summary> /// <summary>
@ -316,23 +319,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
hsizeReg = this.hsize; hsizeReg = this.hsize;
this.ResetCodeTable(hsizeReg); // clear hash table this.ResetCodeTable(); // clear hash table
this.Output(this.clearCode, stream); this.Output(this.clearCode, stream);
Span<int> hashTableSpan = this.hashTable.Span;
Span<int> codeTableSpan = this.codeTable.Span;
while ((c = this.NextPixel()) != Eof) while ((c = this.NextPixel()) != Eof)
{ {
fcode = (c << this.maxbits) + ent; fcode = (c << this.maxbits) + ent;
int i = (c << hshift) ^ ent /* = 0 */; int i = (c << hshift) ^ ent /* = 0 */;
if (this.hashTable[i] == fcode) if (hashTableSpan[i] == fcode)
{ {
ent = this.codeTable[i]; ent = codeTableSpan[i];
continue; continue;
} }
// Non-empty slot // Non-empty slot
if (this.hashTable[i] >= 0) if (hashTableSpan[i] >= 0)
{ {
int disp = hsizeReg - i; int disp = hsizeReg - i;
if (i == 0) if (i == 0)
@ -347,15 +353,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
i += hsizeReg; i += hsizeReg;
} }
if (this.hashTable[i] == fcode) if (hashTableSpan[i] == fcode)
{ {
ent = this.codeTable[i]; ent = codeTableSpan[i];
break; break;
} }
} }
while (this.hashTable[i] >= 0); while (hashTableSpan[i] >= 0);
if (this.hashTable[i] == fcode) if (hashTableSpan[i] == fcode)
{ {
continue; continue;
} }
@ -365,8 +371,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
ent = c; ent = c;
if (this.freeEntry < this.maxmaxcode) if (this.freeEntry < this.maxmaxcode)
{ {
this.codeTable[i] = this.freeEntry++; // code -> hashtable codeTableSpan[i] = this.freeEntry++; // code -> hashtable
this.hashTable[i] = fcode; hashTableSpan[i] = fcode;
} }
else else
{ {
@ -483,8 +489,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (disposing) if (disposing)
{ {
ArrayPool<int>.Shared.Return(this.hashTable); this.hashTable?.Dispose();
ArrayPool<int>.Shared.Return(this.codeTable); this.codeTable?.Dispose();
} }
this.isDisposed = true; this.isDisposed = true;

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

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Represents a byte of data in a GIF data stream which contains a number /// Represents a byte of data in a GIF data stream which contains a number
/// of data items. /// of data items.
/// </summary> /// </summary>
internal struct PackedField : IEquatable<PackedField> internal readonly struct PackedField : IEquatable<PackedField>
{ {
/// <summary> /// <summary>
/// The individual bits representing the packed byte. /// The individual bits representing the packed byte.

186
src/ImageSharp/Formats/ImageFormatManager.cs

@ -0,0 +1,186 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Collection of Image Formats to be used in <see cref="Configuration" /> class.
/// </summary>
public class ImageFormatManager
{
/// <summary>
/// The list of supported <see cref="IImageEncoder"/> keyed to mime types.
/// </summary>
private readonly ConcurrentDictionary<IImageFormat, IImageEncoder> mimeTypeEncoders = new ConcurrentDictionary<IImageFormat, IImageEncoder>();
/// <summary>
/// The list of supported <see cref="IImageEncoder"/> keyed to mime types.
/// </summary>
private readonly ConcurrentDictionary<IImageFormat, IImageDecoder> mimeTypeDecoders = new ConcurrentDictionary<IImageFormat, IImageDecoder>();
/// <summary>
/// The list of supported <see cref="IImageFormat"/>s.
/// </summary>
private readonly ConcurrentBag<IImageFormat> imageFormats = new ConcurrentBag<IImageFormat>();
/// <summary>
/// The list of supported <see cref="IImageFormatDetector"/>s.
/// </summary>
private ConcurrentBag<IImageFormatDetector> imageFormatDetectors = new ConcurrentBag<IImageFormatDetector>();
/// <summary>
/// Initializes a new instance of the <see cref="ImageFormatManager" /> class.
/// </summary>
public ImageFormatManager()
{
}
/// <summary>
/// Gets the maximum header size of all the formats.
/// </summary>
internal int MaxHeaderSize { get; private set; }
/// <summary>
/// Gets the currently registered <see cref="IImageFormat"/>s.
/// </summary>
public IEnumerable<IImageFormat> ImageFormats => this.imageFormats;
/// <summary>
/// Gets the currently registered <see cref="IImageFormatDetector"/>s.
/// </summary>
internal IEnumerable<IImageFormatDetector> FormatDetectors => this.imageFormatDetectors;
/// <summary>
/// Gets the currently registered <see cref="IImageDecoder"/>s.
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, IImageDecoder>> ImageDecoders => this.mimeTypeDecoders;
/// <summary>
/// Gets the currently registered <see cref="IImageEncoder"/>s.
/// </summary>
internal IEnumerable<KeyValuePair<IImageFormat, IImageEncoder>> ImageEncoders => this.mimeTypeEncoders;
/// <summary>
/// Registers a new format provider.
/// </summary>
/// <param name="format">The format to register as a known format.</param>
public void AddImageFormat(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes));
Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions));
this.imageFormats.Add(format);
}
/// <summary>
/// For the specified file extensions type find the e <see cref="IImageFormat"/>.
/// </summary>
/// <param name="extension">The extension to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found otherwise null</returns>
public IImageFormat FindFormatByFileExtension(string extension)
{
return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// For the specified mime type find the <see cref="IImageFormat"/>.
/// </summary>
/// <param name="mimeType">The mime-type to discover</param>
/// <returns>The <see cref="IImageFormat"/> if found; otherwise null</returns>
public IImageFormat FindFormatByMimeType(string mimeType)
{
return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase));
}
/// <summary>
/// Sets a specific image encoder as the encoder for a specific image format.
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="encoder">The encoder to use,</param>
public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(encoder, nameof(encoder));
this.AddImageFormat(imageFormat);
this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder);
}
/// <summary>
/// Sets a specific image decoder as the decoder for a specific image format.
/// </summary>
/// <param name="imageFormat">The image format to register the encoder for.</param>
/// <param name="decoder">The decoder to use,</param>
public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder)
{
Guard.NotNull(imageFormat, nameof(imageFormat));
Guard.NotNull(decoder, nameof(decoder));
this.AddImageFormat(imageFormat);
this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder);
}
/// <summary>
/// Removes all the registered image format detectors.
/// </summary>
public void ClearImageFormatDetectors()
{
this.imageFormatDetectors = new ConcurrentBag<IImageFormatDetector>();
}
/// <summary>
/// Adds a new detector for detecting mime types.
/// </summary>
/// <param name="detector">The detector to add</param>
public void AddImageFormatDetector(IImageFormatDetector detector)
{
Guard.NotNull(detector, nameof(detector));
this.imageFormatDetectors.Add(detector);
this.SetMaxHeaderSize();
}
/// <summary>
/// For the specified mime type find the decoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageDecoder"/> if found otherwise null</returns>
public IImageDecoder FindDecoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder))
{
return decoder;
}
return null;
}
/// <summary>
/// For the specified mime type find the encoder.
/// </summary>
/// <param name="format">The format to discover</param>
/// <returns>The <see cref="IImageEncoder"/> if found otherwise null</returns>
public IImageEncoder FindEncoder(IImageFormat format)
{
Guard.NotNull(format, nameof(format));
if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder))
{
return encoder;
}
return null;
}
/// <summary>
/// Sets the max header size.
/// </summary>
private void SetMaxHeaderSize()
{
this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize);
}
}
}

5
src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common namespace SixLabors.ImageSharp.Formats.Jpeg.Common
@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
public Block8x8(Span<short> coefficients) public Block8x8(Span<short> coefficients)
{ {
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this); ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this);
ref byte sourceRef = ref coefficients.NonPortableCast<short, byte>().DangerousGetPinnableReference(); ref byte sourceRef = ref MemoryMarshal.GetReference(coefficients.NonPortableCast<short, byte>());
Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short));
} }
@ -204,7 +205,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
public void CopyTo(Span<short> destination) public void CopyTo(Span<short> destination)
{ {
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this); ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this);
ref byte destRef = ref destination.NonPortableCast<short, byte>().DangerousGetPinnableReference(); ref byte destRef = ref MemoryMarshal.GetReference(destination.NonPortableCast<short, byte>());
Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short));
} }

11
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs

@ -26,6 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
return; return;
} }
ref float destBase = ref area.GetReferenceToOrigin();
// TODO: Optimize: implement all the cases with loopless special code! (T4?) // TODO: Optimize: implement all the cases with loopless special code! (T4?)
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)
{ {
@ -40,9 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
for (int i = 0; i < verticalScale; i++) for (int i = 0; i < verticalScale; i++)
{ {
int baseIdx = ((yy + i) * area.Stride) + xx;
for (int j = 0; j < horizontalScale; j++) for (int j = 0; j < horizontalScale; j++)
{ {
area[xx + j, yy + i] = value; // area[xx + j, yy + i] = value;
Unsafe.Add(ref destBase, baseIdx + j) = value;
} }
} }
} }
@ -53,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
public void CopyTo(BufferArea<float> area) public void CopyTo(BufferArea<float> area)
{ {
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this); ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref area.GetReferenceToOrigo()); ref byte destBase = ref Unsafe.As<float, byte>(ref area.GetReferenceToOrigin());
int destStride = area.Stride * sizeof(float); int destStride = area.Stride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0); CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
@ -76,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
private void CopyTo2x2(BufferArea<float> area) private void CopyTo2x2(BufferArea<float> area)
{ {
ref float destBase = ref area.GetReferenceToOrigo(); ref float destBase = ref area.GetReferenceToOrigin();
int destStride = area.Stride; int destStride = area.Stride;
this.WidenCopyImpl2x2(ref destBase, 0, destStride); this.WidenCopyImpl2x2(ref destBase, 0, destStride);

4
src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs

@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LoadFrom(Span<float> source) public void LoadFrom(Span<float> source)
{ {
ref byte s = ref Unsafe.As<float, byte>(ref source.DangerousGetPinnableReference()); ref byte s = ref Unsafe.As<float, byte>(ref MemoryMarshal.GetReference(source));
ref byte d = ref Unsafe.As<Block8x8F, byte>(ref this); ref byte d = ref Unsafe.As<Block8x8F, byte>(ref this);
Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float));
@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CopyTo(Span<float> dest) public void CopyTo(Span<float> dest)
{ {
ref byte d = ref Unsafe.As<float, byte>(ref dest.DangerousGetPinnableReference()); ref byte d = ref Unsafe.As<float, byte>(ref MemoryMarshal.GetReference(dest));
ref byte s = ref Unsafe.As<Block8x8F, byte>(ref this); ref byte s = ref Unsafe.As<Block8x8F, byte>(ref this);
Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float));

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// Provides information about the Adobe marker segment. /// Provides information about the Adobe marker segment.
/// </summary> /// </summary>
/// <remarks>See the included 5116.DCT.pdf file in the source for more information.</remarks> /// <remarks>See the included 5116.DCT.pdf file in the source for more information.</remarks>
internal struct AdobeMarker : IEquatable<AdobeMarker> internal readonly struct AdobeMarker : IEquatable<AdobeMarker>
{ {
/// <summary> /// <summary>
/// Gets the length of an adobe marker segment. /// Gets the length of an adobe marker segment.

9
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Tuples; using SixLabors.ImageSharp.Common.Tuples;
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
@ -37,14 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!"); DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!");
ref Vector4Pair yBase = ref Vector4Pair yBase =
ref Unsafe.As<float, Vector4Pair>(ref values.Component0.DangerousGetPinnableReference()); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector4Pair cbBase = ref Vector4Pair cbBase =
ref Unsafe.As<float, Vector4Pair>(ref values.Component1.DangerousGetPinnableReference()); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector4Pair crBase = ref Vector4Pair crBase =
ref Unsafe.As<float, Vector4Pair>(ref values.Component2.DangerousGetPinnableReference()); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase = ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref result.DangerousGetPinnableReference()); ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector4(-128f); var chromaOffset = new Vector4(-128f);

9
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Tuples; using SixLabors.ImageSharp.Common.Tuples;
// ReSharper disable ImpureMethodCallOnReadonlyValueField // ReSharper disable ImpureMethodCallOnReadonlyValueField
@ -46,14 +47,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
} }
ref Vector<float> yBase = ref Vector<float> yBase =
ref Unsafe.As<float, Vector<float>>(ref values.Component0.DangerousGetPinnableReference()); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase = ref Vector<float> cbBase =
ref Unsafe.As<float, Vector<float>>(ref values.Component1.DangerousGetPinnableReference()); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase = ref Vector<float> crBase =
ref Unsafe.As<float, Vector<float>>(ref values.Component2.DangerousGetPinnableReference()); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase = ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref result.DangerousGetPinnableReference()); ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-128f); var chromaOffset = new Vector<float>(-128f);

4
src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs

@ -66,7 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters
/// <summary> /// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s. /// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.
/// </summary> /// </summary>
public struct ComponentValues #pragma warning disable SA1206 // Declaration keywords should follow order
public readonly ref struct ComponentValues
#pragma warning restore SA1206 // Declaration keywords should follow order
{ {
/// <summary> /// <summary>
/// The component count /// The component count

2
src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// Provides information about the JFIF marker segment /// Provides information about the JFIF marker segment
/// TODO: Thumbnail? /// TODO: Thumbnail?
/// </summary> /// </summary>
internal struct JFifMarker : IEquatable<JFifMarker> internal readonly struct JFifMarker : IEquatable<JFifMarker>
{ {
/// <summary> /// <summary>
/// Gets the length of an JFIF marker segment. /// Gets the length of an JFIF marker segment.

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

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

8
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs

@ -22,11 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class. /// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary> /// </summary>
public JpegComponentPostProcessor(JpegImagePostProcessor imagePostProcessor, IJpegComponent component) public JpegComponentPostProcessor(MemoryManager memoryManager, JpegImagePostProcessor imagePostProcessor, IJpegComponent component)
{ {
this.Component = component; this.Component = component;
this.ImagePostProcessor = imagePostProcessor; this.ImagePostProcessor = imagePostProcessor;
this.ColorBuffer = new Buffer2D<float>(imagePostProcessor.PostProcessorBufferSize); this.ColorBuffer = memoryManager.Allocate2D<float>(
imagePostProcessor.PostProcessorBufferSize.Width,
imagePostProcessor.PostProcessorBufferSize.Height);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8; this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
public IJpegComponent Component { get; } public IJpegComponent Component { get; }
/// <summary> /// <summary>
/// Gets the temporal working buffer of color values. /// Gets the temporary working buffer of color values.
/// </summary> /// </summary>
public Buffer2D<float> ColorBuffer { get; } public Buffer2D<float> ColorBuffer { get; }

15
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary> /// <summary>
/// Temporal buffer to store a row of colors. /// Temporal buffer to store a row of colors.
/// </summary> /// </summary>
private readonly Buffer<Vector4> rgbaBuffer; private readonly IBuffer<Vector4> rgbaBuffer;
/// <summary> /// <summary>
/// The <see cref="ColorConverters.JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>. /// The <see cref="ColorConverters.JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
@ -44,16 +44,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class. /// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
/// </summary> /// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param> /// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
public JpegImagePostProcessor(IRawJpegData rawJpeg) public JpegImagePostProcessor(MemoryManager memoryManager, IRawJpegData rawJpeg)
{ {
this.RawJpeg = rawJpeg; this.RawJpeg = rawJpeg;
IJpegComponent c0 = rawJpeg.Components.First(); IJpegComponent c0 = rawJpeg.Components.First();
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);
this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryManager, this, c)).ToArray();
this.rgbaBuffer = new Buffer<Vector4>(rawJpeg.ImageSizeInPixels.Width); this.rgbaBuffer = memoryManager.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = ColorConverters.JpegColorConverter.GetConverter(rawJpeg.ColorSpace); this.colorConverter = ColorConverters.JpegColorConverter.GetConverter(rawJpeg.ColorSpace);
} }
@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
public int NumberOfPostProcessorSteps { get; } public int NumberOfPostProcessorSteps { get; }
/// <summary> /// <summary>
/// Gets the size of the temporal buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>. /// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// </summary> /// </summary>
public Size PostProcessorBufferSize { get; } public Size PostProcessorBufferSize { get; }
@ -149,11 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
int y = yy - this.PixelRowCounter; int y = yy - this.PixelRowCounter;
var values = new ColorConverters.JpegColorConverter.ComponentValues(buffers, y); var values = new ColorConverters.JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer); this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer.Span);
Span<TPixel> destRow = destination.GetPixelRowSpan(yy); Span<TPixel> destRow = destination.GetPixelRowSpan(yy);
PixelOperations<TPixel>.Instance.PackFromVector4(this.rgbaBuffer, destRow, destination.Width); PixelOperations<TPixel>.Instance.PackFromVector4(this.rgbaBuffer.Span, destRow, destination.Width);
} }
} }
} }

26
src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal unsafe partial struct GenericBlock8x8<T>
{
#pragma warning disable 169
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7;
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7;
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7;
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7;
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7;
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7;
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7;
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7;
#pragma warning restore 169
}
}

43
src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt

@ -0,0 +1,43 @@
<#
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
#>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
// <auto-generated />
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
internal unsafe partial struct GenericBlock8x8<T>
{
#pragma warning disable 169
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
<#
PushIndent(" ");
Write(" ");
for (int y = 0; y < 8; y++)
{
Write("private T ");
for (int x = 0; x < 8; x++)
{
Write($"_y{y}_x{x}");
if (x < 7) Write(", ");
}
WriteLine(";");
}
PopIndent();
#>
#pragma warning restore 169
}
}

129
src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs

@ -0,0 +1,129 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Common
{
/// <summary>
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe partial struct GenericBlock8x8<T>
where T : struct
{
public const int Size = 64;
public const int SizeInBytes = Size * 3;
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a <see cref="Rgb24"/> value at the given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The value</returns>
public T this[int idx]
{
get
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
return Unsafe.Add(ref selfRef, idx);
}
set
{
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this);
Unsafe.Add(ref selfRef, idx) = value;
}
}
/// <summary>
/// FOR TESTING ONLY!
/// Gets or sets a value in a row+coulumn of the 8x8 block
/// </summary>
/// <param name="x">The x position index in the row</param>
/// <param name="y">The column index</param>
/// <returns>The value</returns>
public T this[int x, int y]
{
get => this[(y * 8) + x];
set => this[(y * 8) + x] = value;
}
public void LoadAndStretchEdges<TPixel>(IPixelSource<TPixel> source, int sourceX, int sourceY)
where TPixel : struct, IPixel<TPixel>
{
var buffer = source.PixelBuffer as Buffer2D<T>;
if (buffer == null)
{
throw new InvalidOperationException("LoadAndStretchEdges<TPixels>() is only valid for TPixel == T !");
}
this.LoadAndStretchEdges(buffer, sourceX, sourceY);
}
/// <summary>
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
if (width <= 0 || height <= 0)
{
return;
}
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>();
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this);
ref byte imageStart = ref Unsafe.As<T, byte>(
ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX));
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>();
int imageRowSizeInBytes = source.Width * Unsafe.SizeOf<T>();
for (int y = 0; y < height; y++)
{
ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes);
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1);
for (int x = 1; x <= remainderXCount; x++)
{
Unsafe.Add(ref last, x) = last;
}
}
int remainderYCount = 8 - height;
if (remainderYCount == 0)
{
return;
}
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes);
for (int y = 1; y <= remainderYCount; y++)
{
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y);
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes);
}
}
/// <summary>
/// Only for on-stack instances!
/// </summary>
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size);
}
}

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// Bytes is a byte buffer, similar to a stream, except that it /// 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. /// has to be able to unread more than 1 byte, due to byte stuffing.
/// Byte stuffing is specified in section F.1.2.3. /// Byte stuffing is specified in section F.1.2.3.
/// TODO: Optimize buffer management inside this class!
/// </summary> /// </summary>
internal struct Bytes : IDisposable internal struct Bytes : IDisposable
{ {
@ -48,20 +48,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
public int UnreadableBytes; public int UnreadableBytes;
private static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(BufferSize, 50);
private static readonly ArrayPool<int> IntPool = ArrayPool<int>.Create(BufferSize, 50);
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer. /// Creates a new instance of the <see cref="Bytes"/>, and initializes it's buffer.
/// </summary> /// </summary>
/// <returns>The bytes created</returns> /// <returns>The bytes created</returns>
public static Bytes Create() public static Bytes Create()
{ {
// DO NOT bother with buffers and array pooling here!
// It only makes things worse!
return new Bytes return new Bytes
{ {
Buffer = BytePool.Rent(BufferSize), Buffer = new byte[BufferSize],
BufferAsInt = IntPool.Rent(BufferSize) BufferAsInt = new int[BufferSize]
}; };
} }
@ -70,12 +68,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
if (this.Buffer != null)
{
BytePool.Return(this.Buffer);
IntPool.Return(this.BufferAsInt);
}
this.Buffer = null; this.Buffer = null;
this.BufferAsInt = null; this.BufferAsInt = null;
} }

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

@ -18,6 +18,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowExceptionForErrorCode(this OrigDecoderErrorCode errorCode) public static void ThrowExceptionForErrorCode(this OrigDecoderErrorCode 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) switch (errorCode)
{ {
case OrigDecoderErrorCode.NoError: case OrigDecoderErrorCode.NoError:

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

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
public Stream InputStream { get; } public Stream InputStream { get; }
/// <summary> /// <summary>
/// Gets the temporal buffer, same instance as <see cref="OrigJpegDecoderCore.Temp"/> /// Gets the temporary buffer, same instance as <see cref="OrigJpegDecoderCore.Temp"/>
/// </summary> /// </summary>
public byte[] Temp { get; } public byte[] Temp { get; }
@ -105,6 +105,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
return this.Bytes.ReadByte(this.InputStream); return this.Bytes.ReadByte(this.InputStream);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public OrigDecoderErrorCode ReadByteUnsafe(out byte result)
{
this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result);
return this.LastErrorCode;
}
/// <summary> /// <summary>
/// Decodes a single bit /// Decodes a single bit
/// TODO: This method (and also the usages) could be optimized by batching! /// TODO: This method (and also the usages) could be optimized by batching!
@ -373,5 +380,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x); this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x);
return this.LastErrorCode; return this.LastErrorCode;
} }
/// <summary>
/// Reset the Huffman decoder.
/// </summary>
public void ResetHuffmanDecoder()
{
this.Bits = default(Bits);
}
} }
} }

5
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs

@ -54,8 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <summary> /// <summary>
/// Initializes <see cref="SpectralBlocks"/> /// Initializes <see cref="SpectralBlocks"/>
/// </summary> /// </summary>
/// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param>
/// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param> /// <param name="decoder">The <see cref="OrigJpegDecoderCore"/> instance</param>
public void InitializeDerivedData(OrigJpegDecoderCore decoder) public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder)
{ {
// For 4-component images (either CMYK or YCbCrK), we only support two // For 4-component images (either CMYK or YCbCrK), we only support two
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
} }
this.SpectralBlocks = Buffer2D<Block8x8>.CreateClean(this.SizeInBlocks); this.SpectralBlocks = memoryManager.Allocate2D<Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true);
} }
/// <summary> /// <summary>

192
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs

@ -3,13 +3,16 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
/// <summary> /// <summary>
/// Represents a Huffman tree /// Represents a Huffman tree
/// </summary> /// </summary>
internal struct OrigHuffmanTree : IDisposable [StructLayout(LayoutKind.Sequential)]
internal unsafe struct OrigHuffmanTree
{ {
/// <summary> /// <summary>
/// The index of the AC table row /// The index of the AC table row
@ -67,35 +70,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// are 1 plus the code length, or 0 if the value is too large to fit in /// are 1 plus the code length, or 0 if the value is too large to fit in
/// lutSize bits. /// lutSize bits.
/// </summary> /// </summary>
public int[] Lut; public FixedInt32Buffer256 Lut;
/// <summary> /// <summary>
/// Gets the the decoded values, sorted by their encoding. /// Gets the the decoded values, sorted by their encoding.
/// </summary> /// </summary>
public int[] Values; public FixedInt32Buffer256 Values;
/// <summary> /// <summary>
/// Gets the array of minimum codes. /// 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. /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length.
/// </summary> /// </summary>
public int[] MinCodes; public FixedInt32Buffer16 MinCodes;
/// <summary> /// <summary>
/// Gets the array of maximum codes. /// 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. /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length.
/// </summary> /// </summary>
public int[] MaxCodes; public FixedInt32Buffer16 MaxCodes;
/// <summary> /// <summary>
/// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i].
/// </summary> /// </summary>
public int[] Indices; public FixedInt32Buffer16 Indices;
private static readonly ArrayPool<int> IntPool256 = ArrayPool<int>.Create(MaxNCodes, 50);
private static readonly ArrayPool<byte> BytePool256 = ArrayPool<byte>.Create(MaxNCodes, 50);
private static readonly ArrayPool<int> CodesPool16 = ArrayPool<int>.Create(MaxCodeLength, 50);
/// <summary> /// <summary>
/// Creates and initializes an array of <see cref="OrigHuffmanTree" /> instances of size <see cref="NumberOfTrees" /> /// Creates and initializes an array of <see cref="OrigHuffmanTree" /> instances of size <see cref="NumberOfTrees" />
@ -103,35 +100,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <returns>An array of <see cref="OrigHuffmanTree" /> instances representing the Huffman tables</returns> /// <returns>An array of <see cref="OrigHuffmanTree" /> instances representing the Huffman tables</returns>
public static OrigHuffmanTree[] CreateHuffmanTrees() public static OrigHuffmanTree[] CreateHuffmanTrees()
{ {
OrigHuffmanTree[] result = new OrigHuffmanTree[NumberOfTrees]; return new OrigHuffmanTree[NumberOfTrees];
for (int i = 0; i < MaxTc + 1; i++)
{
for (int j = 0; j < MaxTh + 1; j++)
{
result[(i * ThRowSize) + j].Init();
}
}
return result;
}
/// <summary>
/// Disposes the underlying buffers
/// </summary>
public void Dispose()
{
IntPool256.Return(this.Lut, true);
IntPool256.Return(this.Values, true);
CodesPool16.Return(this.MinCodes, true);
CodesPool16.Return(this.MaxCodes, true);
CodesPool16.Return(this.Indices, true);
} }
/// <summary> /// <summary>
/// Internal part of the DHT processor, whatever does it mean /// Internal part of the DHT processor, whatever does it mean
/// </summary> /// </summary>
/// <param name="inputProcessor">The decoder instance</param> /// <param name="inputProcessor">The decoder instance</param>
/// <param name="defineHuffmanTablesData">The temporal buffer that holds the data that has been read from the Jpeg stream</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> /// <param name="remaining">Remaining bits</param>
public void ProcessDefineHuffmanTablesMarkerLoop( public void ProcessDefineHuffmanTablesMarkerLoop(
ref InputProcessor inputProcessor, ref InputProcessor inputProcessor,
@ -166,75 +142,76 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
throw new ImageFormatException("DHT has wrong length"); throw new ImageFormatException("DHT has wrong length");
} }
byte[] values = null; byte[] values = new byte[MaxNCodes];
try inputProcessor.ReadFull(values, 0, this.Length);
{
values = BytePool256.Rent(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++) for (int i = 0; i < values.Length; i++)
{ {
this.Values[i] = values[i]; valuesPtr[i] = values[i];
} }
}
finally
{
BytePool256.Return(values, true);
}
// Derive the look-up table.
for (int i = 0; i < this.Lut.Length; i++)
{
this.Lut[i] = 0;
}
int x = 0, code = 0; // Derive the look-up table.
for (int i = 0; i < MaxNCodes; i++)
{
lutPtr[i] = 0;
}
for (int i = 0; i < LutSizeLog2; i++) int x = 0, code = 0;
{
code <<= 1;
for (int j = 0; j < ncodes[i]; j++) for (int i = 0; i < LutSizeLog2; i++)
{ {
// The codeLength is 1+i, so shift code by 8-(1+i) to code <<= 1;
// calculate the high bits for every 8-bit sequence
// whose codeLength's high bits matches code. for (int j = 0; j < ncodes[i]; j++)
// 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 = (this.Values[x] << 8) | (2 + i);
for (int k = 0; k < 1 << (7 - i); k++)
{ {
this.Lut[base2 | k] = lutValue; // 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++;
} }
code++;
x++;
} }
} }
// Derive minCodes, maxCodes, and indices. fixed (int* minCodesPtr = this.MinCodes.Data)
int c = 0, index = 0; fixed (int* maxCodesPtr = this.MaxCodes.Data)
for (int i = 0; i < ncodes.Length; i++) fixed (int* indicesPtr = this.Indices.Data)
{ {
int nc = ncodes[i]; // Derive minCodes, maxCodes, and indices.
if (nc == 0) int c = 0, index = 0;
{ for (int i = 0; i < ncodes.Length; i++)
this.MinCodes[i] = -1;
this.MaxCodes[i] = -1;
this.Indices[i] = -1;
}
else
{ {
this.MinCodes[i] = c; int nc = ncodes[i];
this.MaxCodes[i] = c + nc - 1; if (nc == 0)
this.Indices[i] = index; {
c += nc; minCodesPtr[i] = -1;
index += nc; maxCodesPtr[i] = -1;
} indicesPtr[i] = -1;
}
else
{
minCodesPtr[i] = c;
maxCodesPtr[i] = c + nc - 1;
indicesPtr[i] = index;
c += nc;
index += nc;
}
c <<= 1; c <<= 1;
}
} }
} }
@ -244,21 +221,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// <param name="code">The code</param> /// <param name="code">The code</param>
/// <param name="codeLength">The code length</param> /// <param name="codeLength">The code length</param>
/// <returns>The value</returns> /// <returns>The value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetValue(int code, int codeLength) public int GetValue(int code, int codeLength)
{ {
return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]];
} }
/// <summary> [StructLayout(LayoutKind.Sequential)]
/// Initializes the Huffman tree internal struct FixedInt32Buffer256
/// </summary>
private void Init()
{ {
this.Lut = IntPool256.Rent(MaxNCodes); public fixed int Data[256];
this.Values = IntPool256.Rent(MaxNCodes);
this.MinCodes = CodesPool16.Rent(MaxCodeLength); public int this[int idx]
this.MaxCodes = CodesPool16.Rent(MaxCodeLength); {
this.Indices = CodesPool16.Rent(MaxCodeLength); [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);
}
}
} }
} }
} }

181
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs

@ -94,6 +94,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
/// </summary> /// </summary>
private int eobRun; 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> /// <summary>
/// Initializes a default-constructed <see cref="OrigJpegScanDecoder"/> instance for reading data from <see cref="OrigJpegDecoderCore"/>-s stream. /// Initializes a default-constructed <see cref="OrigJpegScanDecoder"/> instance for reading data from <see cref="OrigJpegDecoderCore"/>-s stream.
/// </summary> /// </summary>
@ -139,100 +154,136 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder
{ {
decoder.InputProcessor.ResetErrorState(); decoder.InputProcessor.ResetErrorState();
int blockCount = 0; this.blockCounter = 0;
int mcu = 0; this.mcuCounter = 0;
byte expectedRst = OrigJpegConstants.Markers.RST0; this.expectedRst = OrigJpegConstants.Markers.RST0;
for (int my = 0; my < decoder.MCUCountY; my++) for (int my = 0; my < decoder.MCUCountY; my++)
{ {
for (int mx = 0; mx < decoder.MCUCountX; mx++) for (int mx = 0; mx < decoder.MCUCountX; mx++)
{ {
for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) this.DecodeBlocksAtMcuIndex(decoder, mx, my);
{
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
OrigComponent component = decoder.Components[this.ComponentIndex];
this.hi = component.HorizontalSamplingFactor; this.mcuCounter++;
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 = blockCount % q;
this.by = blockCount / q;
blockCount++;
if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight)
{
continue;
}
}
// Find the block at (bx,by) in the component's buffer: // Handling restart intervals
ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by); // Useful info: https://stackoverflow.com/a/8751802
if (decoder.IsAtRestartInterval(this.mcuCounter))
{
this.ProcessRSTMarker(decoder);
this.Reset(decoder);
}
}
}
}
// Copy block to stack private void DecodeBlocksAtMcuIndex(OrigJpegDecoderCore decoder, int mx, int my)
this.data.Block = blockRefOnHeap; {
for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++)
{
this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex;
OrigComponent component = decoder.Components[this.ComponentIndex];
if (!decoder.InputProcessor.ReachedEOF) this.hi = component.HorizontalSamplingFactor;
{ int vi = component.VerticalSamplingFactor;
this.DecodeBlock(decoder, scanIndex);
}
// Store the result block: for (int j = 0; j < this.hi * vi; j++)
blockRefOnHeap = this.data.Block; {
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);
// for j // Copy block to stack
this.data.Block = blockRefOnHeap;
if (!decoder.InputProcessor.ReachedEOF)
{
this.DecodeBlock(decoder, scanIndex);
} }
// for i // Store the result block:
mcu++; blockRefOnHeap = this.data.Block;
}
}
}
if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < decoder.TotalMCUCount) private void ProcessRSTMarker(OrigJpegDecoderCore 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)
{ {
// A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, bool invalidRst = true;
// but this one assumes well-formed input, and hence the restart marker follows immediately.
if (!decoder.InputProcessor.ReachedEOF) // 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)
{ {
decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); while (decoder.Temp[0] == 0xFF && decoder.InputProcessor.CheckEOFEnsureNoError())
if (decoder.InputProcessor.CheckEOFEnsureNoError())
{ {
if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 1);
{ if (!decoder.InputProcessor.CheckEOFEnsureNoError())
throw new ImageFormatException("Bad RST marker");
}
expectedRst++;
if (expectedRst == OrigJpegConstants.Markers.RST7 + 1)
{ {
expectedRst = OrigJpegConstants.Markers.RST0; break;
} }
} }
}
// Reset the Huffman decoder. // Have we found a valid restart marker?
decoder.InputProcessor.Bits = default(Bits); invalidRst = decoder.Temp[0] != this.expectedRst;
}
// Reset the DC components, as per section F.2.1.3.1. if (invalidRst)
this.ResetDc(); {
throw new ImageFormatException("Bad RST marker");
}
}
// Reset the progressive decoder state, as per section G.1.2.2. this.expectedRst++;
this.eobRun = 0; if (this.expectedRst == OrigJpegConstants.Markers.RST7 + 1)
{
this.expectedRst = OrigJpegConstants.Markers.RST0;
} }
} }
// for mx
} }
} }
private void ResetDc() private void Reset(OrigJpegDecoderCore 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(byte), sizeof(int) * OrigJpegDecoderCore.MaxComponents); Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * OrigJpegDecoderCore.MaxComponents);
} }

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
/// codeword size in bits and the 24 least significant bits hold the codeword. /// codeword size in bits and the 24 least significant bits hold the codeword.
/// The maximum codeword size is 16 bits. /// The maximum codeword size is 16 bits.
/// </summary> /// </summary>
internal struct HuffmanLut internal readonly struct HuffmanLut
{ {
/// <summary> /// <summary>
/// The compiled representations of theHuffmanSpec. /// The compiled representations of theHuffmanSpec.

2
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
/// <summary> /// <summary>
/// The Huffman encoding specifications. /// The Huffman encoding specifications.
/// </summary> /// </summary>
internal struct HuffmanSpec internal readonly struct HuffmanSpec
{ {
#pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines #pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines

34
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs

@ -3,11 +3,14 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
{ {
/// <summary> /// <summary>
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
/// Methods to build the tables are based on libjpeg implementation. /// Methods to build the tables are based on libjpeg implementation.
/// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)!
/// </summary> /// </summary>
internal unsafe struct RgbToYCbCrTables internal unsafe struct RgbToYCbCrTables
{ {
@ -91,27 +94,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
} }
/// <summary> /// <summary>
/// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)!
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary> /// </summary>
/// <param name="yBlockRaw">The The luminance block.</param>
/// <param name="cbBlockRaw">The red chroma block.</param>
/// <param name="crBlockRaw">The blue chroma block.</param>
/// <param name="tables">The reference to the tables instance.</param>
/// <param name="index">The current index.</param>
/// <param name="r">The red value.</param>
/// <param name="g">The green value.</param>
/// <param name="b">The blue value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, ref RgbToYCbCrTables* tables, int index, int r, int g, int b) public void ConvertPixelInto(int r, int g, int b, ref float yResult, ref float cbResult, ref float crResult)
{ {
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b); ref int start = ref Unsafe.As<RgbToYCbCrTables, int>(ref this);
yBlockRaw[index] = (tables->YRTable[r] + tables->YGTable[g] + tables->YBTable[b]) >> ScaleBits;
ref int yR = ref start;
ref int yG = ref Unsafe.Add(ref start, 256 * 1);
ref int yB = ref Unsafe.Add(ref start, 256 * 2);
ref int cbR = ref Unsafe.Add(ref start, 256 * 3);
ref int cbG = ref Unsafe.Add(ref start, 256 * 4);
ref int cbB = ref Unsafe.Add(ref start, 256 * 5);
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); ref int crG = ref Unsafe.Add(ref start, 256 * 6);
cbBlockRaw[index] = (tables->CbRTable[r] + tables->CbGTable[g] + tables->CbBTable[b]) >> ScaleBits; ref int crB = ref Unsafe.Add(ref start, 256 * 7);
// float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); yResult = (Unsafe.Add(ref yR, r) + Unsafe.Add(ref yG, g) + Unsafe.Add(ref yB, b)) >> ScaleBits;
crBlockRaw[index] = (tables->CbBTable[r] + tables->CrGTable[g] + tables->CrBTable[b]) >> ScaleBits; cbResult = (Unsafe.Add(ref cbR, r) + Unsafe.Add(ref cbG, g) + Unsafe.Add(ref cbB, b)) >> ScaleBits;
crResult = (Unsafe.Add(ref cbB, r) + Unsafe.Add(ref crG, g) + Unsafe.Add(ref crB, b)) >> ScaleBits;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

82
src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -0,0 +1,82 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal struct YCbCrForwardConverter<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrTables colorTables;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
/// <summary>
/// Temporal RGB block
/// </summary>
private GenericBlock8x8<Rgb24> rgbBlock;
public static YCbCrForwardConverter<TPixel> Create()
{
var result = default(YCbCrForwardConverter<TPixel>);
result.colorTables = RgbToYCbCrTables.Create();
return result;
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(IPixelSource<TPixel> pixels, int x, int y)
{
this.pixelBlock.LoadAndStretchEdges(pixels, x, y);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(this.pixelBlock.AsSpanUnsafe(), rgbSpan, 64);
ref float yBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Y);
ref float cbBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cb);
ref float crBlockStart = ref Unsafe.As<Block8x8F, float>(ref this.Cr);
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++)
{
ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i);
this.colorTables.ConvertPixelInto(
c.R,
c.G,
c.B,
ref Unsafe.Add(ref yBlockStart, i),
ref Unsafe.Add(ref cbBlockStart, i),
ref Unsafe.Add(ref crBlockStart, i));
}
}
}
}

227
src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.MetaData.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -231,10 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.WriteDefineHuffmanTables(componentCount); this.WriteDefineHuffmanTables(componentCount);
// Write the image data. // Write the image data.
using (PixelAccessor<TPixel> pixels = image.Lock()) this.WriteStartOfScan(image);
{
this.WriteStartOfScan(pixels);
}
// Write the End Of Image marker. // Write the End Of Image marker.
this.buffer[0] = OrigJpegConstants.Markers.XFF; this.buffer[0] = OrigJpegConstants.Markers.XFF;
@ -285,58 +282,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
} }
} }
/// <summary>
/// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor.</param>
/// <param name="tables">The reference to the tables instance.</param>
/// <param name="x">The x-position within the image.</param>
/// <param name="y">The y-position within the image.</param>
/// <param name="yBlock">The luminance block.</param>
/// <param name="cbBlock">The red chroma block.</param>
/// <param name="crBlock">The blue chroma block.</param>
/// <param name="rgbBytes">Temporal <see cref="PixelArea{TPixel}"/> provided by the caller</param>
private static void ToYCbCr<TPixel>(
PixelAccessor<TPixel> pixels,
RgbToYCbCrTables* tables,
int x,
int y,
Block8x8F* yBlock,
Block8x8F* cbBlock,
Block8x8F* crBlock,
PixelArea<TPixel> rgbBytes)
where TPixel : struct, IPixel<TPixel>
{
float* yBlockRaw = (float*)yBlock;
float* cbBlockRaw = (float*)cbBlock;
float* crBlockRaw = (float*)crBlock;
rgbBytes.Reset();
pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x);
ref byte data0 = ref rgbBytes.Bytes[0];
int dataIdx = 0;
for (int j = 0; j < 8; j++)
{
int j8 = j * 8;
for (int i = 0; i < 8; i++)
{
// Convert returned bytes into the YCbCr color space. Assume RGBA
int r = Unsafe.Add(ref data0, dataIdx);
int g = Unsafe.Add(ref data0, dataIdx + 1);
int b = Unsafe.Add(ref data0, dataIdx + 2);
int index = j8 + i;
RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, ref tables, index, r, g, b);
dataIdx += 3;
}
}
}
/// <summary> /// <summary>
/// Emits the least significant count of bits of bits to the bit-stream. /// Emits the least significant count of bits of bits to the bit-stream.
/// The precondition is bits /// The precondition is bits
@ -432,14 +377,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode444<TPixel>(PixelAccessor<TPixel> pixels) private void Encode444<TPixel>(Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Block8x8F b = default(Block8x8F); // (Partially done with YCbCrForwardConverter<TPixel>)
Block8x8F cb = default(Block8x8F);
Block8x8F cr = default(Block8x8F);
Block8x8F temp1 = default(Block8x8F); Block8x8F temp1 = default(Block8x8F);
Block8x8F temp2 = default(Block8x8F); Block8x8F temp2 = default(Block8x8F);
@ -451,42 +393,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables) YCbCrForwardConverter<TPixel> pixelConverter = YCbCrForwardConverter<TPixel>.Create();
for (int y = 0; y < pixels.Height; y += 8)
{ {
using (PixelArea<TPixel> rgbBytes = new PixelArea<TPixel>(8, 8, ComponentOrder.Xyz)) for (int x = 0; x < pixels.Width; x += 8)
{ {
for (int y = 0; y < pixels.Height; y += 8) pixelConverter.Convert(pixels.Frames.RootFrame, x, y);
{
for (int x = 0; x < pixels.Width; x += 8) prevDCY = this.WriteBlock(
{ QuantIndex.Luminance,
ToYCbCr(pixels, tables, x, y, &b, &cb, &cr, rgbBytes); prevDCY,
&pixelConverter.Y,
prevDCY = this.WriteBlock( &temp1,
QuantIndex.Luminance, &temp2,
prevDCY, &onStackLuminanceQuantTable,
&b, unzig.Data);
&temp1, prevDCCb = this.WriteBlock(
&temp2, QuantIndex.Chrominance,
&onStackLuminanceQuantTable, prevDCCb,
unzig.Data); &pixelConverter.Cb,
prevDCCb = this.WriteBlock( &temp1,
QuantIndex.Chrominance, &temp2,
prevDCCb, &onStackChrominanceQuantTable,
&cb, unzig.Data);
&temp1, prevDCCr = this.WriteBlock(
&temp2, QuantIndex.Chrominance,
&onStackChrominanceQuantTable, prevDCCr,
unzig.Data); &pixelConverter.Cr,
prevDCCr = this.WriteBlock( &temp1,
QuantIndex.Chrominance, &temp2,
prevDCCr, &onStackChrominanceQuantTable,
&cr, unzig.Data);
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
}
}
} }
} }
} }
@ -657,14 +595,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
// Loop through and collect the tables as one array. // Loop through and collect the tables as one array.
// This allows us to reduce the number of writes to the stream. // This allows us to reduce the number of writes to the stream.
int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount; int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount;
byte[] dqt = ArrayPool<byte>.Shared.Rent(dqtCount); byte[] dqt = new byte[dqtCount];
int offset = 0; int offset = 0;
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable);
this.outputStream.Write(dqt, 0, dqtCount); this.outputStream.Write(dqt, 0, dqtCount);
ArrayPool<byte>.Shared.Return(dqt);
} }
/// <summary> /// <summary>
@ -858,8 +795,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// Writes the StartOfScan marker. /// Writes the StartOfScan marker.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="image">The pixel accessor providing access to the image pixels.</param>
private void WriteStartOfScan<TPixel>(PixelAccessor<TPixel> pixels) private void WriteStartOfScan<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -869,10 +806,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
switch (this.subsample) switch (this.subsample)
{ {
case JpegSubsample.Ratio444: case JpegSubsample.Ratio444:
this.Encode444(pixels); this.Encode444(image);
break; break;
case JpegSubsample.Ratio420: case JpegSubsample.Ratio420:
this.Encode420(pixels); this.Encode420(image);
break; break;
} }
@ -886,7 +823,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode420<TPixel>(PixelAccessor<TPixel> pixels) private void Encode420<TPixel>(Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
@ -905,54 +842,54 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
ZigZag unzig = ZigZag.CreateUnzigTable(); ZigZag unzig = ZigZag.CreateUnzigTable();
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables)
for (int y = 0; y < pixels.Height; y += 16)
{ {
using (PixelArea<TPixel> rgbBytes = new PixelArea<TPixel>(8, 8, ComponentOrder.Xyz)) for (int x = 0; x < pixels.Width; x += 16)
{ {
for (int y = 0; y < pixels.Height; y += 16) for (int i = 0; i < 4; i++)
{ {
for (int x = 0; x < pixels.Width; x += 16) int xOff = (i & 1) * 8;
{ int yOff = (i & 2) * 4;
for (int i = 0; i < 4; i++)
{ pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff);
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4; cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;
ToYCbCr(pixels, tables, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes);
prevDCY = this.WriteBlock(
prevDCY = this.WriteBlock( QuantIndex.Luminance,
QuantIndex.Luminance, prevDCY,
prevDCY, &pixelConverter.Y,
&b, &temp1,
&temp1, &temp2,
&temp2, &onStackLuminanceQuantTable,
&onStackLuminanceQuantTable, unzig.Data);
unzig.Data);
}
Block8x8F.Scale16X16To8X8(&b, cbPtr);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
Block8x8F.Scale16X16To8X8(&b, crPtr);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
}
} }
Block8x8F.Scale16X16To8X8(&b, cbPtr);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
Block8x8F.Scale16X16To8X8(&b, crPtr);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
&b,
&temp1,
&temp2,
&onStackChrominanceQuantTable,
unzig.Data);
} }
} }
} }

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

Loading…
Cancel
Save