Browse Source

Merge pull request #10 from SixLabors/master

Latest changes from official master
pull/1050/head
Sergio Pedri 7 years ago
committed by GitHub
parent
commit
d5e7fa1010
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      .editorconfig
  2. 4
      .github/CONTRIBUTING.md
  3. 6
      .gitmodules
  4. 3
      CODE_OF_CONDUCT.md
  5. 2
      Directory.Build.props
  6. 10
      Directory.Build.targets
  7. 1
      ImageSharp.sln
  8. 4
      README.md
  9. 1
      shared-infrastructure
  10. 4
      src/Directory.Build.props
  11. 11
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  12. 287
      src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
  13. 6
      src/ImageSharp.Drawing/Processing/PatternBrush.cs
  14. 17
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  15. 38
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  16. 9
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
  17. 18
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs
  18. 9
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
  19. 6
      src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs
  20. 8
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor.cs
  21. 66
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
  22. 9
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  23. 37
      src/ImageSharp/Advanced/AotCompilerTools.cs
  24. 10
      src/ImageSharp/Advanced/IImageVisitor.cs
  25. 38
      src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs
  26. 35
      src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
  27. 6
      src/ImageSharp/Color/Color.NamedColors.cs
  28. 6
      src/ImageSharp/Color/Color.cs
  29. 4
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs
  30. 14
      src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs
  31. 178
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  32. 294
      src/ImageSharp/Common/Helpers/Guard.cs
  33. 2
      src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
  34. 4
      src/ImageSharp/Configuration.cs
  35. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
  36. 26
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  37. 20
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  38. 58
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  39. 115
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
  40. 4
      src/ImageSharp/IImage.cs
  41. 43
      src/ImageSharp/Image.cs
  42. 13
      src/ImageSharp/ImageExtensions.cs
  43. 12
      src/ImageSharp/ImageFrame.cs
  44. 2
      src/ImageSharp/ImageFrameCollection{TPixel}.cs
  45. 22
      src/ImageSharp/ImageFrame{TPixel}.cs
  46. 13
      src/ImageSharp/ImageSharp.csproj
  47. 36
      src/ImageSharp/Image{TPixel}.cs
  48. 132
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  49. 22
      src/ImageSharp/Memory/Buffer2D{T}.cs
  50. 14
      src/ImageSharp/Memory/BufferArea{T}.cs
  51. 1
      src/ImageSharp/Memory/MemoryOwnerExtensions.cs
  52. 43
      src/ImageSharp/Memory/RowInterval.cs
  53. 5
      src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs
  54. 6
      src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs
  55. 648
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  56. 6
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  57. 1659
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  58. 100
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  59. 154
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  60. 131
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  61. 2
      src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs
  62. 8
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs
  63. 49
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  64. 4
      src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs
  65. 6
      src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs
  66. 44
      src/ImageSharp/Processing/Extensions/LightnessExtensions.cs
  67. 33
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  68. 50
      src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
  69. 0
      src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs
  70. 26
      src/ImageSharp/Processing/KnownFilterMatrices.cs
  71. 11
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
  72. 19
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs
  73. 11
      src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs
  74. 15
      src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs
  75. 11
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  76. 21
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  77. 135
      src/ImageSharp/Processing/Processors/CloningImageProcessor.cs
  78. 196
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  79. 5
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs
  80. 25
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  81. 5
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
  82. 22
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
  83. 29
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  84. 24
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  85. 28
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  86. 33
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  87. 39
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  88. 7
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
  89. 22
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  90. 9
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
  91. 22
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  92. 9
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  93. 16
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  94. 12
      src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs
  95. 10
      src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs
  96. 8
      src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs
  97. 10
      src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs
  98. 10
      src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs
  99. 10
      src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs
  100. 15
      src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs

13
.editorconfig

@ -79,6 +79,7 @@ dotnet_naming_rule.private_and_internal_fields_should_be_camel_case.symbols = pr
dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.severity = suggestion dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.style = pascal_case dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.symbols = public_and_protected_declarations dotnet_naming_rule.public_and_protected_declarations_should_be_pascal_case.symbols = public_and_protected_declarations
dotnet_naming_symbols.public_and_protected_declarations.applicable_kinds = method, field, event, property
dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.severity = suggestion dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.style = pascal_case dotnet_naming_rule.static_readonly_declarations_should_be_pascal_case.style = pascal_case
@ -322,11 +323,11 @@ csharp_space_between_square_brackets = false
# suggest conditional delegate calls, # suggest conditional delegate calls,
# suggest deconstructed variable declarations, # suggest deconstructed variable declarations,
# generate expression-bodied accessors, # generate expression-bodied accessors,
# don't generate expression-bodied constructors, # generate expression-bodied constructors,
# generate expression-bodied indexers, # generate expression-bodied indexers,
# generate expression-bodied lambdas, # generate expression-bodied lambdas,
# don't generate expression-bodied methods, # generate expression-bodied methods,
# don't generate expression-bodied operators, # generate expression-bodied operators,
# generate expression-bodied properties, # generate expression-bodied properties,
# suggest inlined variable declarations, # suggest inlined variable declarations,
# suggest local over anonymous functions, # suggest local over anonymous functions,
@ -348,11 +349,11 @@ csharp_style_conditional_delegate_call = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_constructors = true:silent
csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_operators = true:silent
csharp_style_expression_bodied_properties = true:silent csharp_style_expression_bodied_properties = true:silent
csharp_style_inlined_variable_declaration = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion

4
.github/CONTRIBUTING.md

@ -29,6 +29,10 @@
* Ask any question about how to use ImageSharp in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General). * Ask any question about how to use ImageSharp in the [ImageSharp Gitter Chat Room](https://gitter.im/ImageSharp/General).
#### Code of Conduct
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community.
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
And please remember. ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible imageprocessing available to all. Open Source can only exist with your help. And please remember. ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible imageprocessing available to all. Open Source can only exist with your help.
Thanks for reading! Thanks for reading!

6
.gitmodules

@ -2,6 +2,6 @@
path = tests/Images/External path = tests/Images/External
url = https://github.com/SixLabors/Imagesharp.Tests.Images.git url = https://github.com/SixLabors/Imagesharp.Tests.Images.git
branch = master branch = master
[submodule "standards"] [submodule "shared-infrastructure"]
path = standards path = shared-infrastructure
url = https://github.com/SixLabors/Standards url = https://github.com/SixLabors/SharedInfrastructure

3
CODE_OF_CONDUCT.md

@ -0,0 +1,3 @@
# Code of Conduct
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community.
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).

2
Directory.Build.props

@ -52,7 +52,7 @@
<!-- Default settings that are otherwise undefined --> <!-- Default settings that are otherwise undefined -->
<PropertyGroup> <PropertyGroup>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)standards/SixLabors.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)shared-infrastructure/SixLabors.snk</AssemblyOriginatorKeyFile>
<Copyright>Copyright © Six Labors and Contributors</Copyright> <Copyright>Copyright © Six Labors and Contributors</Copyright>
<Features>strict;IOperation</Features> <Features>strict;IOperation</Features>
<HighEntropyVA>true</HighEntropyVA> <HighEntropyVA>true</HighEntropyVA>

10
Directory.Build.targets

@ -25,13 +25,13 @@
<PackageReference Update="BenchmarkDotNet" Version="0.11.5" /> <PackageReference Update="BenchmarkDotNet" Version="0.11.5" />
<PackageReference Update="Colourful" Version="2.0.2" /> <PackageReference Update="Colourful" Version="2.0.2" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.12.0" /> <PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.12.0" />
<PackageReference Update="Microsoft.Net.Compilers.Toolset" Version="3.1.0" /> <PackageReference Update="Microsoft.Net.Compilers.Toolset" Version="3.3.1" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="15.9.0" /> <PackageReference Update="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Update="Moq" Version="4.10.0" /> <PackageReference Update="Moq" Version="4.10.0" />
<PackageReference Update="SixLabors.Core" Version="1.0.0-dev000101" /> <PackageReference Update="SixLabors.Core" Version="1.0.0-beta0008" />
<PackageReference Update="SixLabors.Fonts" Version="1.0.0-beta0008" /> <PackageReference Update="SixLabors.Fonts" Version="1.0.0-beta0009" />
<PackageReference Update="SixLabors.Shapes" Version="1.0.0-beta0008" /> <PackageReference Update="SixLabors.Shapes" Version="1.0.0-beta0009" />
<PackageReference Update="SixLabors.Shapes.Text" Version="1.0.0-beta0007" /> <PackageReference Update="SixLabors.Shapes.Text" Version="1.0.0-beta0009" />
<PackageReference Update="StyleCop.Analyzers" Version="1.1.118" /> <PackageReference Update="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Update="System.Drawing.Common" Version="4.5.1" /> <PackageReference Update="System.Drawing.Common" Version="4.5.1" />
<PackageReference Update="System.IO.Compression" Version="4.3.0" /> <PackageReference Update="System.IO.Compression" Version="4.3.0" />

1
ImageSharp.sln

@ -21,7 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt
LICENSE = LICENSE LICENSE = LICENSE
README.md = README.md README.md = README.md
run-tests.ps1 = run-tests.ps1 run-tests.ps1 = run-tests.ps1
stylecop.json = stylecop.json
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{1799C43E-5C54-4A8F-8D64-B1475241DB0D}"

4
README.md

@ -63,6 +63,10 @@ The **ImageSharp** library is made up of multiple packages:
- Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag. **Do not** open issues for questions! - Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag. **Do not** open issues for questions!
- Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening issues or pull requests! - Please read our [Contribution Guide](https://github.com/SixLabors/ImageSharp/blob/master/.github/CONTRIBUTING.md) before opening issues or pull requests!
### Code of Conduct
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community.
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
### API ### API
Our API is designed to be simple to consume. Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. Our API is designed to be simple to consume. Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix.

1
shared-infrastructure

@ -0,0 +1 @@
Subproject commit faf84e44ec90e8a42a7271bcd04fea76279efb08

4
src/Directory.Build.props

@ -18,12 +18,12 @@
<Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.props" /> <Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.props" />
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\standards\SixLabors.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)..\shared-infrastructure\SixLabors.ruleset</CodeAnalysisRuleSet>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)..\standards\stylecop.json" /> <AdditionalFiles Include="$(MSBuildThisFileDirectory)..\shared-infrastructure\stylecop.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

11
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
@ -11,6 +11,15 @@
<TargetFrameworks>netcoreapp2.1;netstandard1.3;netstandard2.0</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;netstandard1.3;netstandard2.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<!-- TODO: Include .NETSTANDARD2.1 when released-->
<PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp2')) ">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp2.1')) ">
<DefineConstants>$(DefineConstants);SUPPORTS_HASHCODE</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.Fonts" /> <PackageReference Include="SixLabors.Fonts" />
<PackageReference Include="SixLabors.Shapes" /> <PackageReference Include="SixLabors.Shapes" />

287
src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

@ -0,0 +1,287 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Provides an implementation of a brush for painting gradients between multiple color positions in 2D coordinates.
/// It works similarly with the class in System.Drawing.Drawing2D of the same name.
/// </summary>
public sealed class PathGradientBrush : IBrush
{
private readonly IList<Edge> edges;
private readonly Color centerColor;
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </summary>
/// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
public PathGradientBrush(PointF[] points, Color[] colors, Color centerColor)
{
if (points == null)
{
throw new ArgumentNullException(nameof(points));
}
if (points.Length < 3)
{
throw new ArgumentOutOfRangeException(
nameof(points),
"There must be at least 3 lines to construct a path gradient brush.");
}
if (colors == null)
{
throw new ArgumentNullException(nameof(colors));
}
if (!colors.Any())
{
throw new ArgumentOutOfRangeException(
nameof(colors),
"One or more color is needed to construct a path gradient brush.");
}
int size = points.Length;
var lines = new ILineSegment[size];
for (int i = 0; i < size; i++)
{
lines[i] = new LinearLineSegment(points[i % size], points[(i + 1) % size]);
}
this.centerColor = centerColor;
Color ColorAt(int index) => colors[index % colors.Length];
this.edges = lines.Select(s => new Path(s))
.Select((path, i) => new Edge(path, ColorAt(i), ColorAt(i + 1))).ToList();
}
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </summary>
/// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
public PathGradientBrush(PointF[] points, Color[] colors)
: this(points, colors, CalculateCenterColor(colors))
{
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator<TPixel>(
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return new PathGradientBrushApplicator<TPixel>(source, this.edges, this.centerColor, options);
}
private static Color CalculateCenterColor(Color[] colors)
{
if (colors == null)
{
throw new ArgumentNullException(nameof(colors));
}
if (!colors.Any())
{
throw new ArgumentOutOfRangeException(
nameof(colors),
"One or more color is needed to construct a path gradient brush.");
}
return new Color(colors.Select(c => c.ToVector4()).Aggregate((p1, p2) => p1 + p2) / colors.Length);
}
private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length();
private struct Intersection
{
public Intersection(PointF point, float distance)
{
this.Point = point;
this.Distance = distance;
}
public PointF Point { get; }
public float Distance { get; }
}
/// <summary>
/// An edge of the polygon that represents the gradient area.
/// </summary>
private class Edge
{
private readonly Path path;
private readonly float length;
private readonly PointF[] buffer;
public Edge(Path path, Color startColor, Color endColor)
{
this.path = path;
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray();
this.Start = points.First();
this.StartColor = startColor.ToVector4();
this.End = points.Last();
this.EndColor = endColor.ToVector4();
this.length = DistanceBetween(this.End, this.Start);
this.buffer = new PointF[this.path.MaxIntersections];
}
public PointF Start { get; }
public Vector4 StartColor { get; }
public PointF End { get; }
public Vector4 EndColor { get; }
public Intersection? FindIntersection(PointF start, PointF end)
{
int intersections = this.path.FindIntersections(start, end, this.buffer);
if (intersections == 0)
{
return null;
}
return this.buffer.Take(intersections)
.Select(p => new Intersection(point: p, distance: ((Vector2)(p - start)).LengthSquared()))
.Aggregate((min, current) => min.Distance > current.Distance ? current : min);
}
public Vector4 ColorAt(float distance)
{
float ratio = this.length > 0 ? distance / this.length : 0;
return Vector4.Lerp(this.StartColor, this.EndColor, ratio);
}
public Vector4 ColorAt(PointF point) => this.ColorAt(DistanceBetween(point, this.Start));
}
/// <summary>
/// The path gradient brush applicator.
/// </summary>
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PointF center;
private readonly Vector4 centerColor;
private readonly float maxDistance;
private readonly IList<Edge> edges;
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="edges">Edges of the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
/// <param name="options">The options.</param>
public PathGradientBrushApplicator(
ImageFrame<TPixel> source,
IList<Edge> edges,
Color centerColor,
GraphicsOptions options)
: base(source, options)
{
this.edges = edges;
PointF[] points = edges.Select(s => s.Start).ToArray();
this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count;
this.centerColor = centerColor.ToVector4();
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max();
}
/// <inheritdoc />
internal override TPixel this[int x, int y]
{
get
{
var point = new PointF(x, y);
if (point == this.center)
{
return new Color(this.centerColor).ToPixel<TPixel>();
}
Vector2 direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance);
(Edge edge, Intersection? info) = this.FindIntersection(point, end);
if (!info.HasValue)
{
return Color.Transparent.ToPixel<TPixel>();
}
PointF intersection = info.Value.Point;
Vector4 edgeColor = edge.ColorAt(intersection);
float length = DistanceBetween(intersection, this.center);
float ratio = length > 0 ? DistanceBetween(intersection, point) / length : 0;
Vector4 color = Vector4.Lerp(edgeColor, this.centerColor, ratio);
return new Color(color).ToPixel<TPixel>();
}
}
private (Edge edge, Intersection? info) FindIntersection(PointF start, PointF end)
{
(Edge edge, Intersection? info) closest = default;
foreach (Edge edge in this.edges)
{
Intersection? intersection = edge.FindIntersection(start, end);
if (!intersection.HasValue)
{
continue;
}
if (closest.info == null || closest.info.Value.Distance > intersection.Value.Distance)
{
closest = (edge, intersection);
}
}
return closest;
}
/// <inheritdoc />
public override void Dispose()
{
}
}
}
}

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

@ -99,7 +99,6 @@ namespace SixLabors.ImageSharp.Processing
new PatternBrushApplicator<TPixel>( new PatternBrushApplicator<TPixel>(
source, source,
this.pattern.ToPixelMatrix<TPixel>(source.Configuration), this.pattern.ToPixelMatrix<TPixel>(source.Configuration),
this.patternVector,
options); options);
/// <summary> /// <summary>
@ -112,20 +111,17 @@ namespace SixLabors.ImageSharp.Processing
/// The pattern. /// The pattern.
/// </summary> /// </summary>
private readonly DenseMatrix<TPixel> pattern; private readonly DenseMatrix<TPixel> pattern;
private readonly DenseMatrix<Vector4> patternVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class. /// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="patternVector">The patternVector.</param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
public PatternBrushApplicator(ImageFrame<TPixel> source, in DenseMatrix<TPixel> pattern, DenseMatrix<Vector4> patternVector, GraphicsOptions options) public PatternBrushApplicator(ImageFrame<TPixel> source, in DenseMatrix<TPixel> pattern, GraphicsOptions options)
: base(source, options) : base(source, options)
{ {
this.pattern = pattern; this.pattern = pattern;
this.patternVector = patternVector;
} }
/// <summary> /// <summary>

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -59,10 +60,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public float Opacity { get; } public float Opacity { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixelBg> CreatePixelSpecificProcessor<TPixelBg>() public IImageProcessor<TPixelBg> CreatePixelSpecificProcessor<TPixelBg>(Image<TPixelBg> source, Rectangle sourceRectangle)
where TPixelBg : struct, IPixel<TPixelBg> where TPixelBg : struct, IPixel<TPixelBg>
{ {
var visitor = new ProcessorFactoryVisitor<TPixelBg>(this); var visitor = new ProcessorFactoryVisitor<TPixelBg>(this, source, sourceRectangle);
this.Image.AcceptVisitor(visitor); this.Image.AcceptVisitor(visitor);
return visitor.Result; return visitor.Result;
} }
@ -71,10 +72,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
where TPixelBg : struct, IPixel<TPixelBg> where TPixelBg : struct, IPixel<TPixelBg>
{ {
private readonly DrawImageProcessor definition; private readonly DrawImageProcessor definition;
private readonly Image<TPixelBg> source;
private readonly Rectangle sourceRectangle;
public ProcessorFactoryVisitor(DrawImageProcessor definition) public ProcessorFactoryVisitor(DrawImageProcessor definition, Image<TPixelBg> source, Rectangle sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
this.source = source;
this.sourceRectangle = sourceRectangle;
} }
public IImageProcessor<TPixelBg> Result { get; private set; } public IImageProcessor<TPixelBg> Result { get; private set; }
@ -84,6 +89,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{ {
this.Result = new DrawImageProcessor<TPixelBg, TPixelFg>( this.Result = new DrawImageProcessor<TPixelBg, TPixelFg>(
image, image,
this.source,
this.sourceRectangle,
this.definition.Location, this.definition.Location,
this.definition.ColorBlendingMode, this.definition.ColorBlendingMode,
this.definition.AlphaCompositionMode, this.definition.AlphaCompositionMode,
@ -91,4 +98,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
} }
} }
} }
} }

38
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -4,7 +4,7 @@
using System; using System;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -20,19 +20,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
where TPixelFg : struct, IPixel<TPixelFg> where TPixelFg : struct, IPixel<TPixelFg>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class. /// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelBg, TPixelFg}"/> class.
/// </summary> /// </summary>
/// <param name="image">The image to blend with the currently processing image.</param> /// <param name="image">The foreground <see cref="Image{TPixelFg}"/> to blend with the currently processing image.</param>
/// <param name="source">The source <see cref="Image{TPixelBg}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</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="colorBlendingMode">The blending mode to use when drawing the image.</param> /// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param> /// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
/// <param name="opacity">The opacity of the 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>
public DrawImageProcessor( public DrawImageProcessor(
Image<TPixelFg> image, Image<TPixelFg> image,
Image<TPixelBg> source,
Rectangle sourceRectangle,
Point location, Point location,
PixelColorBlendingMode colorBlendingMode, PixelColorBlendingMode colorBlendingMode,
PixelAlphaCompositionMode alphaCompositionMode, PixelAlphaCompositionMode alphaCompositionMode,
float opacity) float opacity)
: base(source, sourceRectangle)
{ {
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
@ -63,11 +68,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public Point Location { get; } public Point Location { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply( protected override void OnFrameApply(ImageFrame<TPixelBg> source)
ImageFrame<TPixelBg> source,
Rectangle sourceRectangle,
Configuration configuration)
{ {
Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration;
Image<TPixelFg> targetImage = this.Image; Image<TPixelFg> targetImage = this.Image;
PixelBlender<TPixelBg> blender = this.Blender; PixelBlender<TPixelBg> blender = this.Blender;
int locationY = this.Location.Y; int locationY = this.Location.Y;
@ -86,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
// not a valid operation because rectangle does not overlap with this image. // Not a valid operation because rectangle does not overlap with this image.
if (workingRect.Width <= 0 || workingRect.Height <= 0) if (workingRect.Width <= 0 || workingRect.Height <= 0)
{ {
throw new ImageProcessingException( throw new ImageProcessingException(
@ -97,15 +102,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
workingRect, workingRect,
configuration, configuration,
rows => rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{ {
for (int y = rows.Min; y < rows.Max; y++) Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
{ Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width); blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
Span<TPixelFg> foreground = }
targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); });
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
}
});
} }
} }
} }

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

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{ {
@ -33,10 +34,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public GraphicsOptions Options { get; } public GraphicsOptions Options { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new FillProcessor<TPixel>(this); return new FillProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

18
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs

@ -5,8 +5,8 @@ using System;
using System.Buffers; using System.Buffers;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -21,14 +21,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{ {
private readonly FillProcessor definition; private readonly FillProcessor definition;
public FillProcessor(FillProcessor definition) public FillProcessor(FillProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration;
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
int startY = sourceRectangle.Y; int startY = sourceRectangle.Y;
@ -42,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int width = maxX - minX; int width = maxX - minX;
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); Rectangle workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
IBrush brush = this.definition.Brush; IBrush brush = this.definition.Brush;
GraphicsOptions options = this.definition.Options; GraphicsOptions options = this.definition.Options;
@ -50,9 +53,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
// If there's no reason for blending, then avoid it. // If there's no reason for blending, then avoid it.
if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush))
{ {
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration)
.MultiplyMinimumPixelsPerTask(4);
TPixel colorPixel = solidBrush.Color.ToPixel<TPixel>(); var colorPixel = solidBrush.Color.ToPixel<TPixel>();
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
workingRect, workingRect,
@ -115,4 +119,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color);
} }
} }
} }

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

@ -1,8 +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.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Drawing namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{ {
@ -41,10 +42,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public GraphicsOptions Options { get; } public GraphicsOptions Options { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new FillRegionProcessor<TPixel>(this); return new FillRegionProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

6
src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs

@ -23,14 +23,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
{ {
private readonly FillRegionProcessor definition; private readonly FillRegionProcessor definition;
public FillRegionProcessor(FillRegionProcessor definition) public FillRegionProcessor(FillRegionProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
Configuration configuration = this.Configuration;
GraphicsOptions options = this.definition.Options; GraphicsOptions options = this.definition.Options;
IBrush brush = this.definition.Brush; IBrush brush = this.definition.Brush;
Region region = this.definition.Region; Region region = this.definition.Region;

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

@ -1,4 +1,4 @@
// 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;
@ -72,10 +72,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
public PointF Location { get; } public PointF Location { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new DrawTextProcessor<TPixel>(this); return new DrawTextProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

66
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs

@ -27,7 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
private readonly DrawTextProcessor definition; private readonly DrawTextProcessor definition;
public DrawTextProcessor(DrawTextProcessor definition) public DrawTextProcessor(DrawTextProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
} }
@ -44,35 +45,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
private IBrush Brush => this.definition.Brush; private IBrush Brush => this.definition.Brush;
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) protected override void BeforeImageApply()
{ {
base.BeforeImageApply(source, sourceRectangle); base.BeforeImageApply();
// do everything at the image level as we are delegating the processing down to other processors // do everything at the image level as we are delegating the processing down to other processors
var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location) var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location)
{ {
ApplyKerning = this.Options.ApplyKerning, ApplyKerning = this.Options.ApplyKerning,
TabWidth = this.Options.TabWidth, TabWidth = this.Options.TabWidth,
WrappingWidth = this.Options.WrapTextWidth, WrappingWidth = this.Options.WrapTextWidth,
HorizontalAlignment = this.Options.HorizontalAlignment, HorizontalAlignment = this.Options.HorizontalAlignment,
VerticalAlignment = this.Options.VerticalAlignment VerticalAlignment = this.Options.VerticalAlignment
}; };
this.textRenderer = new CachingGlyphRenderer(source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null); this.textRenderer = new CachingGlyphRenderer(this.Source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null);
this.textRenderer.Options = (GraphicsOptions)this.Options; this.textRenderer.Options = (GraphicsOptions)this.Options;
var renderer = new TextRenderer(this.textRenderer); var renderer = new TextRenderer(this.textRenderer);
renderer.RenderText(this.Text, style); renderer.RenderText(this.Text, style);
} }
protected override void AfterImageApply(Image<TPixel> source, Rectangle sourceRectangle) protected override void AfterImageApply()
{ {
base.AfterImageApply(source, sourceRectangle); base.AfterImageApply();
this.textRenderer?.Dispose(); this.textRenderer?.Dispose();
this.textRenderer = null; this.textRenderer = null;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
// this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome // this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome
Draw(this.textRenderer.FillOperations, this.Brush); Draw(this.textRenderer.FillOperations, this.Brush);
@ -82,33 +83,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{ {
if (operations?.Count > 0) if (operations?.Count > 0)
{ {
using (BrushApplicator<TPixel> app = brush.CreateApplicator(source, sourceRectangle, this.textRenderer.Options)) using (BrushApplicator<TPixel> app = brush.CreateApplicator(source, this.SourceRectangle, this.textRenderer.Options))
{ {
foreach (DrawingOperation operation in operations) foreach (DrawingOperation operation in operations)
{ {
Buffer2D<float> buffer = operation.Map; Buffer2D<float> buffer = operation.Map;
int startY = operation.Location.Y; int startY = operation.Location.Y;
int startX = operation.Location.X; int startX = operation.Location.X;
int offSetSpan = 0; int offsetSpan = 0;
if (startX < 0) if (startX < 0)
{ {
offSetSpan = -startX; offsetSpan = -startX;
startX = 0; startX = 0;
} }
int fistRow = 0; if (startX >= source.Width)
{
continue;
}
int firstRow = 0;
if (startY < 0) if (startY < 0)
{ {
fistRow = -startY; firstRow = -startY;
} }
int maxHeight = source.Height - startY; int maxHeight = source.Height - startY;
int end = Math.Min(operation.Map.Height, maxHeight); int end = Math.Min(operation.Map.Height, maxHeight);
for (int row = fistRow; row < end; row++) for (int row = firstRow; row < end; row++)
{ {
int y = startY + row; int y = startY + row;
Span<float> span = buffer.GetRowSpan(row).Slice(offSetSpan); Span<float> span = buffer.GetRowSpan(row).Slice(offsetSpan);
app.Apply(span, startX, y); app.Apply(span, startX, y);
} }
} }
@ -279,19 +285,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
if (this.renderFill) if (this.renderFill)
{ {
this.FillOperations.Add(new DrawingOperation this.FillOperations.Add(new DrawingOperation
{ {
Location = this.currentRenderPosition, Location = this.currentRenderPosition,
Map = renderData.FillMap Map = renderData.FillMap
}); });
} }
if (this.renderOutline) if (this.renderOutline)
{ {
this.OutlineOperations.Add(new DrawingOperation this.OutlineOperations.Add(new DrawingOperation
{ {
Location = this.currentRenderPosition, Location = this.currentRenderPosition,
Map = renderData.OutlineMap Map = renderData.OutlineMap
}); });
} }
} }

9
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -15,6 +15,15 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary> /// </summary>
public static class AdvancedImageExtensions public static class AdvancedImageExtensions
{ {
/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
/// </summary>
/// <param name="source">The source.</param>
/// <param name="visitor">The visitor.</param>
public static void AcceptVisitor(this Image source, IImageVisitor visitor)
=> source.Accept(visitor);
/// <summary> /// <summary>
/// Gets the configuration for the image. /// Gets the configuration for the image.
/// </summary> /// </summary>

37
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced
{ {
@ -81,9 +80,8 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileWuQuantizer<TPixel>(); AotCompileWuQuantizer<TPixel>();
AotCompileDithering<TPixel>(); AotCompileDithering<TPixel>();
AotCompilePixelOperations<TPixel>(); AotCompilePixelOperations<TPixel>();
AotCompileResizeOperations<TPixel>();
System.Runtime.CompilerServices.Unsafe.SizeOf<TPixel>(); Unsafe.SizeOf<TPixel>();
AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder()); AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder());
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
@ -107,8 +105,10 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileOctreeQuantizer<TPixel>() private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)); using (var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)))
test.AotGetPalette(); {
test.AotGetPalette();
}
} }
/// <summary> /// <summary>
@ -118,9 +118,11 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileWuQuantizer<TPixel>() private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)); using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)))
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1)); {
test.AotGetPalette(); test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
}
} }
/// <summary> /// <summary>
@ -132,7 +134,10 @@ namespace SixLabors.ImageSharp.Advanced
{ {
var test = new FloydSteinbergDiffuser(); var test = new FloydSteinbergDiffuser();
TPixel pixel = default; TPixel pixel = default;
test.Dither(new ImageFrame<TPixel>(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0); using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0);
}
} }
/// <summary> /// <summary>
@ -171,17 +176,5 @@ namespace SixLabors.ImageSharp.Advanced
var pixelOp = new PixelOperations<TPixel>(); var pixelOp = new PixelOperations<TPixel>();
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);
} }
/// <summary>
/// This method pre-seeds the ResizeProcessor for the AoT compiler on iOS.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileResizeOperations<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var resizeProcessor = new ResizeProcessor(new ResizeOptions(), default);
var genericResizeProcessor = new ResizeProcessor<TPixel>((ResizeProcessor)resizeProcessor.CreatePixelSpecificProcessor<TPixel>());
genericResizeProcessor.AotCreateDestination(new Image<TPixel>(0, 0), default);
}
} }
} }

10
src/ImageSharp/IImageVisitor.cs → src/ImageSharp/Advanced/IImageVisitor.cs

@ -3,13 +3,13 @@
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp.Advanced
{ {
/// <summary> /// <summary>
/// A visitor to implement double-dispatch pattern in order to apply pixel-specific operations /// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
/// on non-generic <see cref="Image"/> instances. The operation is dispatched by <see cref="Image.AcceptVisitor"/>. /// on non-generic <see cref="Image"/> instances.
/// </summary> /// </summary>
internal interface IImageVisitor public interface IImageVisitor
{ {
/// <summary> /// <summary>
/// Provides a pixel-specific implementation for a given operation. /// Provides a pixel-specific implementation for a given operation.
@ -19,4 +19,4 @@ namespace SixLabors.ImageSharp
void Visit<TPixel>(Image<TPixel> image) void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

38
src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs → src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs

@ -1,16 +1,17 @@
// 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.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.Memory; using SixLabors.Memory;
namespace SixLabors.ImageSharp.ParallelUtils namespace SixLabors.ImageSharp.Advanced.ParallelUtils
{ {
/// <summary> /// <summary>
/// Defines execution settings for methods in <see cref="ParallelHelper"/>. /// Defines execution settings for methods in <see cref="ParallelHelper"/>.
/// </summary> /// </summary>
internal readonly struct ParallelExecutionSettings public readonly struct ParallelExecutionSettings
{ {
/// <summary> /// <summary>
/// Default value for <see cref="MinimumPixelsProcessedPerTask"/>. /// Default value for <see cref="MinimumPixelsProcessedPerTask"/>.
@ -20,11 +21,24 @@ namespace SixLabors.ImageSharp.ParallelUtils
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct. /// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
/// </summary> /// </summary>
/// <param name="maxDegreeOfParallelism">The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.</param>
/// <param name="minimumPixelsProcessedPerTask">The value for <see cref="MinimumPixelsProcessedPerTask"/>.</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
public ParallelExecutionSettings( public ParallelExecutionSettings(
int maxDegreeOfParallelism, int maxDegreeOfParallelism,
int minimumPixelsProcessedPerTask, int minimumPixelsProcessedPerTask,
MemoryAllocator memoryAllocator) MemoryAllocator memoryAllocator)
{ {
// Shall be compatible with ParallelOptions.MaxDegreeOfParallelism:
// https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism
if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1)
{
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
}
Guard.MustBeGreaterThan(minimumPixelsProcessedPerTask, 0, nameof(minimumPixelsProcessedPerTask));
Guard.NotNull(memoryAllocator, nameof(memoryAllocator));
this.MaxDegreeOfParallelism = maxDegreeOfParallelism; this.MaxDegreeOfParallelism = maxDegreeOfParallelism;
this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
this.MemoryAllocator = memoryAllocator; this.MemoryAllocator = memoryAllocator;
@ -33,13 +47,15 @@ namespace SixLabors.ImageSharp.ParallelUtils
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct. /// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
/// </summary> /// </summary>
/// <param name="maxDegreeOfParallelism">The value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/>.</param>
public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator)
: this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator)
{ {
} }
/// <summary> /// <summary>
/// Gets the MemoryAllocator /// Gets the <see cref="MemoryAllocator"/>.
/// </summary> /// </summary>
public MemoryAllocator MemoryAllocator { get; } public MemoryAllocator MemoryAllocator { get; }
@ -60,12 +76,26 @@ namespace SixLabors.ImageSharp.ParallelUtils
/// Creates a new instance of <see cref="ParallelExecutionSettings"/> /// Creates a new instance of <see cref="ParallelExecutionSettings"/>
/// having <see cref="MinimumPixelsProcessedPerTask"/> multiplied by <paramref name="multiplier"/> /// having <see cref="MinimumPixelsProcessedPerTask"/> multiplied by <paramref name="multiplier"/>
/// </summary> /// </summary>
/// <param name="multiplier">The value to multiply <see cref="MinimumPixelsProcessedPerTask"/> with.</param>
/// <returns>The modified <see cref="ParallelExecutionSettings"/>.</returns>
public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier)
{ {
Guard.MustBeGreaterThan(multiplier, 0, nameof(multiplier));
return new ParallelExecutionSettings( return new ParallelExecutionSettings(
this.MaxDegreeOfParallelism, this.MaxDegreeOfParallelism,
this.MinimumPixelsProcessedPerTask * multiplier, this.MinimumPixelsProcessedPerTask * multiplier,
this.MemoryAllocator); this.MemoryAllocator);
} }
/// <summary>
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelUtils.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/>.</param>
/// <returns>The <see cref="ParallelExecutionSettings"/>.</returns>
public static ParallelExecutionSettings FromConfiguration(Configuration configuration)
{
return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator);
}
} }
} }

35
src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs → src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs

@ -10,29 +10,25 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.ParallelUtils namespace SixLabors.ImageSharp.Advanced.ParallelUtils
{ {
/// <summary> /// <summary>
/// Utility methods for batched processing of pixel row intervals. /// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing. /// Parallel execution is optimized for image processing based on values defined
/// Use this instead of direct <see cref="Parallel"/> calls! /// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </summary> /// </summary>
internal static class ParallelHelper public static class ParallelHelper
{ {
/// <summary>
/// Get the default <see cref="ParallelExecutionSettings"/> for a <see cref="Configuration"/>
/// </summary>
public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration)
{
return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator);
}
/// <summary> /// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s. /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary> /// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body) public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body)
{ {
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings(); ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, parallelSettings, body); IterateRows(rectangle, parallelSettings, body);
} }
@ -40,6 +36,9 @@ namespace SixLabors.ImageSharp.ParallelUtils
/// <summary> /// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s. /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary> /// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows( public static void IterateRows(
Rectangle rectangle, Rectangle rectangle,
in ParallelExecutionSettings parallelSettings, in ParallelExecutionSettings parallelSettings,
@ -47,7 +46,9 @@ namespace SixLabors.ImageSharp.ParallelUtils
{ {
ValidateRectangle(rectangle); ValidateRectangle(rectangle);
int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); int maxSteps = DivideCeil(
rectangle.Width * rectangle.Height,
parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
@ -81,7 +82,7 @@ namespace SixLabors.ImageSharp.ParallelUtils
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation. /// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary> /// </summary>
public static void IterateRowsWithTempBuffer<T>( internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle, Rectangle rectangle,
in ParallelExecutionSettings parallelSettings, in ParallelExecutionSettings parallelSettings,
Action<RowInterval, Memory<T>> body) Action<RowInterval, Memory<T>> body)
@ -133,13 +134,13 @@ namespace SixLabors.ImageSharp.ParallelUtils
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation. /// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary> /// </summary>
public static void IterateRowsWithTempBuffer<T>( internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle, Rectangle rectangle,
Configuration configuration, Configuration configuration,
Action<RowInterval, Memory<T>> body) Action<RowInterval, Memory<T>> body)
where T : unmanaged where T : unmanaged
{ {
IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body); IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

6
src/ImageSharp/Color/Color.NamedColors.cs

@ -174,9 +174,9 @@ namespace SixLabors.ImageSharp
public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255); public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255);
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8FBC8B. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8FBC8F.
/// </summary> /// </summary>
public static readonly Color DarkSeaGreen = FromRgba(143, 188, 139, 255); public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255);
/// <summary> /// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #483D8B. /// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #483D8B.
@ -718,4 +718,4 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255); public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255);
} }
} }

6
src/ImageSharp/Color/Color.cs

@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromHex(string hex) public static Color FromHex(string hex)
{ {
Rgba32 rgba = Rgba32.FromHex(hex); var rgba = Rgba32.FromHex(hex);
return new Color(rgba); return new Color(rgba);
} }
@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source); ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source);
PixelOperations<TPixel>.Instance.FromRgba64(Configuration.Default, rgba64Span, destination); PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination);
} }
} }
} }

4
src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs

@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
} }
else else
{ {
s = chroma / (2F - chroma); s = chroma / (2F - max - min);
} }
return new Hsl(h, s, l); return new Hsl(h, s, l);
@ -157,4 +157,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
return value; return value;
} }
} }
} }

14
src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
/// <summary> /// <summary>
@ -20,17 +18,5 @@ namespace SixLabors.ImageSharp
: base(errorMessage) : base(errorMessage)
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="UnknownImageFormatException"/> class with a specified
/// error message and the exception that is the cause of this exception.
/// </summary>
/// <param name="errorMessage">The error message that explains the reason for this exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic)
/// if no inner exception is specified.</param>
public UnknownImageFormatException(string errorMessage, Exception innerException)
: base(errorMessage, innerException)
{
}
} }
} }

178
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -1,168 +1,17 @@
// 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.Diagnostics; using System.Diagnostics;
// TODO: These should just call the guard equivalents // TODO: These should just call the guard equivalents
namespace SixLabors.ImageSharp namespace SixLabors
{ {
/// <summary> /// <summary>
/// Provides methods to protect against invalid parameters for a DEBUG build. /// Provides methods to protect against invalid parameters for a DEBUG build.
/// </summary> /// </summary>
[DebuggerStepThrough] internal static partial class DebugGuard
internal static class DebugGuard
{ {
/// <summary>
/// Verifies, that the method parameter with specified object value is not null
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="value">The target object, which cannot be null.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
[Conditional("DEBUG")]
public static void NotNull<T>(T value, string parameterName)
where T : class
{
if (value is null)
{
throw new ArgumentNullException(parameterName);
}
}
/// <summary>
/// Verifies that the specified value is less than a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeLessThan<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) >= 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}.");
}
}
/// <summary>
/// Verifies that the specified value is less than or equal to a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeLessThanOrEqualTo<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeGreaterThan<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) <= 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
$"Value must be greater than {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeGreaterThanOrEqualTo<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value and less than
/// or equal to a maximum value and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value of greater than the maximum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue min, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min} and less than or equal to {max}.");
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is true
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">
/// The target value, which cannot be false.
/// </param>
/// <param name="parameterName">
/// The name of the parameter that is to be checked.
/// </param>
/// <param name="message">
/// The error message, if any to add to the exception.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is false
/// </exception>
[Conditional("DEBUG")]
public static void IsTrue(bool target, string parameterName, string message)
{
if (!target)
{
throw new ArgumentException(message, parameterName);
}
}
/// <summary> /// <summary>
/// Verifies whether a specific condition is met, throwing an exception if it's false. /// Verifies whether a specific condition is met, throwing an exception if it's false.
/// </summary> /// </summary>
@ -177,25 +26,6 @@ namespace SixLabors.ImageSharp
} }
} }
/// <summary>
/// Verifies, that the method parameter with specified target value is false
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">The target value, which cannot be true.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is true
/// </exception>
[Conditional("DEBUG")]
public static void IsFalse(bool target, string parameterName, string message)
{
if (target)
{
throw new ArgumentException(message, parameterName);
}
}
/// <summary> /// <summary>
/// Verifies, that the target span is of same size than the 'other' span. /// Verifies, that the target span is of same size than the 'other' span.
/// </summary> /// </summary>
@ -236,4 +66,4 @@ namespace SixLabors.ImageSharp
} }
} }
} }
} }

294
src/ImageSharp/Common/Helpers/Guard.cs

@ -1,294 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Provides methods to protect against invalid parameters.
/// </summary>
[DebuggerStepThrough]
internal static class Guard
{
/// <summary>
/// Ensures that the value is not null.
/// </summary>
/// <param name="value">The target object, which cannot be null.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void NotNull<T>(T value, string parameterName)
where T : class
{
if (value is null)
{
ThrowArgumentNullException(parameterName);
}
}
/// <summary>
/// Ensures that the target value is not null, empty, or whitespace.
/// </summary>
/// <param name="value">The target string, which should be checked against being null or empty.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="value"/> is empty or contains only blanks.</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void NotNullOrWhiteSpace(string value, string parameterName)
{
if (value is null)
{
ThrowArgumentNullException(parameterName);
}
if (string.IsNullOrWhiteSpace(value))
{
ThrowArgumentException("Must not be empty or whitespace.", parameterName);
}
}
/// <summary>
/// Ensures that the specified value is less than a maximum value.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeLessThan<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) >= 0)
{
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be less than {max}.");
}
}
/// <summary>
/// Verifies that the specified value is less than or equal to a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeLessThanOrEqualTo<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) > 0)
{
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be less than or equal to {max}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeGreaterThan<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) <= 0)
{
ThrowArgumentOutOfRangeException(
parameterName,
$"Value {value} must be greater than {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeGreaterThanOrEqualTo<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0)
{
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value and less than
/// or equal to a maximum value and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value of greater than the maximum value.
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue min, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
{
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min} and less than or equal to {max}.");
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is true
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">The target value, which cannot be false.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is false
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IsTrue(bool target, string parameterName, string message)
{
if (!target)
{
ThrowArgumentException(message, parameterName);
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is false
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">The target value, which cannot be true.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is true
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IsFalse(bool target, string parameterName, string message)
{
if (target)
{
ThrowArgumentException(message, parameterName);
}
}
/// <summary>
/// Verifies, that the `source` span has the length of 'minLength', or longer.
/// </summary>
/// <typeparam name="T">The element type of the spans</typeparam>
/// <param name="source">The source span.</param>
/// <param name="minLength">The minimum length.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <exception cref="ArgumentException">
/// <paramref name="source"/> has less than <paramref name="minLength"/> items
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeSizedAtLeast<T>(ReadOnlySpan<T> source, int minLength, string parameterName)
{
if (source.Length < minLength)
{
ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName);
}
}
/// <summary>
/// Verifies that the 'destination' span is not shorter than 'source'.
/// </summary>
/// <typeparam name="TSource">The source element type</typeparam>
/// <typeparam name="TDest">The destination element type</typeparam>
/// <param name="source">The source span</param>
/// <param name="destination">The destination span</param>
/// <param name="destinationParamName">The name of the argument for 'destination'</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void DestinationShouldNotBeTooShort<TSource, TDest>(
ReadOnlySpan<TSource> source,
Span<TDest> destination,
string destinationParamName)
{
if (destination.Length < source.Length)
{
ThrowArgumentException("Destination span is too short!", destinationParamName);
}
}
/// <summary>
/// Verifies that the 'destination' span is not shorter than 'source'.
/// </summary>
/// <typeparam name="TSource">The source element type</typeparam>
/// <typeparam name="TDest">The destination element type</typeparam>
/// <param name="source">The source span</param>
/// <param name="destination">The destination span</param>
/// <param name="destinationParamName">The name of the argument for 'destination'</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void DestinationShouldNotBeTooShort<TSource, TDest>(
Span<TSource> source,
Span<TDest> destination,
string destinationParamName)
{
if (destination.Length < source.Length)
{
ThrowArgumentException("Destination span is too short!", destinationParamName);
}
}
/// <summary>
/// Verifies, that the `source` span has the length of 'minLength', or longer.
/// </summary>
/// <typeparam name="T">The element type of the spans</typeparam>
/// <param name="source">The target span.</param>
/// <param name="minLength">The minimum length.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <exception cref="ArgumentException">
/// <paramref name="source"/> has less than <paramref name="minLength"/> items
/// </exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeSizedAtLeast<T>(Span<T> source, int minLength, string parameterName)
{
if (source.Length < minLength)
{
ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName);
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentException(string message, string parameterName)
{
throw new ArgumentException(message, parameterName);
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentOutOfRangeException(string parameterName, string message)
{
throw new ArgumentOutOfRangeException(parameterName, message);
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowArgumentNullException(string parameterName)
{
throw new ArgumentNullException(parameterName);
}
}
}

2
src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp
{ {
public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture;
#if !SUPPORTS_EXTENDED_INTRINSICS
/// <summary> /// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder). /// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary> /// </summary>
@ -74,6 +75,7 @@ namespace SixLabors.ImageSharp
dest = dest.Slice(adjustedCount); dest = dest.Slice(adjustedCount);
} }
} }
#endif
/// <summary> /// <summary>
/// SIMD optimized implementation for <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/>. /// SIMD optimized implementation for <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/>.

4
src/ImageSharp/Configuration.cs

@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp
get => this.maxDegreeOfParallelism; get => this.maxDegreeOfParallelism;
set set
{ {
if (value <= 0) if (value == 0 || value < -1)
{ {
throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism));
} }
@ -161,4 +161,4 @@ namespace SixLabors.ImageSharp
new BmpConfigurationModule()); new BmpConfigurationModule());
} }
} }
} }

8
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs

@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void CheckBits() public void CheckBits()
{ {
if (this.remainingBits < 16) if (this.remainingBits < JpegConstants.Huffman.MinBits)
{ {
this.FillBuffer(); this.FillBuffer();
} }
@ -85,8 +85,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
// Attempt to load at least the minimum number of required bits into the buffer. // Attempt to load at least the minimum number of required bits into the buffer.
// We fail to do so only if we hit a marker or reach the end of the input stream. // We fail to do so only if we hit a marker or reach the end of the input stream.
this.remainingBits += 48; this.remainingBits += JpegConstants.Huffman.FetchBits;
this.data = (this.data << 48) | this.GetBytes(); this.data = (this.data << JpegConstants.Huffman.FetchBits) | this.GetBytes();
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private ulong GetBytes() private ulong GetBytes()
{ {
ulong temp = 0; ulong temp = 0;
for (int i = 0; i < 6; i++) for (int i = 0; i < JpegConstants.Huffman.FetchLoop; i++)
{ {
int b = this.ReadStream(); int b = this.ReadStream();

26
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -82,12 +82,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Figure C.1: make table of Huffman code length for each symbol // Figure C.1: make table of Huffman code length for each symbol
int p = 0; int p = 0;
for (int l = 1; l <= 16; l++) for (int j = 1; j <= 16; j++)
{ {
int i = this.Sizes[l]; int i = this.Sizes[j];
while (i-- != 0) while (i-- != 0)
{ {
huffSize[p++] = (char)l; huffSize[p++] = (char)j;
} }
} }
@ -111,20 +111,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Figure F.15: generate decoding tables for bit-sequential decoding // Figure F.15: generate decoding tables for bit-sequential decoding
p = 0; p = 0;
for (int l = 1; l <= 16; l++) for (int j = 1; j <= 16; j++)
{ {
if (this.Sizes[l] != 0) if (this.Sizes[j] != 0)
{ {
int offset = p - (int)huffCode[p]; this.ValOffset[j] = p - (int)huffCode[p];
this.ValOffset[l] = offset; p += this.Sizes[j];
p += this.Sizes[l]; this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l
this.MaxCode[l] = huffCode[p - 1]; // Maximum code of length l this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify
this.MaxCode[l] <<= 64 - l; // Left justify this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1;
this.MaxCode[l] |= (1ul << (64 - l)) - 1;
} }
else else
{ {
this.MaxCode[l] = 0; this.MaxCode[j] = 0;
} }
} }
@ -142,11 +141,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
p = 0; p = 0;
for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++)
{ {
int jShift = JpegConstants.Huffman.LookupBits - length;
for (int i = 1; i <= this.Sizes[length]; i++, p++) for (int i = 1; i <= this.Sizes[length]; i++, p++)
{ {
// length = current code's length, p = its index in huffCode[] & Values[]. // length = current code's length, p = its index in huffCode[] & Values[].
// Generate left-justified code followed by all possible bit sequences // Generate left-justified code followed by all possible bit sequences
int lookBits = (int)(huffCode[p] << (JpegConstants.Huffman.LookupBits - length)); int lookBits = (int)(huffCode[p] << jShift);
for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--)
{ {
this.LookaheadSize[lookBits] = (byte)length; this.LookaheadSize[lookBits] = (byte)length;

20
src/ImageSharp/Formats/Jpeg/JpegConstants.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
@ -249,6 +250,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public const int RegisterSize = 64; public const int RegisterSize = 64;
/// <summary>
/// The number of bits to fetch when filling the <see cref="HuffmanScanBuffer"/> buffer.
/// </summary>
public const int FetchBits = 48;
/// <summary>
/// The number of times to read the input stream when filling the <see cref="HuffmanScanBuffer"/> buffer.
/// </summary>
public const int FetchLoop = FetchBits / 8;
/// <summary>
/// The minimum number of bits allowed before by the <see cref="HuffmanScanBuffer"/> before fetching.
/// </summary>
public const int MinBits = RegisterSize - FetchBits;
/// <summary> /// <summary>
/// If the next Huffman code is no more than this number of bits, we can obtain its length /// If the next Huffman code is no more than this number of bits, we can obtain its length
/// and the corresponding symbol directly from this tables. /// and the corresponding symbol directly from this tables.
@ -266,4 +282,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public const int LookupSize = 1 << LookupBits; public const int LookupSize = 1 << LookupBits;
} }
} }
} }

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

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -175,11 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.InitializeImage(metadata, out image); this.InitializeImage(metadata, out image);
} }
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata);
{
deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata);
}
break; break;
case PngChunkType.Palette: case PngChunkType.Palette:
@ -465,19 +462,25 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Reads the scanlines within the image. /// Reads the scanlines within the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="chunk">The png chunk containing the compressed scanline data.</param>
/// <param name="image"> The pixel data.</param> /// <param name="image"> The pixel data.</param>
/// <param name="pngMetadata">The png metadata</param> /// <param name="pngMetadata">The png metadata</param>
private void ReadScanlines<TPixel>(Stream dataStream, ImageFrame<TPixel> image, PngMetadata pngMetadata) private void ReadScanlines<TPixel>(PngChunk chunk, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{ {
this.DecodePixelData(dataStream, image, pngMetadata); deframeStream.AllocateNewBytes(chunk.Length, true);
DeflateStream dataStream = deframeStream.CompressedStream;
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{
this.DecodePixelData(dataStream, image, pngMetadata);
}
} }
} }
@ -924,7 +927,11 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
ReadOnlySpan<byte> compressedData = data.Slice(zeroIndex + 2); ReadOnlySpan<byte> compressedData = data.Slice(zeroIndex + 2);
metadata.TextData.Add(new PngTextData(name, this.UncompressTextData(compressedData, PngConstants.Encoding), string.Empty, string.Empty));
if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed))
{
metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty));
}
} }
/// <summary> /// <summary>
@ -987,7 +994,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (compressionFlag == 1) if (compressionFlag == 1)
{ {
ReadOnlySpan<byte> compressedData = data.Slice(dataStartIdx); ReadOnlySpan<byte> compressedData = data.Slice(dataStartIdx);
metadata.TextData.Add(new PngTextData(keyword, this.UncompressTextData(compressedData, PngConstants.TranslatedEncoding), language, translatedKeyword));
if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed))
{
metadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword));
}
} }
else else
{ {
@ -1001,13 +1012,19 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <param name="compressedData">Compressed text data bytes.</param> /// <param name="compressedData">Compressed text data bytes.</param>
/// <param name="encoding">The string encoding to use.</param> /// <param name="encoding">The string encoding to use.</param>
/// <returns>A string.</returns> /// <param name="value">The uncompressed value.</param>
private string UncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding) /// <returns>The <see cref="bool"/>.</returns>
private bool TryUncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, out string value)
{ {
using (var memoryStream = new MemoryStream(compressedData.ToArray())) using (var memoryStream = new MemoryStream(compressedData.ToArray()))
using (var inflateStream = new ZlibInflateStream(memoryStream, () => 0)) using (var inflateStream = new ZlibInflateStream(memoryStream))
{ {
inflateStream.AllocateNewBytes(compressedData.Length); if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
{
value = null;
return false;
}
var uncompressedBytes = new List<byte>(); var uncompressedBytes = new List<byte>();
// Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here. // Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
@ -1018,7 +1035,8 @@ namespace SixLabors.ImageSharp.Formats.Png
bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
} }
return encoding.GetString(uncompressedBytes.ToArray()); value = encoding.GetString(uncompressedBytes.ToArray());
return true;
} }
} }

115
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

@ -13,21 +13,21 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
internal sealed class ZlibInflateStream : Stream internal sealed class ZlibInflateStream : Stream
{ {
/// <summary> /// <summary>
/// Used to read the Adler-32 and Crc-32 checksums /// Used to read the Adler-32 and Crc-32 checksums.
/// We don't actually use this for anything so it doesn't /// We don't actually use this for anything so it doesn't
/// have to be threadsafe. /// have to be threadsafe.
/// </summary> /// </summary>
private static readonly byte[] ChecksumBuffer = new byte[4]; private static readonly byte[] ChecksumBuffer = new byte[4];
/// <summary> /// <summary>
/// The inner raw memory stream /// A default delegate to get more data from the inner stream.
/// </summary> /// </summary>
private readonly Stream innerStream; private static readonly Func<int> GetDataNoOp = () => 0;
/// <summary> /// <summary>
/// The compressed stream sitting over the top of the deframer /// The inner raw memory stream.
/// </summary> /// </summary>
private DeflateStream compressedStream; private readonly Stream innerStream;
/// <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.
@ -43,20 +43,29 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
/// The current data remaining to be read /// The current data remaining to be read.
/// </summary> /// </summary>
private int currentDataRemaining; private int currentDataRemaining;
/// <summary> /// <summary>
/// Delegate to get more data once we've exhausted the current data remaining /// Delegate to get more data once we've exhausted the current data remaining.
/// </summary> /// </summary>
private readonly Func<int> getData; private readonly Func<int> getData;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class. /// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary> /// </summary>
/// <param name="innerStream">The inner raw stream</param> /// <param name="innerStream">The inner raw stream.</param>
/// <param name="getData">A delegate to get more data from the inner stream</param> public ZlibInflateStream(Stream innerStream)
: this(innerStream, GetDataNoOp)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary>
/// <param name="innerStream">The inner raw stream.</param>
/// <param name="getData">A delegate to get more data from the inner stream.</param>
public ZlibInflateStream(Stream innerStream, Func<int> getData) public ZlibInflateStream(Stream innerStream, Func<int> getData)
{ {
this.innerStream = innerStream; this.innerStream = innerStream;
@ -76,31 +85,32 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override long Length => throw new NotSupportedException(); public override long Length => throw new NotSupportedException();
/// <inheritdoc/> /// <inheritdoc/>
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
/// <summary> /// <summary>
/// Gets the compressed stream over the deframed inner stream /// Gets the compressed stream over the deframed inner stream.
/// </summary> /// </summary>
public DeflateStream CompressedStream => this.compressedStream; public DeflateStream CompressedStream { get; private set; }
/// <summary> /// <summary>
/// Adds new bytes from a frame found in the original stream /// Adds new bytes from a frame found in the original stream.
/// </summary> /// </summary>
/// <param name="bytes">blabla</param> /// <param name="bytes">The current remaining data according to the chunk length.</param>
public void AllocateNewBytes(int bytes) /// <param name="isCriticalChunk">Whether the chunk to be inflated is a critical chunk.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool AllocateNewBytes(int bytes, bool isCriticalChunk)
{ {
this.currentDataRemaining = bytes; this.currentDataRemaining = bytes;
if (this.compressedStream is null) if (this.CompressedStream is null)
{ {
this.InitializeInflateStream(); return this.InitializeInflateStream(isCriticalChunk);
} }
return true;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Flush() public override void Flush() => throw new NotSupportedException();
{
throw new NotSupportedException();
}
/// <inheritdoc/> /// <inheritdoc/>
public override int ReadByte() public override int ReadByte()
@ -114,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
if (this.currentDataRemaining == 0) if (this.currentDataRemaining == 0)
{ {
// last buffer was read in its entirety, let's make sure we don't actually have more // Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks.
this.currentDataRemaining = this.getData(); this.currentDataRemaining = this.getData();
if (this.currentDataRemaining == 0) if (this.currentDataRemaining == 0)
@ -125,32 +135,35 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
int bytesToRead = Math.Min(count, this.currentDataRemaining); int bytesToRead = Math.Min(count, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead; this.currentDataRemaining -= bytesToRead;
int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead);
long length = this.innerStream.Length; long innerStreamLength = this.innerStream.Length;
// Keep reading data until we've reached the end of the stream or filled the buffer // Keep reading data until we've reached the end of the stream or filled the buffer.
while (this.currentDataRemaining == 0 && bytesRead < count) int bytesRead = 0;
offset += totalBytesRead;
while (this.currentDataRemaining == 0 && totalBytesRead < count)
{ {
this.currentDataRemaining = this.getData(); this.currentDataRemaining = this.getData();
if (this.currentDataRemaining == 0) if (this.currentDataRemaining == 0)
{ {
return bytesRead; return totalBytesRead;
} }
offset += bytesRead; offset += bytesRead;
if (offset >= length || offset >= count) if (offset >= innerStreamLength || offset >= count)
{ {
return bytesRead; return totalBytesRead;
} }
bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining);
this.currentDataRemaining -= bytesToRead; this.currentDataRemaining -= bytesToRead;
bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); bytesRead = this.innerStream.Read(buffer, offset, bytesToRead);
totalBytesRead += bytesRead;
} }
return bytesRead; return totalBytesRead;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -181,11 +194,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (disposing) if (disposing)
{ {
// dispose managed resources // Dispose managed resources.
if (this.compressedStream != null) if (this.CompressedStream != null)
{ {
this.compressedStream.Dispose(); this.CompressedStream.Dispose();
this.compressedStream = null; this.CompressedStream = null;
} }
} }
@ -197,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true; this.isDisposed = true;
} }
private void InitializeInflateStream() private bool InitializeInflateStream(bool isCriticalChunk)
{ {
// Read the zlib header : http://tools.ietf.org/html/rfc1950 // Read the zlib header : http://tools.ietf.org/html/rfc1950
// CMF(Compression Method and flags) // CMF(Compression Method and flags)
@ -215,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.currentDataRemaining -= 2; this.currentDataRemaining -= 2;
if (cmf == -1 || flag == -1) if (cmf == -1 || flag == -1)
{ {
return; return false;
} }
if ((cmf & 0x0F) == 8) if ((cmf & 0x0F) == 8)
@ -225,14 +238,28 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (cinfo > 7) if (cinfo > 7)
{ {
// Values of CINFO above 7 are not allowed in RFC1950. if (isCriticalChunk)
// CINFO is not defined in this specification for CM not equal to 8. {
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); // Values of CINFO above 7 are not allowed in RFC1950.
// CINFO is not defined in this specification for CM not equal to 8.
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}");
}
else
{
return false;
}
} }
} }
else else
{ {
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); if (isCriticalChunk)
{
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}");
}
else
{
return false;
}
} }
// The preset dictionary. // The preset dictionary.
@ -246,7 +273,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
} }
// Initialize the deflate Stream. // Initialize the deflate Stream.
this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true);
return true;
} }
} }
} }

4
src/ImageSharp/IImage.cs

@ -1,4 +1,4 @@
// 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;
@ -11,4 +11,4 @@ namespace SixLabors.ImageSharp
public interface IImage : IImageInfo, IDisposable public interface IImage : IImageInfo, IDisposable
{ {
} }
} }

43
src/ImageSharp/Image.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -80,21 +81,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
Configuration IConfigurable.Configuration => this.Configuration; Configuration IConfigurable.Configuration => this.Configuration;
/// <summary>
/// Gets a value indicating whether the image instance is disposed.
/// </summary>
public bool IsDisposed { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (this.IsDisposed) this.Dispose(true);
{ GC.SuppressFinalize(this);
return;
}
this.IsDisposed = true;
this.DisposeImpl();
} }
/// <summary> /// <summary>
@ -109,8 +100,7 @@ namespace SixLabors.ImageSharp
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
this.EnsureNotDisposed(); this.EnsureNotDisposed();
EncodeVisitor visitor = new EncodeVisitor(encoder, stream); this.AcceptVisitor(new EncodeVisitor(encoder, stream));
this.AcceptVisitor(visitor);
} }
/// <summary> /// <summary>
@ -130,13 +120,6 @@ namespace SixLabors.ImageSharp
public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration) public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>; where TPixel2 : struct, IPixel<TPixel2>;
/// <summary>
/// Accept a <see cref="IImageVisitor"/>.
/// Implemented by <see cref="Image{TPixel}"/> invoking <see cref="IImageVisitor.Visit{TPixel}"/>
/// with the pixel type of the image.
/// </summary>
internal abstract void AcceptVisitor(IImageVisitor visitor);
/// <summary> /// <summary>
/// Update the size of the image after mutation. /// Update the size of the image after mutation.
/// </summary> /// </summary>
@ -144,9 +127,23 @@ namespace SixLabors.ImageSharp
protected void UpdateSize(Size size) => this.size = size; protected void UpdateSize(Size size) => this.size = size;
/// <summary> /// <summary>
/// Implements the Dispose logic. /// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if the image is disposed.
/// </summary>
internal abstract void EnsureNotDisposed();
/// <summary>
/// Accepts a <see cref="IImageVisitor"/>.
/// Implemented by <see cref="Image{TPixel}"/> invoking <see cref="IImageVisitor.Visit{TPixel}"/>
/// with the pixel type of the image.
/// </summary> /// </summary>
protected abstract void DisposeImpl(); /// <param name="visitor">The visitor.</param>
internal abstract void Accept(IImageVisitor visitor);
private class EncodeVisitor : IImageVisitor private class EncodeVisitor : IImageVisitor
{ {

13
src/ImageSharp/ImageExtensions.cs

@ -1,4 +1,4 @@
// 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;
@ -119,16 +119,5 @@ namespace SixLabors.ImageSharp
return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
} }
} }
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if the image is disposed.
/// </summary>
internal static void EnsureNotDisposed(this Image image)
{
if (image.IsDisposed)
{
throw new ObjectDisposedException(nameof(image), "Trying to execute an operation on a disposed image.");
}
}
} }
} }

12
src/ImageSharp/ImageFrame.cs

@ -74,7 +74,17 @@ namespace SixLabors.ImageSharp
public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height);
/// <inheritdoc /> /// <inheritdoc />
public abstract void Dispose(); public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination) internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination)
where TDestinationPixel : struct, IPixel<TDestinationPixel>; where TDestinationPixel : struct, IPixel<TDestinationPixel>;

2
src/ImageSharp/ImageFrameCollection{TPixel}.cs

@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp
this.parent.GetConfiguration(), this.parent.GetConfiguration(),
source.Size(), source.Size(),
source.Metadata.DeepClone()); source.Metadata.DeepClone());
source.CopyPixelsTo(result.PixelBuffer.Span); source.CopyPixelsTo(result.PixelBuffer.GetSpan());
return result; return result;
} }
} }

22
src/ImageSharp/ImageFrame{TPixel}.cs

@ -6,9 +6,9 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory; using SixLabors.Memory;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp
/// In all other cases it is the only frame of the image. /// In all other cases it is the only frame of the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>, IDisposable public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed; private bool isDisposed;
@ -196,20 +196,20 @@ namespace SixLabors.ImageSharp
this.UpdateSize(this.PixelBuffer.Size()); this.UpdateSize(this.PixelBuffer.Size());
} }
/// <summary> /// <inheritdoc/>
/// Disposes the object and frees resources for the Garbage Collector. protected override void Dispose(bool disposing)
/// </summary>
public override void Dispose()
{ {
if (this.isDisposed) if (this.isDisposed)
{ {
return; return;
} }
this.PixelBuffer?.Dispose(); if (disposing)
this.PixelBuffer = null; {
this.PixelBuffer?.Dispose();
this.PixelBuffer = null;
}
// Note disposing is done.
this.isDisposed = true; this.isDisposed = true;
} }
@ -218,10 +218,10 @@ namespace SixLabors.ImageSharp
if (typeof(TPixel) == typeof(TDestinationPixel)) if (typeof(TPixel) == typeof(TDestinationPixel))
{ {
Span<TPixel> dest1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(destination); Span<TPixel> dest1 = MemoryMarshal.Cast<TDestinationPixel, TPixel>(destination);
this.PixelBuffer.Span.CopyTo(dest1); this.PixelBuffer.GetSpan().CopyTo(dest1);
} }
PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.Span, destination); PixelOperations<TPixel>.Instance.To(this.Configuration, this.PixelBuffer.GetSpan(), destination);
} }
/// <inheritdoc/> /// <inheritdoc/>

13
src/ImageSharp/ImageSharp.csproj

@ -19,6 +19,15 @@
<RootNamespace>SixLabors.ImageSharp</RootNamespace> <RootNamespace>SixLabors.ImageSharp</RootNamespace>
</PropertyGroup> </PropertyGroup>
<!-- TODO: Include .NETSTANDARD2.1 when released-->
<PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp2')) ">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp2.1')) ">
<DefineConstants>$(DefineConstants);SUPPORTS_HASHCODE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' OR '$(TargetFramework)' == 'net472' "> <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' OR '$(TargetFramework)' == 'net472' ">
<DefineConstants>$(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_EXTENDED_INTRINSICS</DefineConstants>
</PropertyGroup> </PropertyGroup>
@ -27,6 +36,10 @@
<Compile Include="..\Shared\*.cs" /> <Compile Include="..\Shared\*.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\..\shared-infrastructure\**\*.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs"> <Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>

36
src/ImageSharp/Image{TPixel}.cs

@ -1,4 +1,4 @@
// 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;
@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp
public sealed class Image<TPixel> : Image public sealed class Image<TPixel> : Image
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
@ -185,19 +187,41 @@ namespace SixLabors.ImageSharp
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void DisposeImpl() => this.Frames.Dispose(); protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.Frames.Dispose();
}
this.isDisposed = true;
}
/// <inheritdoc/>
internal override void EnsureNotDisposed()
{
if (this.isDisposed)
{
throw new ObjectDisposedException("Trying to execute an operation on a disposed image.");
}
}
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <inheritdoc /> /// <inheritdoc />
internal override void AcceptVisitor(IImageVisitor visitor) internal override void Accept(IImageVisitor visitor)
{ {
this.EnsureNotDisposed(); this.EnsureNotDisposed();
visitor.Visit(this); visitor.Visit(this);
} }
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <summary> /// <summary>
/// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer.
/// </summary> /// </summary>

132
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -13,13 +13,69 @@ namespace SixLabors.ImageSharp.Memory
/// <summary> /// <summary>
/// Defines extension methods for <see cref="Buffer2D{T}"/>. /// Defines extension methods for <see cref="Buffer2D{T}"/>.
/// </summary> /// </summary>
internal static class Buffer2DExtensions public static class Buffer2DExtensions
{ {
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Span{T}"/> referencing the memory area.</returns>
public static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.GetSpan();
}
/// <summary>
/// Gets the <see cref="Memory{T}"/> holding the backing buffer of <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <typeparam name="T">The value type.</typeparam>
/// <returns>The <see cref="Memory{T}"/>.</returns>
public static Memory<T> GetMemory<T>(this Buffer2D<T> buffer)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
Guard.NotNull(buffer, nameof(buffer));
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}
/// <summary> /// <summary>
/// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace, /// Copy <paramref name="columnCount"/> columns of <paramref name="buffer"/> inplace,
/// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>. /// from positions starting at <paramref name="sourceIndex"/> to positions at <paramref name="destIndex"/>.
/// </summary> /// </summary>
public static unsafe void CopyColumns<T>( internal static unsafe void CopyColumns<T>(
this Buffer2D<T> buffer, this Buffer2D<T> buffer,
int sourceIndex, int sourceIndex,
int destIndex, int destIndex,
@ -37,7 +93,7 @@ namespace SixLabors.ImageSharp.Memory
int dOffset = destIndex * elementSize; int dOffset = destIndex * elementSize;
long count = columnCount * elementSize; long count = columnCount * elementSize;
Span<byte> span = MemoryMarshal.AsBytes(buffer.Memory.Span); Span<byte> span = MemoryMarshal.AsBytes(buffer.GetMemory().Span);
fixed (byte* ptr = span) fixed (byte* ptr = span)
{ {
@ -60,7 +116,7 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param> /// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Rectangle"/></returns> /// <returns>The <see cref="Rectangle"/></returns>
public static Rectangle FullRectangle<T>(this Buffer2D<T> buffer) internal static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
where T : struct where T : struct
{ {
return new Rectangle(0, 0, buffer.Width, buffer.Height); return new Rectangle(0, 0, buffer.Width, buffer.Height);
@ -73,11 +129,11 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param> /// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <param name="rectangle">The rectangle subarea</param> /// <param name="rectangle">The rectangle subarea</param>
/// <returns>The <see cref="BufferArea{T}"/></returns> /// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle) internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : struct => where T : struct =>
new BufferArea<T>(buffer, rectangle); new BufferArea<T>(buffer, rectangle);
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height) internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : struct => where T : struct =>
new BufferArea<T>(buffer, new Rectangle(x, y, width, height)); new BufferArea<T>(buffer, new Rectangle(x, y, width, height));
@ -87,64 +143,17 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param> /// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="BufferArea{T}"/></returns> /// <returns>The <see cref="BufferArea{T}"/></returns>
public static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer) internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
where T : struct => where T : struct =>
new BufferArea<T>(buffer); new BufferArea<T>(buffer);
public static BufferArea<T> GetAreaBetweenRows<T>(this Buffer2D<T> buffer, int minY, int maxY)
where T : struct =>
new BufferArea<T>(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
/// <summary> /// <summary>
/// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/> /// Gets a span for all the pixels in <paramref name="buffer"/> defined by <paramref name="rows"/>
/// </summary> /// </summary>
public static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows) internal static Span<T> GetMultiRowSpan<T>(this Buffer2D<T> buffer, in RowInterval rows)
where T : struct where T : struct
{ {
return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); return buffer.GetSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width);
}
/// <summary>
/// Gets a <see cref="Memory{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Memory<T> GetRowMemory<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="x">The x coordinate (position in the row)</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int x, int y)
where T : struct
{
return buffer.GetSpan().Slice((y * buffer.Width) + x, buffer.Width - x);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>
/// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetRowSpan<T>(this Buffer2D<T> buffer, int y)
where T : struct
{
return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width);
} }
/// <summary> /// <summary>
@ -153,21 +162,12 @@ namespace SixLabors.ImageSharp.Memory
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param> /// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Size{T}"/> of the buffer</returns> /// <returns>The <see cref="Size{T}"/> of the buffer</returns>
public static Size Size<T>(this Buffer2D<T> buffer) internal static Size Size<T>(this Buffer2D<T> buffer)
where T : struct where T : struct
{ {
return new Size(buffer.Width, buffer.Height); return new Size(buffer.Width, buffer.Height);
} }
/// <summary>
/// Gets a <see cref="Span{T}"/> to the backing buffer of <paramref name="buffer"/>.
/// </summary>
internal static Span<T> GetSpan<T>(this Buffer2D<T> buffer)
where T : struct
{
return buffer.MemorySource.GetSpan();
}
[Conditional("DEBUG")] [Conditional("DEBUG")]
private static void CheckColumnRegionsDoNotOverlap<T>( private static void CheckColumnRegionsDoNotOverlap<T>(
Buffer2D<T> buffer, Buffer2D<T> buffer,

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

@ -12,8 +12,12 @@ namespace SixLabors.ImageSharp.Memory
/// Represents a buffer of value type objects /// Represents a buffer of value type objects
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements. /// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary> /// </summary>
/// <remarks>
/// Before RC1, this class might be target of API changes, use it on your own risk!
/// </remarks>
/// <typeparam name="T">The value type.</typeparam> /// <typeparam name="T">The value type.</typeparam>
internal sealed class Buffer2D<T> : IDisposable // TODO: Consider moving this type to the SixLabors.Memory namespace (SixLabors.Core).
public sealed class Buffer2D<T> : IDisposable
where T : struct where T : struct
{ {
private MemorySource<T> memorySource; private MemorySource<T> memorySource;
@ -24,7 +28,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="memorySource">The buffer to wrap</param> /// <param name="memorySource">The buffer to wrap</param>
/// <param name="width">The number of elements in a row</param> /// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param> /// <param name="height">The number of rows</param>
public Buffer2D(MemorySource<T> memorySource, int width, int height) internal Buffer2D(MemorySource<T> memorySource, int width, int height)
{ {
this.memorySource = memorySource; this.memorySource = memorySource;
this.Width = width; this.Width = width;
@ -44,11 +48,7 @@ namespace SixLabors.ImageSharp.Memory
/// <summary> /// <summary>
/// Gets the backing <see cref="MemorySource{T}"/> /// Gets the backing <see cref="MemorySource{T}"/>
/// </summary> /// </summary>
public MemorySource<T> MemorySource => this.memorySource; internal MemorySource<T> MemorySource => this.memorySource;
public Memory<T> Memory => this.MemorySource.Memory;
public Span<T> Span => this.Memory.Span;
/// <summary> /// <summary>
/// Gets a reference to the element at the specified position. /// Gets a reference to the element at the specified position.
@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="x">The x coordinate (row)</param> /// <param name="x">The x coordinate (row)</param>
/// <param name="y">The y coordinate (position at row)</param> /// <param name="y">The y coordinate (position at row)</param>
/// <returns>A reference to the element.</returns> /// <returns>A reference to the element.</returns>
public ref T this[int x, int y] internal ref T this[int x, int y]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(x, this.Width, nameof(x));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
Span<T> span = this.Span; Span<T> span = this.GetSpan();
return ref span[(this.Width * y) + x]; return ref span[(this.Width * y) + x];
} }
} }
@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Memory
DebugGuard.MustBeGreaterThan(h, 0, nameof(h)); DebugGuard.MustBeGreaterThan(h, 0, nameof(h));
DebugGuard.MustBeLessThanOrEqualTo(y + h, this.Height, nameof(h)); DebugGuard.MustBeLessThanOrEqualTo(y + h, this.Height, nameof(h));
Memory<T> slice = this.Memory.Slice(y * this.Width, h * this.Width); Memory<T> slice = this.GetMemory().Slice(y * this.Width, h * this.Width);
var memory = new MemorySource<T>(slice); var memory = new MemorySource<T>(slice);
return new Buffer2D<T>(memory, this.Width, h); return new Buffer2D<T>(memory, this.Width, h);
} }
@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Memory
/// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1),
/// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2!
/// </summary> /// </summary>
public static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source) internal static void SwapOrCopyContent(Buffer2D<T> destination, Buffer2D<T> source)
{ {
MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); MemorySource<T>.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource);
SwapDimensionData(destination, source); SwapDimensionData(destination, source);

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

@ -23,10 +23,10 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea(Buffer2D<T> destinationBuffer, Rectangle rectangle) public BufferArea(Buffer2D<T> destinationBuffer, Rectangle rectangle)
{ {
ImageSharp.DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle));
ImageSharp.DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle));
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle));
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle));
this.DestinationBuffer = destinationBuffer; this.DestinationBuffer = destinationBuffer;
this.Rectangle = rectangle; this.Rectangle = rectangle;
@ -122,8 +122,8 @@ namespace SixLabors.ImageSharp.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea<T> GetSubArea(Rectangle rectangle) public BufferArea<T> GetSubArea(Rectangle rectangle)
{ {
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle));
ImageSharp.DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle));
int x = this.Rectangle.X + rectangle.X; int x = this.Rectangle.X + rectangle.X;
int y = this.Rectangle.Y + rectangle.Y; int y = this.Rectangle.Y + rectangle.Y;
@ -161,4 +161,4 @@ namespace SixLabors.ImageSharp.Memory
} }
} }
} }
} }

1
src/ImageSharp/Memory/MemoryOwnerExtensions.cs

@ -13,7 +13,6 @@ namespace SixLabors.ImageSharp.Memory
/// </summary> /// </summary>
internal static class MemoryOwnerExtensions internal static class MemoryOwnerExtensions
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer) public static Span<T> GetSpan<T>(this IMemoryOwner<T> buffer)
=> buffer.Memory.Span; => buffer.Memory.Span;

43
src/ImageSharp/Memory/RowInterval.cs

@ -10,26 +10,32 @@ namespace SixLabors.ImageSharp.Memory
/// <summary> /// <summary>
/// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/> /// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/>
/// </summary> /// </summary>
internal readonly struct RowInterval : IEquatable<RowInterval> /// <remarks>
/// Before RC1, this class might be target of API changes, use it on your own risk!
/// </remarks>
// TODO: Consider moving this type to the SixLabors.Memory namespace (SixLabors.Core).
public readonly struct RowInterval : IEquatable<RowInterval>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RowInterval"/> struct. /// Initializes a new instance of the <see cref="RowInterval"/> struct.
/// </summary> /// </summary>
/// <param name="min">The inclusive minimum row.</param>
/// <param name="max">The exclusive maximum row.</param>
public RowInterval(int min, int max) public RowInterval(int min, int max)
{ {
DebugGuard.MustBeLessThan(min, max, nameof(min)); Guard.MustBeLessThan(min, max, nameof(min));
this.Min = min; this.Min = min;
this.Max = max; this.Max = max;
} }
/// <summary> /// <summary>
/// Gets the INCLUSIVE minimum. /// Gets the inclusive minimum row.
/// </summary> /// </summary>
public int Min { get; } public int Min { get; }
/// <summary> /// <summary>
/// Gets the EXCLUSIVE maximum. /// Gets the exclusive maximum row.
/// </summary> /// </summary>
public int Max { get; } public int Max { get; }
@ -38,33 +44,48 @@ namespace SixLabors.ImageSharp.Memory
/// </summary> /// </summary>
public int Height => this.Max - this.Min; public int Height => this.Max - this.Min;
/// <summary>
/// Returns a boolean indicating whether the given two <see cref="RowInterval"/>-s are equal.
/// </summary>
/// <param name="left">The first <see cref="RowInterval"/> to compare.</param>
/// <param name="right">The second <see cref="RowInterval"/> to compare.</param>
/// <returns>True if the given <see cref="RowInterval"/>-s are equal; False otherwise.</returns>
public static bool operator ==(RowInterval left, RowInterval right) public static bool operator ==(RowInterval left, RowInterval right)
{ {
return left.Equals(right); return left.Equals(right);
} }
/// <summary>
/// Returns a boolean indicating whether the given two <see cref="RowInterval"/>-s are not equal.
/// </summary>
/// <param name="left">The first <see cref="RowInterval"/> to compare.</param>
/// <param name="right">The second <see cref="RowInterval"/> to compare.</param>
/// <returns>True if the given <see cref="RowInterval"/>-s are not equal; False otherwise.</returns>
public static bool operator !=(RowInterval left, RowInterval right) public static bool operator !=(RowInterval left, RowInterval right)
{ {
return !left.Equals(right); return !left.Equals(right);
} }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]";
public RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max);
public RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length);
public bool Equals(RowInterval other) public bool Equals(RowInterval other)
{ {
return this.Min == other.Min && this.Max == other.Max; return this.Min == other.Min && this.Max == other.Max;
} }
/// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other);
} }
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); public override int GetHashCode() => HashCode.Combine(this.Min, this.Max);
/// <inheritdoc />
public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]";
internal RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max);
internal RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length);
} }
} }

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

@ -12,9 +12,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
internal sealed class ExifTagDescriptionAttribute : Attribute internal sealed class ExifTagDescriptionAttribute : Attribute
{ {
private readonly object value;
private readonly string description;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExifTagDescriptionAttribute"/> class. /// Initializes a new instance of the <see cref="ExifTagDescriptionAttribute"/> class.
/// </summary> /// </summary>
@ -22,8 +19,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <param name="description">The description for the value of the exif tag.</param> /// <param name="description">The description for the value of the exif tag.</param>
public ExifTagDescriptionAttribute(object value, string description) public ExifTagDescriptionAttribute(object value, string description)
{ {
this.value = value;
this.description = description;
} }
/// <summary> /// <summary>

6
src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs

@ -9,17 +9,17 @@ namespace SixLabors.ImageSharp.PixelFormats
public enum PixelAlphaCompositionMode public enum PixelAlphaCompositionMode
{ {
/// <summary> /// <summary>
/// returns the destination over the source. /// Returns the destination over the source.
/// </summary> /// </summary>
SrcOver = 0, SrcOver = 0,
/// <summary> /// <summary>
/// returns the source colors. /// Returns the source colors.
/// </summary> /// </summary>
Src, Src,
/// <summary> /// <summary>
/// returns the source over the destination. /// Returns the source over the destination.
/// </summary> /// </summary>
SrcAtop, SrcAtop,

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

File diff suppressed because it is too large

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

@ -68,9 +68,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
foreach(var blender in blenders) { foreach(var blender in blenders) {
var blender_composer= $"{blender}{composer}"; var blender_composer= $"{blender}{composer}";
#> #>
internal class <#= blender_composer#> : PixelBlender<TPixel> /// <summary>
/// A pixel blender that implements the "<#= blender_composer#>" composition equation.
/// </summary>
public class <#= blender_composer#> : PixelBlender<TPixel>
{ {
/// <summary> /// <summary>
/// Gets the static instance of this blender. /// Gets the static instance of this blender.

1659
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs

File diff suppressed because it is too large

100
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt

@ -17,8 +17,6 @@
// Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when // Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when
// AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit. // AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit.
#> #>
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -26,9 +24,14 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{ {
internal static partial class PorterDuffFunctions internal static partial class PorterDuffFunctions
{ {
<# void GeneratePixelBlenders(string blender) { #> <# void GeneratePixelBlenders(string blender) { #>
/// <summary>
/// Returns the result of the "<#=blender#>Src" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -37,6 +40,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return source; return source;
} }
/// <summary>
/// Returns the result of the "<#=blender#>SrcAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -45,6 +55,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Atop(backdrop, source, <#=blender#>(backdrop, source)); return Atop(backdrop, source, <#=blender#>(backdrop, source));
} }
/// <summary>
/// Returns the result of the "<#=blender#>SrcOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -53,14 +70,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Over(backdrop, source, <#=blender#>(backdrop, source)); return Over(backdrop, source, <#=blender#>(backdrop, source));
} }
/// <summary>
/// Returns the result of the "<#=blender#>SrcIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity)
{ {
source.W *= opacity; source.W *= opacity;
return In(backdrop, source, <#=blender#>(backdrop, source)); return In(backdrop, source);
} }
/// <summary>
/// Returns the result of the "<#=blender#>SrcOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -69,12 +100,26 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Out(backdrop, source); return Out(backdrop, source);
} }
/// <summary>
/// Returns the result of the "<#=blender#>Dest" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity)
{ {
return backdrop; return backdrop;
} }
/// <summary>
/// Returns the result of the "<#=blender#>DestAtop" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -83,6 +128,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Atop(source, backdrop, <#=blender#>(source, backdrop)); return Atop(source, backdrop, <#=blender#>(source, backdrop));
} }
/// <summary>
/// Returns the result of the "<#=blender#>DestOver" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -91,14 +143,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Over(source, backdrop, <#=blender#>(source, backdrop)); return Over(source, backdrop, <#=blender#>(source, backdrop));
} }
/// <summary>
/// Returns the result of the "<#=blender#>DestIn" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity)
{ {
source.W *= opacity; source.W *= opacity;
return In(source, backdrop, <#=blender#>(source, backdrop)); return In(source, backdrop);
} }
/// <summary>
/// Returns the result of the "<#=blender#>DestOut" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -107,6 +173,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Out(source, backdrop); return Out(source, backdrop);
} }
/// <summary>
/// Returns the result of the "<#=blender#>Xor" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -115,6 +188,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Xor(backdrop, source); return Xor(backdrop, source);
} }
/// <summary>
/// Returns the result of the "<#=blender#>Clear" compositing equation.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity)
{ {
@ -127,6 +207,14 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
<# void GenerateGenericPixelBlender(string blender, string composer) { #> <# void GenerateGenericPixelBlender(string blender, string composer) { #>
/// <summary>
/// Returns the result of the "<#=blender#><#=composer#>" compositing equation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="opacity">The source opacity. Range 0..1</param>
/// <returns>The <typeparamref name="TPixel"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel <#=blender#><#=composer#><TPixel>(TPixel backdrop, TPixel source, float opacity) public static TPixel <#=blender#><#=composer#><TPixel>(TPixel backdrop, TPixel source, float opacity)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>

154
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -1,4 +1,4 @@
// 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;
@ -21,11 +21,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
internal static partial class PorterDuffFunctions internal static partial class PorterDuffFunctions
{ {
/// <summary> /// <summary>
/// Source over backdrop /// Returns the result of the "Normal" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Normal(Vector4 backdrop, Vector4 source) public static Vector4 Normal(Vector4 backdrop, Vector4 source)
{ {
@ -33,11 +33,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Source multiplied by backdrop /// Returns the result of the "Multiply" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Multiply(Vector4 backdrop, Vector4 source) public static Vector4 Multiply(Vector4 backdrop, Vector4 source)
{ {
@ -45,11 +45,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Source added to backdrop /// Returns the result of the "Add" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Add(Vector4 backdrop, Vector4 source) public static Vector4 Add(Vector4 backdrop, Vector4 source)
{ {
@ -57,11 +57,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Source subtracted from backdrop /// Returns the result of the "Subtract" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Subtract(Vector4 backdrop, Vector4 source) public static Vector4 Subtract(Vector4 backdrop, Vector4 source)
{ {
@ -69,11 +69,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Complement of source multiplied by the complement of backdrop /// Returns the result of the "Screen" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Screen(Vector4 backdrop, Vector4 source) public static Vector4 Screen(Vector4 backdrop, Vector4 source)
{ {
@ -81,11 +81,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Per element, chooses the smallest value of source and backdrop /// Returns the result of the "Darken" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Darken(Vector4 backdrop, Vector4 source) public static Vector4 Darken(Vector4 backdrop, Vector4 source)
{ {
@ -93,11 +93,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Per element, chooses the largest value of source and backdrop /// Returns the result of the "Lighten" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Lighten(Vector4 backdrop, Vector4 source) public static Vector4 Lighten(Vector4 backdrop, Vector4 source)
{ {
@ -105,11 +105,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Overlays source over backdrop /// Returns the result of the "Overlay" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Overlay(Vector4 backdrop, Vector4 source) public static Vector4 Overlay(Vector4 backdrop, Vector4 source)
{ {
@ -121,11 +121,11 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
} }
/// <summary> /// <summary>
/// Hard light effect /// Returns the result of the "HardLight" compositing equation.
/// </summary> /// </summary>
/// <param name="backdrop">Backdrop color</param> /// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">Source color</param> /// <param name="source">The source vector.</param>
/// <returns>Output color</returns> /// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 HardLight(Vector4 backdrop, Vector4 source) public static Vector4 HardLight(Vector4 backdrop, Vector4 source)
{ {
@ -145,22 +145,29 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float OverlayValueFunction(float backdrop, float source) private static float OverlayValueFunction(float backdrop, float source)
{ {
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop));
} }
/// <summary>
/// Returns the result of the "Over" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Over(Vector4 dst, Vector4 src, Vector4 blend) public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend)
{ {
// calculate weights // calculate weights
float blendW = dst.W * src.W; float blendW = destination.W * source.W;
float dstW = dst.W - blendW; float dstW = destination.W - blendW;
float srcW = src.W - blendW; float srcW = source.W - blendW;
// calculate final alpha // calculate final alpha
float alpha = dstW + srcW + blendW; float alpha = dstW + source.W;
// calculate final color // calculate final color
Vector4 color = (dst * dstW) + (src * srcW) + (blend * blendW); Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW);
// unpremultiply // unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon); color /= MathF.Max(alpha, Constants.Epsilon);
@ -169,18 +176,25 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color; return color;
} }
/// <summary>
/// Returns the result of the "Atop" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="blend">The amount to blend. Range 0..1</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend) public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend)
{ {
// calculate weights // calculate weights
float blendW = dst.W * src.W; float blendW = destination.W * source.W;
float dstW = dst.W - blendW; float dstW = destination.W - blendW;
// calculate final alpha // calculate final alpha
float alpha = dstW + blendW; float alpha = destination.W;
// calculate final color // calculate final color
Vector4 color = (dst * dstW) + (blend * blendW); Vector4 color = (destination * dstW) + (blend * blendW);
// unpremultiply // unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon); color /= MathF.Max(alpha, Constants.Epsilon);
@ -189,38 +203,56 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color; return color;
} }
/// <summary>
/// Returns the result of the "In" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend) public static Vector4 In(Vector4 destination, Vector4 source)
{ {
float alpha = dst.W * src.W; float alpha = destination.W * source.W;
Vector4 color = src * alpha; // premultiply Vector4 color = source * alpha; // premultiply
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
color.W = alpha; color.W = alpha;
return color; return color;
} }
/// <summary>
/// Returns the result of the "Out" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 dst, Vector4 src) public static Vector4 Out(Vector4 destination, Vector4 source)
{ {
float alpha = (1 - dst.W) * src.W; float alpha = (1 - destination.W) * source.W;
Vector4 color = src * alpha; // premultiply Vector4 color = source * alpha; // premultiply
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
color.W = alpha; color.W = alpha;
return color; return color;
} }
/// <summary>
/// Returns the result of the "XOr" compositing equation.
/// </summary>
/// <param name="destination">The destination vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 dst, Vector4 src) public static Vector4 Xor(Vector4 destination, Vector4 source)
{ {
float srcW = 1 - dst.W; float srcW = 1 - destination.W;
float dstW = 1 - src.W; float dstW = 1 - source.W;
float alpha = (src.W * srcW) + (dst.W * dstW); float alpha = (source.W * srcW) + (destination.W * dstW);
Vector4 color = (src.W * src * srcW) + (dst.W * dst * dstW); Vector4 color = (source.W * source * srcW) + (destination.W * destination * dstW);
// unpremultiply // unpremultiply
color /= MathF.Max(alpha, Constants.Epsilon); color /= MathF.Max(alpha, Constants.Epsilon);
@ -235,4 +267,4 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return Vector4.Zero; return Vector4.Zero;
} }
} }
} }

131
src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

@ -1,10 +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 System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.PixelFormats namespace SixLabors.ImageSharp.PixelFormats
@ -13,7 +12,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// Abstract base class for calling pixel composition functions /// Abstract base class for calling pixel composition functions
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel</typeparam> /// <typeparam name="TPixel">The type of the pixel</typeparam>
internal abstract class PixelBlender<TPixel> public abstract class PixelBlender<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
@ -23,64 +22,11 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="source">The source color.</param> /// <param name="source">The source color.</param>
/// <param name="amount"> /// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector. /// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned. /// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param> /// </param>
/// <returns>The final pixel value after composition</returns> /// <returns>The final pixel value after composition.</returns>
public abstract TPixel Blend(TPixel background, TPixel source, float amount); public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
ReadOnlySpan<float> amount);
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixel> source,
ReadOnlySpan<float> amount)
{
this.Blend<TPixel>(configuration, destination, background, source, amount);
}
/// <summary> /// <summary>
/// Blends 2 rows together /// Blends 2 rows together
/// </summary> /// </summary>
@ -90,20 +36,20 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="background">the background span</param> /// <param name="background">the background span</param>
/// <param name="source">the source span</param> /// <param name="source">the source span</param>
/// <param name="amount"> /// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector. /// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned. /// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param> /// </param>
public void Blend<TPixelSrc>( public void Blend<TPixelSrc>(
Configuration configuration, Configuration configuration,
Span<TPixel> destination, Span<TPixel> destination,
ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source, ReadOnlySpan<TPixelSrc> source,
ReadOnlySpan<float> amount) float amount)
where TPixelSrc : struct, IPixel<TPixelSrc> where TPixelSrc : struct, IPixel<TPixelSrc>
{ {
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
using (IMemoryOwner<Vector4> buffer = using (IMemoryOwner<Vector4> buffer =
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
@ -124,6 +70,25 @@ namespace SixLabors.ImageSharp.PixelFormats
} }
} }
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
public void Blend(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixel> source,
ReadOnlySpan<float> amount)
=> this.Blend<TPixel>(configuration, destination, background, source, amount);
/// <summary> /// <summary>
/// Blends 2 rows together /// Blends 2 rows together
/// </summary> /// </summary>
@ -133,20 +98,20 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="background">the background span</param> /// <param name="background">the background span</param>
/// <param name="source">the source span</param> /// <param name="source">the source span</param>
/// <param name="amount"> /// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector. /// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned. /// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param> /// </param>
public void Blend<TPixelSrc>( public void Blend<TPixelSrc>(
Configuration configuration, Configuration configuration,
Span<TPixel> destination, Span<TPixel> destination,
ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source, ReadOnlySpan<TPixelSrc> source,
float amount) ReadOnlySpan<float> amount)
where TPixelSrc : struct, IPixel<TPixelSrc> where TPixelSrc : struct, IPixel<TPixelSrc>
{ {
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer = using (IMemoryOwner<Vector4> buffer =
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
@ -166,5 +131,37 @@ namespace SixLabors.ImageSharp.PixelFormats
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale); PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.Scale);
} }
} }
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "background" is returned, at amount = 1, "source" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
ReadOnlySpan<float> amount);
} }
} }

2
src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs

@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.PixelFormats namespace SixLabors.ImageSharp.PixelFormats
{ {
/// <summary> /// <summary>
/// Unpacked pixel type containing four 16-bit floating-point values typically ranging from 0 to 1. /// Unpacked pixel type containing four 32-bit floating-point values typically ranging from 0 to 1.
/// The color components are stored in red, green, blue, and alpha order. /// The color components are stored in red, green, blue, and alpha order.
/// <para> /// <para>
/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form.

8
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs

@ -1,4 +1,4 @@
// 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.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// </summary> /// </summary>
/// <param name="options">the blending and composition to apply</param> /// <param name="options">the blending and composition to apply</param>
/// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns> /// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns>
internal PixelBlender<TPixel> GetPixelBlender(GraphicsOptions options) public PixelBlender<TPixel> GetPixelBlender(GraphicsOptions options)
{ {
return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode); return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode);
} }
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.PixelFormats
/// <param name="colorMode">The color blending mode to apply</param> /// <param name="colorMode">The color blending mode to apply</param>
/// <param name="alphaMode">The alpha composition mode to apply</param> /// <param name="alphaMode">The alpha composition mode to apply</param>
/// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns> /// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns>
internal virtual PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) public virtual PixelBlender<TPixel> GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode)
{ {
switch (alphaMode) switch (alphaMode)
{ {
@ -214,4 +214,4 @@ namespace SixLabors.ImageSharp.PixelFormats
} }
} }
} }
} }

49
src/ImageSharp/Processing/DefaultImageProcessorContext.cs → src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

@ -1,4 +1,4 @@
// 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.Advanced;
@ -29,6 +29,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
this.mutate = mutate; this.mutate = mutate;
this.source = source; this.source = source;
// Mutate acts upon the source image only.
if (this.mutate) if (this.mutate)
{ {
this.destination = source; this.destination = source;
@ -43,7 +45,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
if (!this.mutate && this.destination is null) if (!this.mutate && this.destination is null)
{ {
// Ensure we have cloned it if we are not mutating as we might have failed to register any processors // Ensure we have cloned the source if we are not mutating as we might have failed
// to register any processors.
this.destination = this.source.Clone(); this.destination = this.source.Clone();
} }
@ -53,43 +56,41 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc/> /// <inheritdoc/>
public Size GetCurrentSize() => this.GetCurrentBounds().Size; public Size GetCurrentSize() => this.GetCurrentBounds().Size;
public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) /// <inheritdoc/>
{
var processorImplementation = processor.CreatePixelSpecificProcessor<TPixel>();
return this.ApplyProcessor(processorImplementation, rectangle);
}
public IImageProcessingContext ApplyProcessor(IImageProcessor processor) public IImageProcessingContext ApplyProcessor(IImageProcessor processor)
{ {
var processorImplementation = processor.CreatePixelSpecificProcessor<TPixel>(); return this.ApplyProcessor(processor, this.GetCurrentBounds());
return this.ApplyProcessor(processorImplementation);
} }
private IImageProcessingContext ApplyProcessor(IImageProcessor<TPixel> processor, Rectangle rectangle) /// <inheritdoc/>
public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle)
{ {
if (!this.mutate && this.destination is null) if (!this.mutate && this.destination is null)
{ {
// This will only work if the first processor applied is the cloning one thus // When cloning an image we can optimize the processing pipeline by avoiding an unnecessary
// realistically for this optimization to work the resize must the first processor // interim clone if the first processor in the pipeline is a cloning processor.
// applied any only up processors will take the double data path. if (processor is ICloningImageProcessor cloningImageProcessor)
if (processor is ICloningImageProcessor<TPixel> cloningImageProcessor)
{ {
this.destination = cloningImageProcessor.CloneAndApply(this.source, rectangle); using (ICloningImageProcessor<TPixel> pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.source, rectangle))
return this; {
this.destination = pixelProcessor.CloneAndExecute();
return this;
}
} }
// Not a cloning processor? We need to create a clone to operate on.
this.destination = this.source.Clone(); this.destination = this.source.Clone();
} }
processor.Apply(this.destination, rectangle); // Standard processing pipeline.
return this; using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
} {
specificProcessor.Execute();
}
private IImageProcessingContext ApplyProcessor(IImageProcessor<TPixel> processor) return this;
{
return this.ApplyProcessor(processor, this.GetCurrentBounds());
} }
private Rectangle GetCurrentBounds() => this.destination?.Bounds() ?? this.source.Bounds(); private Rectangle GetCurrentBounds() => this.destination?.Bounds() ?? this.source.Bounds();
} }
} }

4
src/ImageSharp/Processing/Extensions/DiffuseExtensions.cs

@ -1,4 +1,4 @@
// 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;
@ -95,4 +95,4 @@ namespace SixLabors.ImageSharp.Processing.Dithering
Rectangle rectangle) => Rectangle rectangle) =>
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
} }
} }

6
src/ImageSharp/Processing/Extensions/GrayscaleExtensions.cs

@ -1,4 +1,4 @@
// 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.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing
{ {
IImageProcessor processor = mode == GrayscaleMode.Bt709 IImageProcessor processor = mode == GrayscaleMode.Bt709
? (IImageProcessor)new GrayscaleBt709Processor(amount) ? (IImageProcessor)new GrayscaleBt709Processor(amount)
: new GrayscaleBt601Processor(1F); : new GrayscaleBt601Processor(amount);
source.ApplyProcessor(processor); source.ApplyProcessor(processor);
return source; return source;
@ -111,4 +111,4 @@ namespace SixLabors.ImageSharp.Processing
return source; return source;
} }
} }
} }

44
src/ImageSharp/Processing/Extensions/LightnessExtensions.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Defines extensions that allow the alteration of the lightness component of an <see cref="Image"/>
/// using Mutate/Clone.
/// </summary>
public static class LightnessExtensions
{
/// <summary>
/// Alters the lightness component of the image.
/// </summary>
/// <remarks>
/// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged.
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results.
/// </remarks>
/// <param name="source">The image this method extends.</param>
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount)
=> source.ApplyProcessor(new LightnessProcessor(amount));
/// <summary>
/// Alters the lightness component of the image.
/// </summary>
/// <remarks>
/// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged.
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results.
/// </remarks>
/// <param name="source">The image this method extends.</param>
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount, Rectangle rectangle)
=> source.ApplyProcessor(new LightnessProcessor(amount), rectangle);
}
}

33
src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs

@ -25,8 +25,7 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation)); Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
var visitor = new ProcessingVisitor(operation, true); source.AcceptVisitor(new ProcessingVisitor(operation, true));
source.AcceptVisitor(visitor);
} }
/// <summary> /// <summary>
@ -42,8 +41,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation)); Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, true); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, true);
operation(operationsRunner); operation(operationsRunner);
} }
@ -60,8 +61,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operations, nameof(operations)); Guard.NotNull(operations, nameof(operations));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, true); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, true);
operationsRunner.ApplyProcessors(operations); operationsRunner.ApplyProcessors(operations);
} }
@ -96,8 +99,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation)); Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, false); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, false);
operation(operationsRunner); operation(operationsRunner);
return operationsRunner.GetResultImage(); return operationsRunner.GetResultImage();
} }
@ -116,8 +121,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operations, nameof(operations)); Guard.NotNull(operations, nameof(operations));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, false); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, false);
operationsRunner.ApplyProcessors(operations); operationsRunner.ApplyProcessors(operations);
return operationsRunner.GetResultImage(); return operationsRunner.GetResultImage();
} }
@ -157,8 +164,10 @@ namespace SixLabors.ImageSharp.Processing
public void Visit<TPixel>(Image<TPixel> image) public void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
IInternalImageProcessingContext<TPixel> operationsRunner = image.GetConfiguration() IInternalImageProcessingContext<TPixel> operationsRunner =
.ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate); image.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate);
this.operation(operationsRunner); this.operation(operationsRunner);
this.ResultImage = operationsRunner.GetResultImage(); this.ResultImage = operationsRunner.GetResultImage();
} }

50
src/ImageSharp/Processing/Extensions/ResizeExtensions.cs

@ -1,4 +1,4 @@
// 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.Processing.Processors.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -12,16 +12,6 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
public static class ResizeExtensions public static class ResizeExtensions
{ {
/// <summary>
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="options">The resize options.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio.</remarks>
public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options)
=> source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()));
/// <summary> /// <summary>
/// Resizes an image to the given <see cref="Size"/>. /// Resizes an image to the given <see cref="Size"/>.
/// </summary> /// </summary>
@ -128,7 +118,18 @@ namespace SixLabors.ImageSharp.Processing
Rectangle sourceRectangle, Rectangle sourceRectangle,
Rectangle targetRectangle, Rectangle targetRectangle,
bool compand) bool compand)
=> source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand), sourceRectangle); {
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.Manual,
Sampler = sampler,
TargetRectangle = targetRectangle,
Compand = compand
};
return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle);
}
/// <summary> /// <summary>
/// Resizes an image to the given width and height with the given sampler and source rectangle. /// Resizes an image to the given width and height with the given sampler and source rectangle.
@ -150,6 +151,27 @@ namespace SixLabors.ImageSharp.Processing
IResampler sampler, IResampler sampler,
Rectangle targetRectangle, Rectangle targetRectangle,
bool compand) bool compand)
=> source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand)); {
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.Manual,
Sampler = sampler,
TargetRectangle = targetRectangle,
Compand = compand
};
return Resize(source, options);
}
/// <summary>
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="options">The resize options.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio.</remarks>
public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options)
=> source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()));
} }
} }

0
src/ImageSharp/Processing/IInternalImageProcessingContext.cs → src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs

26
src/ImageSharp/Processing/KnownFilterMatrices.cs

@ -432,6 +432,32 @@ namespace SixLabors.ImageSharp.Processing
return m; return m;
} }
/// <summary>
/// Create a lightness filter matrix using the given amount.
/// </summary>
/// <remarks>
/// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged.
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results.
/// </remarks>
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
/// <returns>The <see cref="ColorMatrix"/></returns>
public static ColorMatrix CreateLightnessFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
amount--;
return new ColorMatrix
{
M11 = 1F,
M22 = 1F,
M33 = 1F,
M44 = 1F,
M51 = amount,
M52 = amount,
M53 = amount
};
}
/// <summary> /// <summary>
/// Create a sepia filter matrix using the given amount. /// Create a sepia filter matrix using the given amount.
/// The formula used matches the svg specification. <see href="http://www.w3.org/TR/filter-effects/#sepiaEquivalent"/> /// The formula used matches the svg specification. <see href="http://www.w3.org/TR/filter-effects/#sepiaEquivalent"/>

11
src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs

@ -1,8 +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.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{ {
@ -69,10 +70,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
public Color LowerColor { get; } public Color LowerColor { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => new BinaryErrorDiffusionProcessor<TPixel>(this, source, sourceRectangle);
return new BinaryErrorDiffusionProcessor<TPixel>(this);
}
} }
} }

19
src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs

@ -14,18 +14,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
/// Performs binary threshold filtering against an image using error diffusion. /// Performs binary threshold filtering against an image using error diffusion.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel> internal sealed class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly BinaryErrorDiffusionProcessor definition; private readonly BinaryErrorDiffusionProcessor definition;
public BinaryErrorDiffusionProcessor(BinaryErrorDiffusionProcessor definition) /// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="definition">The <see cref="BinaryErrorDiffusionProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BinaryErrorDiffusionProcessor(BinaryErrorDiffusionProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
TPixel upperColor = this.definition.UpperColor.ToPixel<TPixel>(); TPixel upperColor = this.definition.UpperColor.ToPixel<TPixel>();
TPixel lowerColor = this.definition.LowerColor.ToPixel<TPixel>(); TPixel lowerColor = this.definition.LowerColor.ToPixel<TPixel>();
@ -34,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
int startX = interest.X; int startX = interest.X;
@ -69,9 +76,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
} }
TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor; TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor;
diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY);
} }
} }
} }
} }
} }

11
src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs

@ -1,10 +1,11 @@
// 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 SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{ {
@ -51,10 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
public Color LowerColor { get; } public Color LowerColor { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => new BinaryOrderedDitherProcessor<TPixel>(this, source, sourceRectangle);
return new BinaryOrderedDitherProcessor<TPixel>(this);
}
} }
} }

15
src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs

@ -19,13 +19,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{ {
private readonly BinaryOrderedDitherProcessor definition; private readonly BinaryOrderedDitherProcessor definition;
public BinaryOrderedDitherProcessor(BinaryOrderedDitherProcessor definition) /// <summary>
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="definition">The <see cref="BinaryErrorDiffusionProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BinaryOrderedDitherProcessor(BinaryOrderedDitherProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
IOrderedDither dither = this.definition.Dither; IOrderedDither dither = this.definition.Dither;
TPixel upperColor = this.definition.UpperColor.ToPixel<TPixel>(); TPixel upperColor = this.definition.UpperColor.ToPixel<TPixel>();
@ -33,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
int startX = interest.X; int startX = interest.X;
@ -72,4 +79,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
} }
} }
} }
} }

11
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Binarization namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{ {
@ -49,10 +50,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
public Color LowerColor { get; } public Color LowerColor { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => new BinaryThresholdProcessor<TPixel>(this, source, sourceRectangle);
return new BinaryThresholdProcessor<TPixel>(this);
}
} }
} }

21
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

@ -4,7 +4,7 @@
using System; using System;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -19,21 +19,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
{ {
private readonly BinaryThresholdProcessor definition; private readonly BinaryThresholdProcessor definition;
public BinaryThresholdProcessor(BinaryThresholdProcessor definition) /// <summary>
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor{TPixel}"/> class.
/// </summary>
/// <param name="definition">The <see cref="BinaryThresholdProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BinaryThresholdProcessor(BinaryThresholdProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply( protected override void OnFrameApply(ImageFrame<TPixel> source)
ImageFrame<TPixel> source,
Rectangle sourceRectangle,
Configuration configuration)
{ {
byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F);
TPixel upper = this.definition.UpperColor.ToPixel<TPixel>(); TPixel upper = this.definition.UpperColor.ToPixel<TPixel>();
TPixel lower = this.definition.LowerColor.ToPixel<TPixel>(); TPixel lower = this.definition.LowerColor.ToPixel<TPixel>();
Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
@ -67,4 +74,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
}); });
} }
} }
} }

135
src/ImageSharp/Processing/Processors/CloningImageProcessor.cs

@ -1,141 +1,22 @@
// 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 SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Allows the application of processing algorithms to a clone of the original image. /// The base class for all cloning image processors.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public abstract class CloningImageProcessor : ICloningImageProcessor
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> CloneAndApply(Image<TPixel> source, Rectangle sourceRectangle) public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ where TPixel : struct, IPixel<TPixel>;
try
{
Image<TPixel> clone = this.CreateDestination(source, sourceRectangle);
if (clone.Frames.Count != source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
Configuration configuration = source.GetConfiguration();
this.BeforeImageApply(source, clone, sourceRectangle);
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> clonedFrame = clone.Frames[i];
this.BeforeFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration);
this.OnFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration);
this.AfterFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration);
}
this.AfterImageApply(source, clone, sourceRectangle);
return clone;
}
#if DEBUG
catch (Exception)
{
throw;
#else
catch (Exception ex)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
#endif
}
}
/// <inheritdoc/> /// <inheritdoc/>
public void Apply(Image<TPixel> source, Rectangle sourceRectangle) IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ => this.CreatePixelSpecificCloningProcessor(source, sourceRectangle);
using (Image<TPixel> cloned = this.CloneAndApply(source, sourceRectangle))
{
// we now need to move the pixel data/size data from one image base to another
if (cloned.Frames.Count != source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
source.SwapOrCopyPixelsBuffersFrom(cloned);
}
}
/// <summary>
/// Generates a deep clone of the source image that operations should be applied to.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">The source rectangle.</param>
/// <returns>The cloned image.</returns>
protected virtual Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
return source.Clone();
}
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void BeforeImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle)
{
}
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
/// <param name="configuration">The configuration.</param>
protected virtual void BeforeFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}" /> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
/// <param name="configuration">The configuration.</param>
protected abstract void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration);
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
/// <param name="configuration">The configuration.</param>
protected virtual void AfterFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
}
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void AfterImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle)
{
}
} }
} }

196
src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

@ -0,0 +1,196 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// The base class for all pixel specific cloning image processors.
/// Allows the application of processing algorithms to the image.
/// The image is cloned before operating upon and the buffers swapped upon completion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
protected CloningImageProcessor(Image<TPixel> source, Rectangle sourceRectangle)
{
this.Source = source;
this.SourceRectangle = sourceRectangle;
this.Configuration = this.Source.GetConfiguration();
}
/// <summary>
/// Gets The source <see cref="Image{TPixel}"/> for the current processor instance.
/// </summary>
protected Image<TPixel> Source { get; }
/// <summary>
/// Gets The source area to process for the current processor instance.
/// </summary>
protected Rectangle SourceRectangle { get; }
/// <summary>
/// Gets the <see cref="Configuration"/> instance to use when performing operations.
/// </summary>
protected Configuration Configuration { get; }
/// <inheritdoc/>
Image<TPixel> ICloningImageProcessor<TPixel>.CloneAndExecute()
{
try
{
Image<TPixel> clone = this.CreateTarget();
this.CheckFrameCount(this.Source, clone);
Configuration configuration = this.Source.GetConfiguration();
this.BeforeImageApply(clone);
for (int i = 0; i < this.Source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = this.Source.Frames[i];
ImageFrame<TPixel> clonedFrame = clone.Frames[i];
this.BeforeFrameApply(sourceFrame, clonedFrame);
this.OnFrameApply(sourceFrame, clonedFrame);
this.AfterFrameApply(sourceFrame, clonedFrame);
}
this.AfterImageApply(clone);
return clone;
}
#if DEBUG
catch (Exception)
{
throw;
#else
catch (Exception ex)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
#endif
}
}
/// <inheritdoc/>
void IImageProcessor<TPixel>.Execute()
{
// Create an interim clone of the source image to operate on.
// Doing this allows for the application of transforms that will alter
// the dimensions of the image.
Image<TPixel> clone = default;
try
{
clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute();
// We now need to move the pixel data/size data from the clone to the source.
this.CheckFrameCount(this.Source, clone);
this.Source.SwapOrCopyPixelsBuffersFrom(clone);
}
finally
{
// Dispose of the clone now that we have swapped the pixel/size data.
clone?.Dispose();
}
}
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets the size of the target image.
/// </summary>
/// <returns>The <see cref="Size"/>.</returns>
protected abstract Size GetTargetSize();
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
protected virtual void BeforeImageApply(Image<TPixel> destination)
{
}
/// <summary>
/// This method is called before the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
protected virtual void BeforeFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}" /> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
protected abstract void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination);
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
protected virtual void AfterFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
}
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
protected virtual void AfterImageApply(Image<TPixel> destination)
{
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{
}
private Image<TPixel> CreateTarget()
{
Image<TPixel> source = this.Source;
Size targetSize = this.GetTargetSize();
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
targetSize.Width,
targetSize.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), frames);
}
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)
{
if (a.Frames.Count != b.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
}
}
}

5
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
@ -112,10 +113,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public BokehBlurExecutionMode ExecutionMode { get; } public BokehBlurExecutionMode ExecutionMode { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new BokehBlurProcessor<TPixel>(this); return new BokehBlurProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

25
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

@ -8,8 +8,8 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters;
@ -75,7 +75,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="BokehBlurProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="BokehBlurProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param> /// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param>
public BokehBlurProcessor(BokehBlurProcessor definition) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BokehBlurProcessor(BokehBlurProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.radius = definition.Radius; this.radius = definition.Radius;
this.kernelSize = (this.radius * 2) + 1; this.kernelSize = (this.radius * 2) + 1;
@ -271,36 +274,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
// Preliminary gamma highlight pass // Preliminary gamma highlight pass
this.ApplyGammaExposure(source.PixelBuffer, sourceRectangle, configuration); this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration);
// Create a 0-filled buffer to use to store the result of the component convolutions // Create a 0-filled buffer to use to store the result of the component convolutions
using (Buffer2D<Vector4> processing = configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean)) using (Buffer2D<Vector4> processing = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean))
{ {
if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage) if (this.executionMode == BokehBlurExecutionMode.PreferLowMemoryUsage)
{ {
// Memory usage priority: allocate a shared buffer and execute the second convolution in sequential mode // Memory usage priority: allocate a shared buffer and execute the second convolution in sequential mode
using (Buffer2D<ComplexVector4> buffer = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Width, source.Height + this.radius)) using (Buffer2D<ComplexVector4> buffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Width, source.Height + this.radius))
using (Buffer2D<ComplexVector4> firstPassBuffer = buffer.Slice(this.radius, source.Height)) using (Buffer2D<ComplexVector4> firstPassBuffer = buffer.Slice(this.radius, source.Height))
using (Buffer2D<ComplexVector4> secondPassBuffer = buffer.Slice(0, source.Height)) using (Buffer2D<ComplexVector4> secondPassBuffer = buffer.Slice(0, source.Height))
{ {
this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassBuffer, secondPassBuffer); this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processing, firstPassBuffer, secondPassBuffer);
} }
} }
else else
{ {
// Performance priority: allocate two independent buffers and execute both convolutions in parallel mode // Performance priority: allocate two independent buffers and execute both convolutions in parallel mode
using (Buffer2D<ComplexVector4> firstPassValues = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size())) using (Buffer2D<ComplexVector4> firstPassValues = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
using (Buffer2D<ComplexVector4> secondPassBuffer = configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size())) using (Buffer2D<ComplexVector4> secondPassBuffer = this.Configuration.MemoryAllocator.Allocate2D<ComplexVector4>(source.Size()))
{ {
this.OnFrameApplyCore(source, sourceRectangle, configuration, processing, firstPassValues, secondPassBuffer); this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processing, firstPassValues, secondPassBuffer);
} }
} }
// Apply the inverse gamma exposure pass, and write the final pixel data // Apply the inverse gamma exposure pass, and write the final pixel data
this.ApplyInverseGammaExposure(source.PixelBuffer, processing, sourceRectangle, configuration); this.ApplyInverseGammaExposure(source.PixelBuffer, processing, this.SourceRectangle, this.Configuration);
} }
} }

5
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
@ -40,10 +41,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public int Radius { get; } public int Radius { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new BoxBlurProcessor<TPixel>(this); return new BoxBlurProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

22
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs

@ -20,7 +20,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="BoxBlurProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="BoxBlurProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param> /// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param>
public BoxBlurProcessor(BoxBlurProcessor definition) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public BoxBlurProcessor(BoxBlurProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.definition = definition; this.definition = definition;
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
@ -39,14 +42,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix<float> KernelY { get; } public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply( protected override void OnFrameApply(ImageFrame<TPixel> source)
ImageFrame<TPixel> source, {
Rectangle sourceRectangle, using (var processor = new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
Configuration configuration) => {
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false).Apply( processor.Apply(source);
source, }
sourceRectangle, }
configuration);
/// <summary> /// <summary>
/// Create a 1 dimensional Box kernel. /// Create a 1 dimensional Box kernel.
@ -60,4 +62,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
return kernel; return kernel;
} }
} }
} }

29
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -5,8 +5,8 @@ using System;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -26,7 +26,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param> /// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param> /// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public Convolution2DProcessor(in DenseMatrix<float> kernelX, in DenseMatrix<float> kernelY, bool preserveAlpha) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public Convolution2DProcessor(
in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX; this.KernelX = kernelX;
@ -50,16 +58,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool PreserveAlpha { get; } public bool PreserveAlpha { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply( protected override void OnFrameApply(ImageFrame<TPixel> source)
ImageFrame<TPixel> source,
Rectangle sourceRectangle,
Configuration configuration)
{ {
DenseMatrix<float> matrixY = this.KernelY; DenseMatrix<float> matrixY = this.KernelY;
DenseMatrix<float> matrixX = this.KernelX; DenseMatrix<float> matrixX = this.KernelX;
bool preserveAlpha = this.PreserveAlpha; bool preserveAlpha = this.PreserveAlpha;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
int startX = interest.X; int startX = interest.X;
@ -67,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int maxY = endY - 1; int maxY = endY - 1;
int maxX = endX - 1; int maxX = endX - 1;
using (Buffer2D<TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height)) using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height))
{ {
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
@ -76,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle, workingRectangle,
configuration, this.Configuration,
(rows, vectorBuffer) => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span; Span<Vector4> vectorSpan = vectorBuffer.Span;
@ -86,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan);
if (preserveAlpha) if (preserveAlpha)
{ {
@ -123,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan);
} }
}); });
@ -131,4 +136,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
} }
} }
} }

24
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -1,11 +1,12 @@
// 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.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -25,10 +26,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param> /// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param> /// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public Convolution2PassProcessor( public Convolution2PassProcessor(
in DenseMatrix<float> kernelX, in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY, in DenseMatrix<float> kernelY,
bool preserveAlpha) bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.KernelX = kernelX; this.KernelX = kernelX;
this.KernelY = kernelY; this.KernelY = kernelY;
@ -51,13 +57,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool PreserveAlpha { get; } public bool PreserveAlpha { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using (Buffer2D<TPixel> firstPassPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size())) using (Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()))
{ {
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration); this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, this.Configuration);
this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration); this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, this.Configuration);
} }
} }
@ -144,4 +150,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}); });
} }
} }
} }

28
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -1,11 +1,12 @@
// 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.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -24,7 +25,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param> /// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param> /// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public ConvolutionProcessor(in DenseMatrix<float> kernelXY, bool preserveAlpha) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public ConvolutionProcessor(
in DenseMatrix<float> kernelXY,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.KernelXY = kernelXY; this.KernelXY = kernelXY;
this.PreserveAlpha = preserveAlpha; this.PreserveAlpha = preserveAlpha;
@ -41,12 +49,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool PreserveAlpha { get; } public bool PreserveAlpha { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
DenseMatrix<float> matrix = this.KernelXY; DenseMatrix<float> matrix = this.KernelXY;
bool preserveAlpha = this.PreserveAlpha; bool preserveAlpha = this.PreserveAlpha;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
int startY = interest.Y; int startY = interest.Y;
int endY = interest.Bottom; int endY = interest.Bottom;
int startX = interest.X; int startX = interest.X;
@ -54,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int maxY = endY - 1; int maxY = endY - 1;
int maxX = endX - 1; int maxX = endX - 1;
using (Buffer2D<TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size())) using (Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()))
{ {
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
@ -63,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle, workingRectangle,
configuration, this.Configuration,
(rows, vectorBuffer) => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span; Span<Vector4> vectorSpan = vectorBuffer.Span;
@ -73,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan);
if (preserveAlpha) if (preserveAlpha)
{ {
@ -108,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan);
} }
}); });
@ -116,4 +124,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
} }
} }
} }

33
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

@ -1,4 +1,4 @@
// 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.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -21,7 +21,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param> /// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param> /// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
internal EdgeDetector2DProcessor(in DenseMatrix<float> kernelX, in DenseMatrix<float> kernelY, bool grayscale) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
internal EdgeDetector2DProcessor(
in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY,
bool grayscale,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX; this.KernelX = kernelX;
@ -41,17 +49,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool Grayscale { get; } public bool Grayscale { get; }
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
=> new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY, true).Apply(source, sourceRectangle, configuration);
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void BeforeImageApply()
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
}
base.BeforeImageApply();
}
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using (var processor = new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
} }
} }
} }
} }

39
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -1,4 +1,4 @@
// 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;
@ -6,8 +6,8 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced.ParallelUtils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors.Filters;
@ -27,7 +27,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
/// <param name="kernels">Gets the kernels to use.</param> /// <param name="kernels">Gets the kernels to use.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param> /// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
internal EdgeDetectorCompassProcessor(CompassKernels kernels, bool grayscale) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
internal EdgeDetectorCompassProcessor(CompassKernels kernels, bool grayscale, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.Grayscale = grayscale; this.Grayscale = grayscale;
this.Kernels = kernels; this.Kernels = kernels;
@ -38,23 +41,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private bool Grayscale { get; } private bool Grayscale { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void BeforeImageApply()
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply();
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
DenseMatrix<float>[] kernels = this.Kernels.Flatten(); DenseMatrix<float>[] kernels = this.Kernels.Flatten();
int startY = sourceRectangle.Y; int startY = this.SourceRectangle.Y;
int endY = sourceRectangle.Bottom; int endY = this.SourceRectangle.Bottom;
int startX = sourceRectangle.X; int startX = this.SourceRectangle.X;
int endX = sourceRectangle.Right; int endX = this.SourceRectangle.Right;
// Align start/end positions. // Align start/end positions.
int minX = Math.Max(0, startX); int minX = Math.Max(0, startX);
@ -65,7 +70,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// we need a clean copy for each pass to start from // we need a clean copy for each pass to start from
using (ImageFrame<TPixel> cleanCopy = source.Clone()) using (ImageFrame<TPixel> cleanCopy = source.Clone())
{ {
new ConvolutionProcessor<TPixel>(kernels[0], true).Apply(source, sourceRectangle, configuration); using (var processor = new ConvolutionProcessor<TPixel>(kernels[0], true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
if (kernels.Length == 1) if (kernels.Length == 1)
{ {
@ -94,14 +102,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
using (ImageFrame<TPixel> pass = cleanCopy.Clone()) using (ImageFrame<TPixel> pass = cleanCopy.Clone())
{ {
new ConvolutionProcessor<TPixel>(kernels[i], true).Apply(pass, sourceRectangle, configuration); using (var processor = new ConvolutionProcessor<TPixel>(kernels[i], true, this.Source, this.SourceRectangle))
{
processor.Apply(pass);
}
Buffer2D<TPixel> passPixels = pass.PixelBuffer; Buffer2D<TPixel> passPixels = pass.PixelBuffer;
Buffer2D<TPixel> targetPixels = source.PixelBuffer; Buffer2D<TPixel> targetPixels = source.PixelBuffer;
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
workingRect, workingRect,
configuration, this.Configuration,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
@ -132,4 +143,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
} }
} }
} }

7
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool Grayscale { get; } public bool Grayscale { get; }
/// <inheritdoc /> /// <inheritdoc />
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

22
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

@ -20,7 +20,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param> /// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param> /// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
public EdgeDetectorProcessor(in DenseMatrix<float> kernelXY, bool grayscale) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The target area to process for the current processor instance.</param>
public EdgeDetectorProcessor(in DenseMatrix<float> kernelXY, bool grayscale, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
this.KernelXY = kernelXY; this.KernelXY = kernelXY;
this.Grayscale = grayscale; this.Grayscale = grayscale;
@ -34,16 +37,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix<float> KernelXY { get; } public DenseMatrix<float> KernelXY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void BeforeImageApply()
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply();
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
=> new ConvolutionProcessor<TPixel>(this.KernelXY, true).Apply(source, sourceRectangle, configuration); {
using (var processor = new ConvolutionProcessor<TPixel>(this.KernelXY, true, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
} }
} }

9
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
@ -70,10 +71,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public int Radius { get; } public int Radius { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new GaussianBlurProcessor<TPixel>(this); return new GaussianBlurProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

22
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -18,7 +18,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param> /// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
public GaussianBlurProcessor(GaussianBlurProcessor definition) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianBlurProcessor(GaussianBlurProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
@ -36,13 +39,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix<float> KernelY { get; } public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply( protected override void OnFrameApply(ImageFrame<TPixel> source)
ImageFrame<TPixel> source, {
Rectangle sourceRectangle, using (var processor = new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
Configuration configuration) => {
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false).Apply( processor.Apply(source);
source, }
sourceRectangle, }
configuration);
} }
} }

9
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
@ -70,10 +71,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public int Radius { get; } public int Radius { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new GaussianSharpenProcessor<TPixel>(this); return new GaussianSharpenProcessor<TPixel>(this, source, sourceRectangle);
} }
} }
} }

16
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -18,7 +18,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param> /// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
public GaussianSharpenProcessor(GaussianSharpenProcessor definition) /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianSharpenProcessor(GaussianSharpenProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{ {
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
@ -36,7 +39,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix<float> KernelY { get; } public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) protected override void OnFrameApply(ImageFrame<TPixel> source)
=> new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration); {
using (var processor = new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle))
{
processor.Apply(source);
}
}
} }
} }

12
src/ImageSharp/Processing/Processors/Convolution/KayyaliProcessor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,12 +21,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetector2DProcessor<TPixel>( return new EdgeDetector2DProcessor<TPixel>(
KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliX,
KayyaliKernels.KayyaliY, KayyaliKernels.KayyaliY,
this.Grayscale); this.Grayscale,
source,
sourceRectangle);
} }
} }
} }

10
src/ImageSharp/Processing/Processors/Convolution/KirschProcessor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetectorCompassProcessor<TPixel>(new KirschKernels(), this.Grayscale); return new EdgeDetectorCompassProcessor<TPixel>(new KirschKernels(), this.Grayscale, source, sourceRectangle);
} }
} }
} }

8
src/ImageSharp/Processing/Processors/Convolution/Laplacian3x3Processor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetectorProcessor<TPixel>(LaplacianKernels.Laplacian3x3, this.Grayscale); return new EdgeDetectorProcessor<TPixel>(LaplacianKernels.Laplacian3x3, this.Grayscale, source, sourceRectangle);
} }
} }
} }

10
src/ImageSharp/Processing/Processors/Convolution/Laplacian5x5Processor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetectorProcessor<TPixel>(LaplacianKernels.Laplacian5x5, this.Grayscale); return new EdgeDetectorProcessor<TPixel>(LaplacianKernels.Laplacian5x5, this.Grayscale, source, sourceRectangle);
} }
} }
} }

10
src/ImageSharp/Processing/Processors/Convolution/LaplacianOfGaussianProcessor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetectorProcessor<TPixel>(LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale); return new EdgeDetectorProcessor<TPixel>(LaplacianKernels.LaplacianOfGaussianXY, this.Grayscale, source, sourceRectangle);
} }
} }
} }

10
src/ImageSharp/Processing/Processors/Convolution/PrewittProcessor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetector2DProcessor<TPixel>(PrewittKernels.PrewittX, PrewittKernels.PrewittY, this.Grayscale); return new EdgeDetector2DProcessor<TPixel>(PrewittKernels.PrewittX, PrewittKernels.PrewittY, this.Grayscale, source, sourceRectangle);
} }
} }
} }

15
src/ImageSharp/Processing/Processors/Convolution/RobertsCrossProcessor.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
@ -19,9 +21,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ {
return new EdgeDetector2DProcessor<TPixel>(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY, this.Grayscale); return new EdgeDetector2DProcessor<TPixel>(
RobertsCrossKernels.RobertsCrossX,
RobertsCrossKernels.RobertsCrossY,
this.Grayscale,
source,
sourceRectangle);
} }
} }
} }

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

Loading…
Cancel
Save