diff --git a/.editorconfig b/.editorconfig
index b0d0662bf..0e4883082 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -339,6 +339,7 @@ csharp_space_between_square_brackets = false
# warn when using var for built-in types,
# warn when using var when the type is not apparent, and
# warn when not using var when the type is apparent
+# warn when using simplified "using" declaration
###############################################################################
[*.cs]
csharp_prefer_braces = true:silent
@@ -367,6 +368,6 @@ csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-csharp_style_var_for_built_in_types = false:warning
-csharp_style_var_elsewhere = false:warning
+csharp_style_var_for_built_in_types = never
csharp_style_var_when_type_is_apparent = true:warning
+csharp_style_var_elsewhere = false:warning
diff --git a/.gitattributes b/.gitattributes
index dd6625081..66aaca373 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -96,22 +96,22 @@
*.gif binary
*.jpg binary
*.ktx binary
+*.otf binary
*.pbm binary
*.pdf binary
*.png binary
*.ppt binary
*.pptx binary
*.pvr binary
-*.ttf binary
*.snk binary
*.tga binary
+*.ttc binary
*.ttf binary
*.woff binary
*.woff2 binary
*.xls binary
*.xlsx binary
-
###############################################################################
# Set explicit file behavior to:
# diff as plain text
diff --git a/Directory.Build.props b/Directory.Build.props
index def231a7a..2f9863561 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -95,16 +95,16 @@
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json;
- 002400000c8000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97
+ 00240000048000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97
true
+ true
-
-
+
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 40878c575..f1d4afef4 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -334,6 +334,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingS
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2aa31a1f-142c-43f4-8687-09abca4b3a26}*SharedItemsImports = 5
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/codecov.yml b/codecov.yml
index 3941f7ff9..833fc0a51 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -5,3 +5,7 @@ codecov:
# https://github.com/codecov/support/issues/363
# https://docs.codecov.io/docs/comparing-commits
allow_coverage_offsets: true
+
+ # Avoid Report Expired
+ # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports
+ max_report_age: off
diff --git a/shared-infrastructure b/shared-infrastructure
index 36b2d55f5..ea561c249 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5
+Subproject commit ea561c249ba86352fe3b69e612b8072f3652eacb
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 5e3f9b061..a78a75d42 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -27,14 +27,13 @@
-
-
-
+
-
-
-
-
+
+
+
+
+
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 68d4f8949..d7171aa0f 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -52,4 +52,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index d810296d6..0273f02f5 100644
--- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
+++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Advanced
=> GetConfiguration((IConfigurationProvider)source);
///
- /// Gets the configuration .
+ /// Gets the configuration.
///
/// The source image
/// Returns the bounds of the image
@@ -48,15 +49,58 @@ namespace SixLabors.ImageSharp.Advanced
=> source?.Configuration ?? Configuration.Default;
///
- /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
- /// stored in row major order.
+ /// Gets the representation of the pixels as a containing the backing pixel data of the image
+ /// stored in row major order, as a list of contiguous blocks in the source image's pixel format.
///
+ /// The source image.
/// The type of the pixel.
- /// The source.
+ /// The .
+ ///
+ /// Certain Image Processors may invalidate the returned and all it's buffers,
+ /// therefore it's not recommended to mutate the image while holding a reference to it's .
+ ///
+ public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source)
+ where TPixel : unmanaged, IPixel
+ => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source));
+
+ ///
+ /// Gets the representation of the pixels as a containing the backing pixel data of the image
+ /// stored in row major order, as a list of contiguous blocks in the source image's pixel format.
+ ///
+ /// The source image.
+ /// The type of the pixel.
+ /// The .
+ ///
+ /// Certain Image Processors may invalidate the returned and all it's buffers,
+ /// therefore it's not recommended to mutate the image while holding a reference to it's .
+ ///
+ public static IMemoryGroup GetPixelMemoryGroup(this Image source)
+ where TPixel : unmanaged, IPixel
+ => source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
+
+ ///
+ /// Gets the representation of the pixels as a in the source image's pixel format
+ /// stored in row major order, if the backing buffer is contiguous.
+ ///
+ /// The type of the pixel.
+ /// The source image.
/// The
+ /// Thrown when the backing buffer is discontiguous.
+ [Obsolete(
+ @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span GetPixelSpan(this ImageFrame source)
- where TPixel : struct, IPixel
- => source.GetPixelMemory().Span;
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(source, nameof(source));
+
+ IMemoryGroup mg = source.GetPixelMemoryGroup();
+ if (mg.Count > 1)
+ {
+ throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!");
+ }
+
+ return mg.Single().Span;
+ }
///
/// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
@@ -65,9 +109,16 @@ namespace SixLabors.ImageSharp.Advanced
/// The type of the pixel.
/// The source.
/// The
+ /// Thrown when the backing buffer is discontiguous.
+ [Obsolete(
+ @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span GetPixelSpan(this Image source)
- where TPixel : struct, IPixel
- => source.Frames.RootFrame.GetPixelSpan();
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(source, nameof(source));
+
+ return source.Frames.RootFrame.GetPixelSpan();
+ }
///
/// Gets the representation of the pixels as a of contiguous memory
@@ -78,8 +129,14 @@ namespace SixLabors.ImageSharp.Advanced
/// The row.
/// The
public static Span GetPixelRowSpan(this ImageFrame source, int rowIndex)
- where TPixel : struct, IPixel
- => source.PixelBuffer.GetRowSpan(rowIndex);
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
+
+ return source.PixelBuffer.GetRowSpan(rowIndex);
+ }
///
/// Gets the representation of the pixels as of of contiguous memory
@@ -90,59 +147,13 @@ namespace SixLabors.ImageSharp.Advanced
/// The row.
/// The
public static Span GetPixelRowSpan(this Image source, int rowIndex)
- where TPixel : struct, IPixel
- => source.Frames.RootFrame.GetPixelRowSpan(rowIndex);
-
- ///
- /// Returns a reference to the 0th element of the Pixel buffer,
- /// allowing direct manipulation of pixel data through unsafe operations.
- /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
- ///
- /// The Pixel format.
- /// The source image frame
- /// A pinnable reference the first root of the pixel buffer.
- [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
- public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source)
- where TPixel : struct, IPixel
- => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source);
-
- ///
- /// Returns a reference to the 0th element of the Pixel buffer,
- /// allowing direct manipulation of pixel data through unsafe operations.
- /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order.
- ///
- /// The Pixel format.
- /// The source image
- /// A pinnable reference the first root of the pixel buffer.
- [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
- public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source)
- where TPixel : struct, IPixel
- => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
-
- ///
- /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
- /// stored in row major order.
- ///
- /// The Pixel format.
- /// The source
- /// The
- internal static Memory GetPixelMemory(this ImageFrame source)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
- return source.PixelBuffer.MemorySource.Memory;
- }
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
- ///
- /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format
- /// stored in row major order.
- ///
- /// The Pixel format.
- /// The source
- /// The
- internal static Memory GetPixelMemory(this Image source)
- where TPixel : struct, IPixel
- {
- return source.Frames.RootFrame.GetPixelMemory();
+ return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
}
///
@@ -153,9 +164,15 @@ namespace SixLabors.ImageSharp.Advanced
/// The source.
/// The row.
/// The
- internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex)
- where TPixel : struct, IPixel
- => source.PixelBuffer.GetRowMemory(rowIndex);
+ public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
+
+ return source.PixelBuffer.GetSafeRowMemory(rowIndex);
+ }
///
/// Gets the representation of the pixels as of of contiguous memory
@@ -165,9 +182,15 @@ namespace SixLabors.ImageSharp.Advanced
/// The source.
/// The row.
/// The
- internal static Memory GetPixelRowMemory(this Image source, int rowIndex)
- where TPixel : struct, IPixel
- => source.Frames.RootFrame.GetPixelRowMemory(rowIndex);
+ public static Memory GetPixelRowMemory(this Image source, int rowIndex)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(source, nameof(source));
+ Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
+ Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
+
+ return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex);
+ }
///
/// Gets the assigned to 'source'.
@@ -176,15 +199,5 @@ namespace SixLabors.ImageSharp.Advanced
/// Returns the configuration.
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
=> GetConfiguration(source).MemoryAllocator;
-
- ///
- /// Returns a reference to the 0th element of the Pixel buffer.
- /// Such a reference can be used for pinning but must never be dereferenced.
- ///
- /// The source image frame
- /// A reference to the element.
- private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source)
- where TPixel : struct, IPixel
- => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan());
}
}
diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index 142ea3f3e..23ae62c7a 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -1,12 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@@ -77,11 +79,12 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The pixel format.
private static void Seed()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// This is we actually call all the individual methods you need to seed.
AotCompileOctreeQuantizer();
AotCompileWuQuantizer();
+ AotCompilePaletteQuantizer();
AotCompileDithering();
AotCompilePixelOperations();
@@ -107,11 +110,12 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The pixel format.
private static void AotCompileOctreeQuantizer()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
- using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer(false)))
+ using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer().Options))
{
- test.AotGetPalette();
+ var frame = new ImageFrame(Configuration.Default, 1, 1);
+ test.QuantizeFrame(frame, frame.Bounds());
}
}
@@ -120,12 +124,26 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The pixel format.
private static void AotCompileWuQuantizer()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
- using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer(false)))
+ using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer().Options))
{
- test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1));
- test.AotGetPalette();
+ var frame = new ImageFrame(Configuration.Default, 1, 1);
+ test.QuantizeFrame(frame, frame.Bounds());
+ }
+ }
+
+ ///
+ /// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS.
+ ///
+ /// The pixel format.
+ private static void AotCompilePaletteQuantizer()
+ where TPixel : unmanaged, IPixel
+ {
+ using (var test = (PaletteFrameQuantizer)new PaletteQuantizer(Array.Empty()).CreateFrameQuantizer(Configuration.Default))
+ {
+ var frame = new ImageFrame(Configuration.Default, 1, 1);
+ test.QuantizeFrame(frame, frame.Bounds());
}
}
@@ -134,13 +152,15 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The pixel format.
private static void AotCompileDithering()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
- var test = new FloydSteinbergDiffuser();
+ ErrorDither errorDither = ErrorDither.FloydSteinberg;
+ OrderedDither orderedDither = OrderedDither.Bayer2x2;
TPixel pixel = default;
using (var image = new ImageFrame(Configuration.Default, 1, 1))
{
- test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0);
+ errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0);
+ orderedDither.Dither(pixel, 0, 0, 0, 0);
}
}
@@ -151,7 +171,7 @@ namespace SixLabors.ImageSharp.Advanced
/// The image encoder to seed.
/// The pixel format.
private static void AotCodec(IImageDecoder decoder, IImageEncoder encoder)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
try
{
@@ -175,7 +195,7 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The pixel format.
private static void AotCompilePixelOperations()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var pixelOp = new PixelOperations();
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);
diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs
index ba8b13e2e..079db42c0 100644
--- a/src/ImageSharp/Advanced/IImageVisitor.cs
+++ b/src/ImageSharp/Advanced/IImageVisitor.cs
@@ -17,6 +17,6 @@ namespace SixLabors.ImageSharp.Advanced
/// The image.
/// The pixel type.
void Visit(Image image)
- where TPixel : struct, IPixel;
+ where TPixel : unmanaged, IPixel;
}
}
diff --git a/src/ImageSharp/Advanced/IPixelSource.cs b/src/ImageSharp/Advanced/IPixelSource.cs
index a321e877b..d7162bc61 100644
--- a/src/ImageSharp/Advanced/IPixelSource.cs
+++ b/src/ImageSharp/Advanced/IPixelSource.cs
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Advanced
///
/// The type of the pixel.
internal interface IPixelSource
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
///
/// Gets the pixel buffer.
diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs
new file mode 100644
index 000000000..980ed91a7
--- /dev/null
+++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Defines the contract for an action that operates on a row interval.
+ ///
+ public interface IRowIntervalOperation
+ {
+ ///
+ /// Invokes the method passing the row interval.
+ ///
+ /// The row interval.
+ void Invoke(in RowInterval rows);
+ }
+}
diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
new file mode 100644
index 000000000..47fcf253e
--- /dev/null
+++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Defines the contract for an action that operates on a row interval with a temporary buffer.
+ ///
+ /// The type of buffer elements.
+ public interface IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ ///
+ /// Invokes the method passing the row interval and a buffer.
+ ///
+ /// The row interval.
+ /// The contiguous region of memory.
+ void Invoke(in RowInterval rows, Span span);
+ }
+}
diff --git a/src/ImageSharp/Advanced/IRowOperation.cs b/src/ImageSharp/Advanced/IRowOperation.cs
new file mode 100644
index 000000000..0a6065e4b
--- /dev/null
+++ b/src/ImageSharp/Advanced/IRowOperation.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Defines the contract for an action that operates on a row.
+ ///
+ public interface IRowOperation
+ {
+ ///
+ /// Invokes the method passing the row y coordinate.
+ ///
+ /// The row y coordinate.
+ void Invoke(int y);
+ }
+}
diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs
new file mode 100644
index 000000000..7a13930fa
--- /dev/null
+++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Defines the contract for an action that operates on a row with a temporary buffer.
+ ///
+ /// The type of buffer elements.
+ public interface IRowOperation
+ where TBuffer : unmanaged
+ {
+ ///
+ /// Invokes the method passing the row and a buffer.
+ ///
+ /// The row y coordinate.
+ /// The contiguous region of memory.
+ void Invoke(int y, Span span);
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs
similarity index 95%
rename from src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs
rename to src/ImageSharp/Advanced/ParallelExecutionSettings.cs
index f17d70a2a..54ee06918 100644
--- a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs
+++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.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.
using System;
@@ -6,10 +6,10 @@ using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
-namespace SixLabors.ImageSharp.Advanced.ParallelUtils
+namespace SixLabors.ImageSharp.Advanced
{
///
- /// Defines execution settings for methods in .
+ /// Defines execution settings for methods in .
///
public readonly struct ParallelExecutionSettings
{
@@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
}
///
- /// Get the default for a
+ /// Get the default for a
///
/// The .
/// The .
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
new file mode 100644
index 000000000..3f0f77ca3
--- /dev/null
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
@@ -0,0 +1,198 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Utility methods for batched processing of pixel row intervals.
+ /// Parallel execution is optimized for image processing based on values defined
+ /// or .
+ /// Using this class is preferred over direct usage of utility methods.
+ ///
+ public static partial class ParallelRowIterator
+ {
+ private readonly struct RowOperationWrapper
+ where T : struct, IRowOperation
+ {
+ private readonly int minY;
+ private readonly int maxY;
+ private readonly int stepY;
+ private readonly T action;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowOperationWrapper(
+ int minY,
+ int maxY,
+ int stepY,
+ in T action)
+ {
+ this.minY = minY;
+ this.maxY = maxY;
+ this.stepY = stepY;
+ this.action = action;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.minY + (i * this.stepY);
+
+ if (yMin >= this.maxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.stepY, this.maxY);
+
+ for (int y = yMin; y < yMax; y++)
+ {
+ // Skip the safety copy when invoking a potentially impure method on a readonly field
+ Unsafe.AsRef(this.action).Invoke(y);
+ }
+ }
+ }
+
+ private readonly struct RowOperationWrapper
+ where T : struct, IRowOperation
+ where TBuffer : unmanaged
+ {
+ private readonly int minY;
+ private readonly int maxY;
+ private readonly int stepY;
+ private readonly int width;
+ private readonly MemoryAllocator allocator;
+ private readonly T action;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowOperationWrapper(
+ int minY,
+ int maxY,
+ int stepY,
+ int width,
+ MemoryAllocator allocator,
+ in T action)
+ {
+ this.minY = minY;
+ this.maxY = maxY;
+ this.stepY = stepY;
+ this.width = width;
+ this.allocator = allocator;
+ this.action = action;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.minY + (i * this.stepY);
+
+ if (yMin >= this.maxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.stepY, this.maxY);
+
+ using IMemoryOwner buffer = this.allocator.Allocate(this.width);
+
+ Span span = buffer.Memory.Span;
+
+ for (int y = yMin; y < yMax; y++)
+ {
+ Unsafe.AsRef(this.action).Invoke(y, span);
+ }
+ }
+ }
+
+ private readonly struct RowIntervalOperationWrapper
+ where T : struct, IRowIntervalOperation
+ {
+ private readonly int minY;
+ private readonly int maxY;
+ private readonly int stepY;
+ private readonly T operation;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperationWrapper(
+ int minY,
+ int maxY,
+ int stepY,
+ in T operation)
+ {
+ this.minY = minY;
+ this.maxY = maxY;
+ this.stepY = stepY;
+ this.operation = operation;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.minY + (i * this.stepY);
+
+ if (yMin >= this.maxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.stepY, this.maxY);
+ var rows = new RowInterval(yMin, yMax);
+
+ // Skip the safety copy when invoking a potentially impure method on a readonly field
+ Unsafe.AsRef(in this.operation).Invoke(in rows);
+ }
+ }
+
+ private readonly struct RowIntervalOperationWrapper
+ where T : struct, IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ private readonly int minY;
+ private readonly int maxY;
+ private readonly int stepY;
+ private readonly int width;
+ private readonly MemoryAllocator allocator;
+ private readonly T operation;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public RowIntervalOperationWrapper(
+ int minY,
+ int maxY,
+ int stepY,
+ int width,
+ MemoryAllocator allocator,
+ in T operation)
+ {
+ this.minY = minY;
+ this.maxY = maxY;
+ this.stepY = stepY;
+ this.width = width;
+ this.allocator = allocator;
+ this.operation = operation;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Invoke(int i)
+ {
+ int yMin = this.minY + (i * this.stepY);
+
+ if (yMin >= this.maxY)
+ {
+ return;
+ }
+
+ int yMax = Math.Min(yMin + this.stepY, this.maxY);
+ var rows = new RowInterval(yMin, yMax);
+
+ using IMemoryOwner buffer = this.allocator.Allocate(this.width);
+
+ Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs
new file mode 100644
index 000000000..fb85de986
--- /dev/null
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs
@@ -0,0 +1,288 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Advanced
+{
+ ///
+ /// Utility methods for batched processing of pixel row intervals.
+ /// Parallel execution is optimized for image processing based on values defined
+ /// or .
+ /// Using this class is preferred over direct usage of utility methods.
+ ///
+ public static partial class ParallelRowIterator
+ {
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches.
+ ///
+ /// The type of row operation to perform.
+ /// The to get the parallel settings from.
+ /// The .
+ /// The operation defining the iteration logic on a single row.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation)
+ where T : struct, IRowOperation
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRows(rectangle, in parallelSettings, in operation);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches.
+ ///
+ /// The type of row operation to perform.
+ /// The .
+ /// The .
+ /// The operation defining the iteration logic on a single row.
+ public static void IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T operation)
+ where T : struct, IRowOperation
+ {
+ ValidateRectangle(rectangle);
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ for (int y = top; y < bottom; y++)
+ {
+ Unsafe.AsRef(operation).Invoke(y);
+ }
+
+ return;
+ }
+
+ int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, in operation);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ wrappingOperation.Invoke);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches.
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ /// The type of row operation to perform.
+ /// The type of buffer elements.
+ /// The to get the parallel settings from.
+ /// The .
+ /// The operation defining the iteration logic on a single row.
+ public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation)
+ where T : struct, IRowOperation
+ where TBuffer : unmanaged
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRows(rectangle, in parallelSettings, in operation);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches.
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ /// The type of row operation to perform.
+ /// The type of buffer elements.
+ /// The .
+ /// The .
+ /// The operation defining the iteration logic on a single row.
+ public static void IterateRows(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T operation)
+ where T : struct, IRowOperation
+ where TBuffer : unmanaged
+ {
+ ValidateRectangle(rectangle);
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+ MemoryAllocator allocator = parallelSettings.MemoryAllocator;
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ using IMemoryOwner buffer = allocator.Allocate(width);
+ Span span = buffer.Memory.Span;
+
+ for (int y = top; y < bottom; y++)
+ {
+ Unsafe.AsRef(operation).Invoke(y, span);
+ }
+
+ return;
+ }
+
+ int verticalStep = DivideCeil(height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ wrappingOperation.Invoke);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ /// The type of row operation to perform.
+ /// The to get the parallel settings from.
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation)
+ where T : struct, IRowIntervalOperation
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRowIntervals(rectangle, in parallelSettings, in operation);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s.
+ ///
+ /// The type of row operation to perform.
+ /// The .
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ public static void IterateRowIntervals(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T operation)
+ where T : struct, IRowIntervalOperation
+ {
+ ValidateRectangle(rectangle);
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ var rows = new RowInterval(top, bottom);
+ Unsafe.AsRef(in operation).Invoke(in rows);
+ return;
+ }
+
+ int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ wrappingOperation.Invoke);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ /// The type of row operation to perform.
+ /// The type of buffer elements.
+ /// The to get the parallel settings from.
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation)
+ where T : struct, IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ IterateRowIntervals(rectangle, in parallelSettings, in operation);
+ }
+
+ ///
+ /// Iterate through the rows of a rectangle in optimized batches defined by -s
+ /// instantiating a temporary buffer for each invocation.
+ ///
+ /// The type of row operation to perform.
+ /// The type of buffer elements.
+ /// The .
+ /// The .
+ /// The operation defining the iteration logic on a single .
+ public static void IterateRowIntervals(
+ Rectangle rectangle,
+ in ParallelExecutionSettings parallelSettings,
+ in T operation)
+ where T : struct, IRowIntervalOperation
+ where TBuffer : unmanaged
+ {
+ ValidateRectangle(rectangle);
+
+ int top = rectangle.Top;
+ int bottom = rectangle.Bottom;
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
+ MemoryAllocator allocator = parallelSettings.MemoryAllocator;
+
+ // Avoid TPL overhead in this trivial case:
+ if (numOfSteps == 1)
+ {
+ var rows = new RowInterval(top, bottom);
+ using IMemoryOwner buffer = allocator.Allocate(width);
+
+ Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span);
+
+ return;
+ }
+
+ int verticalStep = DivideCeil(height, numOfSteps);
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
+ var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation);
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ wrappingOperation.Invoke);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
+
+ private static void ValidateRectangle(Rectangle rectangle)
+ {
+ Guard.MustBeGreaterThan(
+ rectangle.Width,
+ 0,
+ $"{nameof(rectangle)}.{nameof(rectangle.Width)}");
+
+ Guard.MustBeGreaterThan(
+ rectangle.Height,
+ 0,
+ $"{nameof(rectangle)}.{nameof(rectangle.Height)}");
+ }
+ }
+}
diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
deleted file mode 100644
index 4833dbafd..000000000
--- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Buffers;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Advanced.ParallelUtils
-{
- ///
- /// Utility methods for batched processing of pixel row intervals.
- /// Parallel execution is optimized for image processing based on values defined
- /// or .
- /// Using this class is preferred over direct usage of utility methods.
- ///
- public static class ParallelHelper
- {
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s.
- ///
- /// The .
- /// The to get the parallel settings from.
- /// The method body defining the iteration logic on a single .
- public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body)
- {
- ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
-
- IterateRows(rectangle, parallelSettings, body);
- }
-
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s.
- ///
- /// The .
- /// The .
- /// The method body defining the iteration logic on a single .
- public static void IterateRows(
- Rectangle rectangle,
- in ParallelExecutionSettings parallelSettings,
- Action body)
- {
- ValidateRectangle(rectangle);
-
- int maxSteps = DivideCeil(
- rectangle.Width * rectangle.Height,
- parallelSettings.MinimumPixelsProcessedPerTask);
-
- int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
-
- // Avoid TPL overhead in this trivial case:
- if (numOfSteps == 1)
- {
- var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
- body(rows);
- return;
- }
-
- int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
-
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
-
- Parallel.For(
- 0,
- numOfSteps,
- parallelOptions,
- i =>
- {
- int yMin = rectangle.Top + (i * verticalStep);
- int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom);
-
- var rows = new RowInterval(yMin, yMax);
- body(rows);
- });
- }
-
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s
- /// instantiating a temporary buffer for each invocation.
- ///
- internal static void IterateRowsWithTempBuffer(
- Rectangle rectangle,
- in ParallelExecutionSettings parallelSettings,
- Action> body)
- where T : unmanaged
- {
- ValidateRectangle(rectangle);
-
- int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask);
-
- int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
-
- MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator;
-
- // Avoid TPL overhead in this trivial case:
- if (numOfSteps == 1)
- {
- var rows = new RowInterval(rectangle.Top, rectangle.Bottom);
- using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width))
- {
- body(rows, buffer.Memory);
- }
-
- return;
- }
-
- int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
-
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
-
- Parallel.For(
- 0,
- numOfSteps,
- parallelOptions,
- i =>
- {
- int yMin = rectangle.Top + (i * verticalStep);
- int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom);
-
- var rows = new RowInterval(yMin, yMax);
-
- using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width))
- {
- body(rows, buffer.Memory);
- }
- });
- }
-
- ///
- /// Iterate through the rows of a rectangle in optimized batches defined by -s
- /// instantiating a temporary buffer for each invocation.
- ///
- internal static void IterateRowsWithTempBuffer(
- Rectangle rectangle,
- Configuration configuration,
- Action> body)
- where T : unmanaged
- {
- IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
-
- private static void ValidateRectangle(Rectangle rectangle)
- {
- Guard.MustBeGreaterThan(
- rectangle.Width,
- 0,
- $"{nameof(rectangle)}.{nameof(rectangle.Width)}");
-
- Guard.MustBeGreaterThan(
- rectangle.Height,
- 0,
- $"{nameof(rectangle)}.{nameof(rectangle.Height)}");
- }
- }
-}
diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs
index 0575a3e99..240ce304d 100644
--- a/src/ImageSharp/Color/Color.NamedColors.cs
+++ b/src/ImageSharp/Color/Color.NamedColors.cs
@@ -1,13 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
+using System.Collections.Generic;
+
namespace SixLabors.ImageSharp
{
///
/// Contains static named color values.
+ ///
///
public readonly partial struct Color
{
+ private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true);
+
///
/// Represents a matching the W3C definition that has an hex value of #F0F8FF.
///
@@ -111,7 +117,7 @@ namespace SixLabors.ImageSharp
///
/// Represents a matching the W3C definition that has an hex value of #00FFFF.
///
- public static readonly Color Cyan = FromRgba(0, 255, 255, 255);
+ public static readonly Color Cyan = Aqua;
///
/// Represents a matching the W3C definition that has an hex value of #00008B.
@@ -138,6 +144,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #A9A9A9.
+ ///
+ public static readonly Color DarkGrey = DarkGray;
+
///
/// Represents a matching the W3C definition that has an hex value of #BDB76B.
///
@@ -188,6 +199,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #2F4F4F.
+ ///
+ public static readonly Color DarkSlateGrey = DarkSlateGray;
+
///
/// Represents a matching the W3C definition that has an hex value of #00CED1.
///
@@ -213,6 +229,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color DimGray = FromRgba(105, 105, 105, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #696969.
+ ///
+ public static readonly Color DimGrey = DimGray;
+
///
/// Represents a matching the W3C definition that has an hex value of #1E90FF.
///
@@ -273,6 +294,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #808080.
+ ///
+ public static readonly Color Grey = Gray;
+
///
/// Represents a matching the W3C definition that has an hex value of #F0FFF0.
///
@@ -353,6 +379,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color LightGreen = FromRgba(144, 238, 144, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #D3D3D3.
+ ///
+ public static readonly Color LightGrey = LightGray;
+
///
/// Represents a matching the W3C definition that has an hex value of #FFB6C1.
///
@@ -378,6 +409,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #778899.
+ ///
+ public static readonly Color LightSlateGrey = LightSlateGray;
+
///
/// Represents a matching the W3C definition that has an hex value of #B0C4DE.
///
@@ -406,7 +442,7 @@ namespace SixLabors.ImageSharp
///
/// Represents a matching the W3C definition that has an hex value of #FF00FF.
///
- public static readonly Color Magenta = FromRgba(255, 0, 255, 255);
+ public static readonly Color Magenta = Fuchsia;
///
/// Represents a matching the W3C definition that has an hex value of #800000.
@@ -643,6 +679,11 @@ namespace SixLabors.ImageSharp
///
public static readonly Color SlateGray = FromRgba(112, 128, 144, 255);
+ ///
+ /// Represents a matching the W3C definition that has an hex value of #708090.
+ ///
+ public static readonly Color SlateGrey = SlateGray;
+
///
/// Represents a matching the W3C definition that has an hex value of #FFFAFA.
///
@@ -679,9 +720,9 @@ namespace SixLabors.ImageSharp
public static readonly Color Tomato = FromRgba(255, 99, 71, 255);
///
- /// Represents a matching the W3C definition that has an hex value of #FFFFFF.
+ /// Represents a matching the W3C definition that has an hex value of #00000000.
///
- public static readonly Color Transparent = FromRgba(255, 255, 255, 0);
+ public static readonly Color Transparent = FromRgba(0, 0, 0, 0);
///
/// Represents a matching the W3C definition that has an hex value of #40E0D0.
@@ -717,5 +758,161 @@ namespace SixLabors.ImageSharp
/// Represents a matching the W3C definition that has an hex value of #9ACD32.
///
public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255);
+
+ private static Dictionary CreateNamedColorsLookup()
+ {
+ return new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { nameof(AliceBlue), AliceBlue },
+ { nameof(AntiqueWhite), AntiqueWhite },
+ { nameof(Aqua), Aqua },
+ { nameof(Aquamarine), Aquamarine },
+ { nameof(Azure), Azure },
+ { nameof(Beige), Beige },
+ { nameof(Bisque), Bisque },
+ { nameof(Black), Black },
+ { nameof(BlanchedAlmond), BlanchedAlmond },
+ { nameof(Blue), Blue },
+ { nameof(BlueViolet), BlueViolet },
+ { nameof(Brown), Brown },
+ { nameof(BurlyWood), BurlyWood },
+ { nameof(CadetBlue), CadetBlue },
+ { nameof(Chartreuse), Chartreuse },
+ { nameof(Chocolate), Chocolate },
+ { nameof(Coral), Coral },
+ { nameof(CornflowerBlue), CornflowerBlue },
+ { nameof(Cornsilk), Cornsilk },
+ { nameof(Crimson), Crimson },
+ { nameof(Cyan), Cyan },
+ { nameof(DarkBlue), DarkBlue },
+ { nameof(DarkCyan), DarkCyan },
+ { nameof(DarkGoldenrod), DarkGoldenrod },
+ { nameof(DarkGray), DarkGray },
+ { nameof(DarkGreen), DarkGreen },
+ { nameof(DarkGrey), DarkGrey },
+ { nameof(DarkKhaki), DarkKhaki },
+ { nameof(DarkMagenta), DarkMagenta },
+ { nameof(DarkOliveGreen), DarkOliveGreen },
+ { nameof(DarkOrange), DarkOrange },
+ { nameof(DarkOrchid), DarkOrchid },
+ { nameof(DarkRed), DarkRed },
+ { nameof(DarkSalmon), DarkSalmon },
+ { nameof(DarkSeaGreen), DarkSeaGreen },
+ { nameof(DarkSlateBlue), DarkSlateBlue },
+ { nameof(DarkSlateGray), DarkSlateGray },
+ { nameof(DarkSlateGrey), DarkSlateGrey },
+ { nameof(DarkTurquoise), DarkTurquoise },
+ { nameof(DarkViolet), DarkViolet },
+ { nameof(DeepPink), DeepPink },
+ { nameof(DeepSkyBlue), DeepSkyBlue },
+ { nameof(DimGray), DimGray },
+ { nameof(DimGrey), DimGrey },
+ { nameof(DodgerBlue), DodgerBlue },
+ { nameof(Firebrick), Firebrick },
+ { nameof(FloralWhite), FloralWhite },
+ { nameof(ForestGreen), ForestGreen },
+ { nameof(Fuchsia), Fuchsia },
+ { nameof(Gainsboro), Gainsboro },
+ { nameof(GhostWhite), GhostWhite },
+ { nameof(Gold), Gold },
+ { nameof(Goldenrod), Goldenrod },
+ { nameof(Gray), Gray },
+ { nameof(Green), Green },
+ { nameof(GreenYellow), GreenYellow },
+ { nameof(Grey), Grey },
+ { nameof(Honeydew), Honeydew },
+ { nameof(HotPink), HotPink },
+ { nameof(IndianRed), IndianRed },
+ { nameof(Indigo), Indigo },
+ { nameof(Ivory), Ivory },
+ { nameof(Khaki), Khaki },
+ { nameof(Lavender), Lavender },
+ { nameof(LavenderBlush), LavenderBlush },
+ { nameof(LawnGreen), LawnGreen },
+ { nameof(LemonChiffon), LemonChiffon },
+ { nameof(LightBlue), LightBlue },
+ { nameof(LightCoral), LightCoral },
+ { nameof(LightCyan), LightCyan },
+ { nameof(LightGoldenrodYellow), LightGoldenrodYellow },
+ { nameof(LightGray), LightGray },
+ { nameof(LightGreen), LightGreen },
+ { nameof(LightGrey), LightGrey },
+ { nameof(LightPink), LightPink },
+ { nameof(LightSalmon), LightSalmon },
+ { nameof(LightSeaGreen), LightSeaGreen },
+ { nameof(LightSkyBlue), LightSkyBlue },
+ { nameof(LightSlateGray), LightSlateGray },
+ { nameof(LightSlateGrey), LightSlateGrey },
+ { nameof(LightSteelBlue), LightSteelBlue },
+ { nameof(LightYellow), LightYellow },
+ { nameof(Lime), Lime },
+ { nameof(LimeGreen), LimeGreen },
+ { nameof(Linen), Linen },
+ { nameof(Magenta), Magenta },
+ { nameof(Maroon), Maroon },
+ { nameof(MediumAquamarine), MediumAquamarine },
+ { nameof(MediumBlue), MediumBlue },
+ { nameof(MediumOrchid), MediumOrchid },
+ { nameof(MediumPurple), MediumPurple },
+ { nameof(MediumSeaGreen), MediumSeaGreen },
+ { nameof(MediumSlateBlue), MediumSlateBlue },
+ { nameof(MediumSpringGreen), MediumSpringGreen },
+ { nameof(MediumTurquoise), MediumTurquoise },
+ { nameof(MediumVioletRed), MediumVioletRed },
+ { nameof(MidnightBlue), MidnightBlue },
+ { nameof(MintCream), MintCream },
+ { nameof(MistyRose), MistyRose },
+ { nameof(Moccasin), Moccasin },
+ { nameof(NavajoWhite), NavajoWhite },
+ { nameof(Navy), Navy },
+ { nameof(OldLace), OldLace },
+ { nameof(Olive), Olive },
+ { nameof(OliveDrab), OliveDrab },
+ { nameof(Orange), Orange },
+ { nameof(OrangeRed), OrangeRed },
+ { nameof(Orchid), Orchid },
+ { nameof(PaleGoldenrod), PaleGoldenrod },
+ { nameof(PaleGreen), PaleGreen },
+ { nameof(PaleTurquoise), PaleTurquoise },
+ { nameof(PaleVioletRed), PaleVioletRed },
+ { nameof(PapayaWhip), PapayaWhip },
+ { nameof(PeachPuff), PeachPuff },
+ { nameof(Peru), Peru },
+ { nameof(Pink), Pink },
+ { nameof(Plum), Plum },
+ { nameof(PowderBlue), PowderBlue },
+ { nameof(Purple), Purple },
+ { nameof(RebeccaPurple), RebeccaPurple },
+ { nameof(Red), Red },
+ { nameof(RosyBrown), RosyBrown },
+ { nameof(RoyalBlue), RoyalBlue },
+ { nameof(SaddleBrown), SaddleBrown },
+ { nameof(Salmon), Salmon },
+ { nameof(SandyBrown), SandyBrown },
+ { nameof(SeaGreen), SeaGreen },
+ { nameof(SeaShell), SeaShell },
+ { nameof(Sienna), Sienna },
+ { nameof(Silver), Silver },
+ { nameof(SkyBlue), SkyBlue },
+ { nameof(SlateBlue), SlateBlue },
+ { nameof(SlateGray), SlateGray },
+ { nameof(SlateGrey), SlateGrey },
+ { nameof(Snow), Snow },
+ { nameof(SpringGreen), SpringGreen },
+ { nameof(SteelBlue), SteelBlue },
+ { nameof(Tan), Tan },
+ { nameof(Teal), Teal },
+ { nameof(Thistle), Thistle },
+ { nameof(Tomato), Tomato },
+ { nameof(Transparent), Transparent },
+ { nameof(Turquoise), Turquoise },
+ { nameof(Violet), Violet },
+ { nameof(Wheat), Wheat },
+ { nameof(White), White },
+ { nameof(WhiteSmoke), WhiteSmoke },
+ { nameof(Yellow), Yellow },
+ { nameof(YellowGreen), YellowGreen }
+ };
+ }
}
}
diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs
index 768fe065c..2948b4c52 100644
--- a/src/ImageSharp/Color/Color.WernerPalette.cs
+++ b/src/ImageSharp/Color/Color.WernerPalette.cs
@@ -20,116 +20,116 @@ namespace SixLabors.ImageSharp
private static Color[] CreateWernerPalette() => new[]
{
- FromHex("#f1e9cd"),
- FromHex("#f2e7cf"),
- FromHex("#ece6d0"),
- FromHex("#f2eacc"),
- FromHex("#f3e9ca"),
- FromHex("#f2ebcd"),
- FromHex("#e6e1c9"),
- FromHex("#e2ddc6"),
- FromHex("#cbc8b7"),
- FromHex("#bfbbb0"),
- FromHex("#bebeb3"),
- FromHex("#b7b5ac"),
- FromHex("#bab191"),
- FromHex("#9c9d9a"),
- FromHex("#8a8d84"),
- FromHex("#5b5c61"),
- FromHex("#555152"),
- FromHex("#413f44"),
- FromHex("#454445"),
- FromHex("#423937"),
- FromHex("#433635"),
- FromHex("#252024"),
- FromHex("#241f20"),
- FromHex("#281f3f"),
- FromHex("#1c1949"),
- FromHex("#4f638d"),
- FromHex("#383867"),
- FromHex("#5c6b8f"),
- FromHex("#657abb"),
- FromHex("#6f88af"),
- FromHex("#7994b5"),
- FromHex("#6fb5a8"),
- FromHex("#719ba2"),
- FromHex("#8aa1a6"),
- FromHex("#d0d5d3"),
- FromHex("#8590ae"),
- FromHex("#3a2f52"),
- FromHex("#39334a"),
- FromHex("#6c6d94"),
- FromHex("#584c77"),
- FromHex("#533552"),
- FromHex("#463759"),
- FromHex("#bfbac0"),
- FromHex("#77747f"),
- FromHex("#4a475c"),
- FromHex("#b8bfaf"),
- FromHex("#b2b599"),
- FromHex("#979c84"),
- FromHex("#5d6161"),
- FromHex("#61ac86"),
- FromHex("#a4b6a7"),
- FromHex("#adba98"),
- FromHex("#93b778"),
- FromHex("#7d8c55"),
- FromHex("#33431e"),
- FromHex("#7c8635"),
- FromHex("#8e9849"),
- FromHex("#c2c190"),
- FromHex("#67765b"),
- FromHex("#ab924b"),
- FromHex("#c8c76f"),
- FromHex("#ccc050"),
- FromHex("#ebdd99"),
- FromHex("#ab9649"),
- FromHex("#dbc364"),
- FromHex("#e6d058"),
- FromHex("#ead665"),
- FromHex("#d09b2c"),
- FromHex("#a36629"),
- FromHex("#a77d35"),
- FromHex("#f0d696"),
- FromHex("#d7c485"),
- FromHex("#f1d28c"),
- FromHex("#efcc83"),
- FromHex("#f3daa7"),
- FromHex("#dfa837"),
- FromHex("#ebbc71"),
- FromHex("#d17c3f"),
- FromHex("#92462f"),
- FromHex("#be7249"),
- FromHex("#bb603c"),
- FromHex("#c76b4a"),
- FromHex("#a75536"),
- FromHex("#b63e36"),
- FromHex("#b5493a"),
- FromHex("#cd6d57"),
- FromHex("#711518"),
- FromHex("#e9c49d"),
- FromHex("#eedac3"),
- FromHex("#eecfbf"),
- FromHex("#ce536b"),
- FromHex("#b74a70"),
- FromHex("#b7757c"),
- FromHex("#612741"),
- FromHex("#7a4848"),
- FromHex("#3f3033"),
- FromHex("#8d746f"),
- FromHex("#4d3635"),
- FromHex("#6e3b31"),
- FromHex("#864735"),
- FromHex("#553d3a"),
- FromHex("#613936"),
- FromHex("#7a4b3a"),
- FromHex("#946943"),
- FromHex("#c39e6d"),
- FromHex("#513e32"),
- FromHex("#8b7859"),
- FromHex("#9b856b"),
- FromHex("#766051"),
- FromHex("#453b32")
+ ParseHex("#f1e9cd"),
+ ParseHex("#f2e7cf"),
+ ParseHex("#ece6d0"),
+ ParseHex("#f2eacc"),
+ ParseHex("#f3e9ca"),
+ ParseHex("#f2ebcd"),
+ ParseHex("#e6e1c9"),
+ ParseHex("#e2ddc6"),
+ ParseHex("#cbc8b7"),
+ ParseHex("#bfbbb0"),
+ ParseHex("#bebeb3"),
+ ParseHex("#b7b5ac"),
+ ParseHex("#bab191"),
+ ParseHex("#9c9d9a"),
+ ParseHex("#8a8d84"),
+ ParseHex("#5b5c61"),
+ ParseHex("#555152"),
+ ParseHex("#413f44"),
+ ParseHex("#454445"),
+ ParseHex("#423937"),
+ ParseHex("#433635"),
+ ParseHex("#252024"),
+ ParseHex("#241f20"),
+ ParseHex("#281f3f"),
+ ParseHex("#1c1949"),
+ ParseHex("#4f638d"),
+ ParseHex("#383867"),
+ ParseHex("#5c6b8f"),
+ ParseHex("#657abb"),
+ ParseHex("#6f88af"),
+ ParseHex("#7994b5"),
+ ParseHex("#6fb5a8"),
+ ParseHex("#719ba2"),
+ ParseHex("#8aa1a6"),
+ ParseHex("#d0d5d3"),
+ ParseHex("#8590ae"),
+ ParseHex("#3a2f52"),
+ ParseHex("#39334a"),
+ ParseHex("#6c6d94"),
+ ParseHex("#584c77"),
+ ParseHex("#533552"),
+ ParseHex("#463759"),
+ ParseHex("#bfbac0"),
+ ParseHex("#77747f"),
+ ParseHex("#4a475c"),
+ ParseHex("#b8bfaf"),
+ ParseHex("#b2b599"),
+ ParseHex("#979c84"),
+ ParseHex("#5d6161"),
+ ParseHex("#61ac86"),
+ ParseHex("#a4b6a7"),
+ ParseHex("#adba98"),
+ ParseHex("#93b778"),
+ ParseHex("#7d8c55"),
+ ParseHex("#33431e"),
+ ParseHex("#7c8635"),
+ ParseHex("#8e9849"),
+ ParseHex("#c2c190"),
+ ParseHex("#67765b"),
+ ParseHex("#ab924b"),
+ ParseHex("#c8c76f"),
+ ParseHex("#ccc050"),
+ ParseHex("#ebdd99"),
+ ParseHex("#ab9649"),
+ ParseHex("#dbc364"),
+ ParseHex("#e6d058"),
+ ParseHex("#ead665"),
+ ParseHex("#d09b2c"),
+ ParseHex("#a36629"),
+ ParseHex("#a77d35"),
+ ParseHex("#f0d696"),
+ ParseHex("#d7c485"),
+ ParseHex("#f1d28c"),
+ ParseHex("#efcc83"),
+ ParseHex("#f3daa7"),
+ ParseHex("#dfa837"),
+ ParseHex("#ebbc71"),
+ ParseHex("#d17c3f"),
+ ParseHex("#92462f"),
+ ParseHex("#be7249"),
+ ParseHex("#bb603c"),
+ ParseHex("#c76b4a"),
+ ParseHex("#a75536"),
+ ParseHex("#b63e36"),
+ ParseHex("#b5493a"),
+ ParseHex("#cd6d57"),
+ ParseHex("#711518"),
+ ParseHex("#e9c49d"),
+ ParseHex("#eedac3"),
+ ParseHex("#eecfbf"),
+ ParseHex("#ce536b"),
+ ParseHex("#b74a70"),
+ ParseHex("#b7757c"),
+ ParseHex("#612741"),
+ ParseHex("#7a4848"),
+ ParseHex("#3f3033"),
+ ParseHex("#8d746f"),
+ ParseHex("#4d3635"),
+ ParseHex("#6e3b31"),
+ ParseHex("#864735"),
+ ParseHex("#553d3a"),
+ ParseHex("#613936"),
+ ParseHex("#7a4b3a"),
+ ParseHex("#946943"),
+ ParseHex("#c39e6d"),
+ ParseHex("#513e32"),
+ ParseHex("#8b7859"),
+ ParseHex("#9b856b"),
+ ParseHex("#766051"),
+ ParseHex("#453b32")
};
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index 5fad7a8e3..e0f9d1e8d 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -95,21 +95,102 @@ namespace SixLabors.ImageSharp
public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b);
///
- /// Creates a new instance from the string representing a color in hexadecimal form.
+ /// Creates a new instance of the struct
+ /// from the given hexadecimal string.
///
///
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
///
- /// Returns a that represents the color defined by the provided RGBA hex string.
+ ///
+ /// The .
+ ///
[MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromHex(string hex)
+ public static Color ParseHex(string hex)
{
- var rgba = Rgba32.FromHex(hex);
+ var rgba = Rgba32.ParseHex(hex);
return new Color(rgba);
}
+ ///
+ /// Attempts to creates a new instance of the struct
+ /// from the given hexadecimal string.
+ ///
+ ///
+ /// The hexadecimal representation of the combined color components arranged
+ /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ ///
+ /// When this method returns, contains the equivalent of the hexadecimal input.
+ ///
+ /// The .
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static bool TryParseHex(string hex, out Color result)
+ {
+ result = default;
+
+ if (Rgba32.TryParseHex(hex, out Rgba32 rgba))
+ {
+ result = new Color(rgba);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Creates a new instance of the struct
+ /// from the given input string.
+ ///
+ ///
+ /// The name of the color or the hexadecimal representation of the combined color components arranged
+ /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ ///
+ ///
+ /// The .
+ ///
+ public static Color Parse(string input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ if (!TryParse(input, out Color color))
+ {
+ throw new ArgumentException("Input string is not in the correct format.", nameof(input));
+ }
+
+ return color;
+ }
+
+ ///
+ /// Attempts to creates a new instance of the struct
+ /// from the given input string.
+ ///
+ ///
+ /// The name of the color or the hexadecimal representation of the combined color components arranged
+ /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ ///
+ /// When this method returns, contains the equivalent of the hexadecimal input.
+ ///
+ /// The .
+ ///
+ public static bool TryParse(string input, out Color result)
+ {
+ result = default;
+
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return false;
+ }
+
+ if (NamedColorsLookupLazy.Value.TryGetValue(input, out result))
+ {
+ return true;
+ }
+
+ return TryParseHex(input, out result);
+ }
+
///
/// Alters the alpha channel of the color, returning a new instance.
///
@@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp
/// The color having it's alpha channel altered.
public Color WithAlpha(float alpha)
{
- Vector4 v = (Vector4)this;
+ var v = (Vector4)this;
v.W = alpha;
return new Color(v);
}
@@ -139,7 +220,7 @@ namespace SixLabors.ImageSharp
/// The pixel value.
[MethodImpl(InliningOptions.ShortMethod)]
public TPixel ToPixel()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
TPixel pixel = default;
pixel.FromRgba64(this.data);
@@ -158,7 +239,7 @@ namespace SixLabors.ImageSharp
Configuration configuration,
ReadOnlySpan source,
Span destination)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source);
PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination);
diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs
index c2331c379..5229cf14f 100644
--- a/src/ImageSharp/ColorSpaces/Cmyk.cs
+++ b/src/ImageSharp/ColorSpaces/Cmyk.cs
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.ColorSpaces
[MethodImpl(InliningOptions.ShortMethod)]
public Cmyk(Vector4 vector)
{
- vector = Vector4.Clamp(vector, Min, Max);
+ vector = Vector4Utilities.FastClamp(vector, Min, Max);
this.C = vector.X;
this.M = vector.Y;
this.Y = vector.Z;
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
index 4be3f0079..5b312f4f8 100644
--- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
@@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
}
///
- /// Performs the bulk conversion from into .
+ /// Performs the bulk conversion from into .
///
/// The span to the source colors
/// The span to the destination colors
@@ -435,4 +435,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
return this.ToLinearRgb(rgb);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs
index 8b9dbe1b8..4028b70b0 100644
--- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs
+++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs
@@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp
{
///
/// The exception that is thrown when the library tries to load
- /// an image, which has an invalid format.
+ /// an image, which has format or content that is invalid or unsupported by ImageSharp.
///
public class ImageFormatException : Exception
{
diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
index cff6e3b60..983a1eb8b 100644
--- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
@@ -1,10 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
-namespace SixLabors.ImageSharp.Common
+namespace SixLabors.ImageSharp
{
///
/// Encapsulates a series of time saving extension methods to the interface.
@@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common
///
/// Generates a sequence of integral numbers within a specified range.
///
- ///
- /// The start index, inclusive.
- ///
+ /// The start index, inclusive.
///
/// A method that has one parameter and returns a calculating the end index.
///
- ///
- /// The incremental step.
- ///
+ /// The incremental step.
///
/// The that contains a range of sequential integral numbers.
///
@@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
index 971bff322..5d8668257 100644
--- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs
@@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Memory;
+#if !SUPPORTS_SPAN_STREAM
+using System.Buffers;
+#endif
namespace SixLabors.ImageSharp
{
diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
index f82774601..312ab388d 100644
--- a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
+++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
@@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
ComplexVector4 vector = default;
int kernelLength = kernel.Length;
diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
index ff6e3a4ec..462eeb302 100644
--- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
+++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
@@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Convolve2DImpl(
in matrixY,
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
- Vector4Utils.UnPremultiply(ref vector);
+ Vector4Utilities.UnPremultiply(ref vector);
target = vector;
}
@@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Convolve2DImpl(
in matrixY,
@@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp
out Vector4 vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
- Vector4Utils.UnPremultiply(ref vector);
+ Vector4Utilities.UnPremultiply(ref vector);
target = vector;
}
@@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp
int minColumn,
int maxColumn,
out Vector4 vector)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Vector4 vectorY = default;
Vector4 vectorX = default;
@@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
- Vector4Utils.Premultiply(ref currentColor);
+ Vector4Utilities.Premultiply(ref currentColor);
vectorX += matrixX[y, x] * currentColor;
vectorY += matrixY[y, x] * currentColor;
@@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Vector4 vector = default;
@@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
- Vector4Utils.UnPremultiply(ref vector);
+ Vector4Utilities.UnPremultiply(ref vector);
target = vector;
}
@@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Vector4 vector = default;
@@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
- Vector4Utils.UnPremultiply(ref vector);
+ Vector4Utilities.UnPremultiply(ref vector);
target = vector;
}
@@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp
int minColumn,
int maxColumn,
ref Vector4 vector)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
@@ -270,7 +270,7 @@ namespace SixLabors.ImageSharp
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
- Vector4Utils.Premultiply(ref currentColor);
+ Vector4Utilities.Premultiply(ref currentColor);
vector += matrix[y, x] * currentColor;
}
}
diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs
new file mode 100644
index 000000000..3ab1b199a
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/Guard.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp;
+
+namespace SixLabors
+{
+ internal static partial class Guard
+ {
+ ///
+ /// Ensures that the value is a value type.
+ ///
+ /// The target object, which cannot be null.
+ /// The name of the parameter that is to be checked.
+ /// The type of the value.
+ /// is not a value type.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void MustBeValueType(TValue value, string parameterName)
+ {
+ if (!value.GetType().GetTypeInfo().IsValueType)
+ {
+ ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index e7b14be42..92430c915 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -242,40 +242,6 @@ namespace SixLabors.ImageSharp
return 1F;
}
- ///
- /// Returns the result of a B-C filter against the given value.
- ///
- ///
- /// The value to process.
- /// The B-Spline curve variable.
- /// The Cardinal curve variable.
- ///
- /// The .
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static float GetBcValue(float x, float b, float c)
- {
- if (x < 0F)
- {
- x = -x;
- }
-
- float temp = x * x;
- if (x < 1F)
- {
- x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b));
- return x / 6F;
- }
-
- if (x < 2F)
- {
- x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c));
- return x / 6F;
- }
-
- return 0F;
- }
-
///
/// Gets the bounding from the given points.
///
@@ -303,7 +269,7 @@ namespace SixLabors.ImageSharp
/// The .
///
public static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int width = bitmap.Width;
int height = bitmap.Height;
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs
new file mode 100644
index 000000000..ea1ffba05
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+#if SUPPORTS_RUNTIME_INTRINSICS
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace SixLabors.ImageSharp
+{
+ internal static partial class SimdUtils
+ {
+ public static class Avx2Intrinsics
+ {
+ private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 };
+
+ ///
+ /// as many elements as possible, slicing them down (keeping the remainder).
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static void NormalizedFloatToByteSaturateReduce(
+ ref ReadOnlySpan source,
+ ref Span dest)
+ {
+ DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
+
+ if (Avx2.IsSupported)
+ {
+ int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count);
+ int adjustedCount = source.Length - remainder;
+
+ if (adjustedCount > 0)
+ {
+ NormalizedFloatToByteSaturate(
+ source.Slice(0, adjustedCount),
+ dest.Slice(0, adjustedCount));
+
+ source = source.Slice(adjustedCount);
+ dest = dest.Slice(adjustedCount);
+ }
+ }
+ }
+
+ ///
+ /// Implementation of , which is faster on new .NET runtime.
+ ///
+ ///
+ /// Implementation is based on MagicScaler code:
+ /// https://github.com/saucecontrol/PhotoSauce/blob/a9bd6e5162d2160419f0cf743fd4f536c079170b/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L453-L477
+ ///
+ internal static void NormalizedFloatToByteSaturate(
+ ReadOnlySpan source,
+ Span dest)
+ {
+ VerifySpanInput(source, dest, Vector256.Count);
+
+ int n = dest.Length / Vector256.Count;
+
+ ref Vector256 sourceBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+ ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
+
+ var maxBytes = Vector256.Create(255f);
+ ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32);
+ Vector256 mask = Unsafe.As>(ref maskBase);
+
+ for (int i = 0; i < n; i++)
+ {
+ ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4);
+
+ Vector256 f0 = s;
+ Vector256 f1 = Unsafe.Add(ref s, 1);
+ Vector256 f2 = Unsafe.Add(ref s, 2);
+ Vector256 f3 = Unsafe.Add(ref s, 3);
+
+ Vector256 w0 = ConvertToInt32(f0, maxBytes);
+ Vector256 w1 = ConvertToInt32(f1, maxBytes);
+ Vector256 w2 = ConvertToInt32(f2, maxBytes);
+ Vector256 w3 = ConvertToInt32(f3, maxBytes);
+
+ Vector256 u0 = Avx2.PackSignedSaturate(w0, w1);
+ Vector256 u1 = Avx2.PackSignedSaturate(w2, w3);
+ Vector256 b = Avx2.PackUnsignedSaturate(u0, u1);
+ b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte();
+
+ Unsafe.Add(ref destBase, i) = b;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale)
+ {
+ vf = Avx.Multiply(vf, scale);
+ return Avx.ConvertToVector256Int32(vf);
+ }
+ }
+ }
+}
+#endif
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
index bc07fbf31..1099678f7 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
@@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp
///
public static class BasicIntrinsics256
{
- public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture;
+ public static bool IsAvailable { get; } = HasVector8;
#if !SUPPORTS_EXTENDED_INTRINSICS
///
- /// as many elements as possible, slicing them down (keeping the remainder).
+ /// as many elements as possible, slicing them down (keeping the remainder).
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertByteToNormalizedFloatReduce(
+ internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan source,
ref Span dest)
{
@@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp
if (adjustedCount > 0)
{
- BulkConvertByteToNormalizedFloat(
+ ByteToNormalizedFloat(
source.Slice(0, adjustedCount),
dest.Slice(0, adjustedCount));
@@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp
}
///
- /// as many elements as possible, slicing them down (keeping the remainder).
+ /// as many elements as possible, slicing them down (keeping the remainder).
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
+ internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan source,
ref Span dest)
{
@@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp
if (adjustedCount > 0)
{
- BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount));
+ NormalizedFloatToByteSaturate(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount));
source = source.Slice(adjustedCount);
dest = dest.Slice(adjustedCount);
@@ -78,15 +78,15 @@ namespace SixLabors.ImageSharp
#endif
///
- /// SIMD optimized implementation for .
+ /// SIMD optimized implementation for .
/// Works only with span Length divisible by 8.
/// Implementation adapted from:
/// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions
/// http://stackoverflow.com/a/536278
///
- internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest)
{
- VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat));
+ VerifyHasVector8(nameof(ByteToNormalizedFloat));
VerifySpanInput(source, dest, 8);
var bVec = new Vector(256.0f / 255.0f);
@@ -94,17 +94,17 @@ namespace SixLabors.ImageSharp
var magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f
var mask = new Vector(255);
- ref Octet.OfByte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source));
- ref Octet.OfUInt32 destBaseAsWideOctet = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+ ref Octet sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
+ ref Octet destBaseAsWideOctet = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
- ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsWideOctet);
+ ref Vector destBaseAsFloat = ref Unsafe.As, Vector>(ref destBaseAsWideOctet);
int n = dest.Length / 8;
for (int i = 0; i < n; i++)
{
- ref Octet.OfByte s = ref Unsafe.Add(ref sourceBase, i);
- ref Octet.OfUInt32 d = ref Unsafe.Add(ref destBaseAsWideOctet, i);
+ ref Octet s = ref Unsafe.Add(ref sourceBase, i);
+ ref Octet d = ref Unsafe.Add(ref destBaseAsWideOctet, i);
d.LoadFrom(ref s);
}
@@ -124,11 +124,11 @@ namespace SixLabors.ImageSharp
}
///
- /// Implementation of which is faster on older runtimes.
+ /// Implementation of which is faster on older runtimes.
///
- internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest)
+ internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest)
{
- VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows));
+ VerifyHasVector8(nameof(NormalizedFloatToByteSaturate));
VerifySpanInput(source, dest, 8);
if (source.Length == 0)
@@ -137,17 +137,17 @@ namespace SixLabors.ImageSharp
}
ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+ ref Octet destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 8;
var magick = new Vector(32768.0f);
var scale = new Vector(255f) / new Vector(256f);
// need to copy to a temporary struct, because
- // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x)
+ // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report
- var temp = default(Octet.OfUInt32);
- ref Vector tempRef = ref Unsafe.As>(ref temp);
+ var temp = default(Octet);
+ ref Vector tempRef = ref Unsafe.As, Vector>(ref temp);
for (int i = 0; i < n; i++)
{
@@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp
x = (x * scale) + magick;
tempRef = x;
- ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i);
+ ref Octet d = ref Unsafe.Add(ref destBase, i);
d.LoadFrom(ref temp);
}
}
@@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp
///
internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest)
{
- VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte));
+ VerifyHasVector8(nameof(BulkConvertNormalizedFloatToByte));
VerifySpanInput(source, dest, 8);
if (source.Length == 0)
@@ -186,17 +186,17 @@ namespace SixLabors.ImageSharp
}
ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
+ ref Octet destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 8;
var magick = new Vector(32768.0f);
var scale = new Vector(255f) / new Vector(256f);
// need to copy to a temporary struct, because
- // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x)
+ // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report
- var temp = default(Octet.OfUInt32);
- ref Vector tempRef = ref Unsafe.As>(ref temp);
+ var temp = default(Octet);
+ ref Vector tempRef = ref Unsafe.As, Vector>(ref temp);
for (int i = 0; i < n; i++)
{
@@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp
x = (x * scale) + magick;
tempRef = x;
- ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i);
+ ref Octet d = ref Unsafe.Add(ref destBase, i);
d.LoadFrom(ref temp);
}
}
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
index 7baa788e4..69d5dfa73 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
@@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp
}
///
- /// as many elements as possible, slicing them down (keeping the remainder).
+ /// as many elements as possible, slicing them down (keeping the remainder).
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertByteToNormalizedFloatReduce(
+ internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan source,
ref Span dest)
{
@@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp
if (adjustedCount > 0)
{
- BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount));
+ ByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount));
source = source.Slice(adjustedCount);
dest = dest.Slice(adjustedCount);
@@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp
}
///
- /// as many elements as possible, slicing them down (keeping the remainder).
+ /// as many elements as possible, slicing them down (keeping the remainder).
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
+ internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan source,
ref Span dest)
{
@@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp
if (adjustedCount > 0)
{
- BulkConvertNormalizedFloatToByteClampOverflows(
+ NormalizedFloatToByteSaturate(
source.Slice(0, adjustedCount),
dest.Slice(0, adjustedCount));
@@ -99,9 +99,9 @@ namespace SixLabors.ImageSharp
}
///
- /// Implementation , which is faster on new RyuJIT runtime.
+ /// Implementation , which is faster on new RyuJIT runtime.
///
- internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest)
{
VerifySpanInput(source, dest, Vector.Count);
@@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp
}
///
- /// Implementation of , which is faster on new .NET runtime.
+ /// Implementation of , which is faster on new .NET runtime.
///
- internal static void BulkConvertNormalizedFloatToByteClampOverflows(
+ internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan source,
Span dest)
{
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
index 565ea08f5..f16c91b40 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.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.
using System;
@@ -19,10 +19,10 @@ namespace SixLabors.ImageSharp
public static class FallbackIntrinsics128
{
///
- /// as many elements as possible, slicing them down (keeping the remainder).
+ /// as many elements as possible, slicing them down (keeping the remainder).
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertByteToNormalizedFloatReduce(
+ internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan source,
ref Span dest)
{
@@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp
if (adjustedCount > 0)
{
- BulkConvertByteToNormalizedFloat(
+ ByteToNormalizedFloat(
source.Slice(0, adjustedCount),
dest.Slice(0, adjustedCount));
@@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp
}
///
- /// as many elements as possible, slicing them down (keeping the remainder).
+ /// as many elements as possible, slicing them down (keeping the remainder).
///
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
+ internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan source,
ref Span dest)
{
@@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp
if (adjustedCount > 0)
{
- BulkConvertNormalizedFloatToByteClampOverflows(
+ NormalizedFloatToByteSaturate(
source.Slice(0, adjustedCount),
dest.Slice(0, adjustedCount));
@@ -67,10 +67,10 @@ namespace SixLabors.ImageSharp
}
///
- /// Implementation of using .
+ /// Implementation of using .
///
[MethodImpl(InliningOptions.ColdPath)]
- internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest)
{
VerifySpanInput(source, dest, 4);
@@ -99,10 +99,10 @@ namespace SixLabors.ImageSharp
}
///
- /// Implementation of using .
+ /// Implementation of using .
///
[MethodImpl(InliningOptions.ColdPath)]
- internal static void BulkConvertNormalizedFloatToByteClampOverflows(
+ internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan source,
Span dest)
{
@@ -125,10 +125,7 @@ namespace SixLabors.ImageSharp
Vector4 s = Unsafe.Add(ref sBase, i);
s *= maxBytes;
s += half;
-
- // I'm not sure if Vector4.Clamp() is properly implemented with intrinsics.
- s = Vector4.Max(Vector4.Zero, s);
- s = Vector4.Min(maxBytes, s);
+ s = Vector4Utilities.FastClamp(s, Vector4.Zero, maxBytes);
ref ByteVector4 d = ref Unsafe.Add(ref dBase, i);
d.X = (byte)s.X;
@@ -148,4 +145,4 @@ namespace SixLabors.ImageSharp
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs
index 4c34e28bc..0dc45d887 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs
@@ -15,9 +15,10 @@ namespace SixLabors.ImageSharp
internal static partial class SimdUtils
{
///
- /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte.
+ /// Gets a value indicating whether code is being JIT-ed to AVX2 instructions
+ /// where both float and integer registers are of size 256 byte.
///
- public static bool IsAvx2CompatibleArchitecture { get; } =
+ public static bool HasVector8 { get; } =
Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8;
///
@@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 PseudoRound(this Vector4 v)
{
- var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1));
+ var sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1));
return v + (sign * 0.5f);
}
@@ -60,16 +61,18 @@ namespace SixLabors.ImageSharp
/// The source span of bytes
/// The destination span of floats
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest)
+ internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest)
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
#if SUPPORTS_EXTENDED_INTRINSICS
- ExtendedIntrinsics.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest);
+ ExtendedIntrinsics.ByteToNormalizedFloatReduce(ref source, ref dest);
#else
- BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest);
+ BasicIntrinsics256.ByteToNormalizedFloatReduce(ref source, ref dest);
#endif
- FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest);
+
+ // Also deals with the remainder from previous conversions:
+ FallbackIntrinsics128.ByteToNormalizedFloatReduce(ref source, ref dest);
// Deal with the remainder:
if (source.Length > 0)
@@ -87,16 +90,20 @@ namespace SixLabors.ImageSharp
/// The source span of floats
/// The destination span of bytes
[MethodImpl(InliningOptions.ShortMethod)]
- internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest)
+ internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest)
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
-#if SUPPORTS_EXTENDED_INTRINSICS
- ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest);
+#if SUPPORTS_RUNTIME_INTRINSICS
+ Avx2Intrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest);
+#elif SUPPORTS_EXTENDED_INTRINSICS
+ ExtendedIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest);
#else
- BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest);
+ BasicIntrinsics256.NormalizedFloatToByteSaturateReduce(ref source, ref dest);
#endif
- FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest);
+
+ // Also deals with the remainder from previous conversions:
+ FallbackIntrinsics128.NormalizedFloatToByteSaturateReduce(ref source, ref dest);
// Deal with the remainder:
if (source.Length > 0)
@@ -151,9 +158,9 @@ namespace SixLabors.ImageSharp
private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f);
[Conditional("DEBUG")]
- private static void VerifyIsAvx2Compatible(string operation)
+ private static void VerifyHasVector8(string operation)
{
- if (!IsAvx2CompatibleArchitecture)
+ if (!HasVector8)
{
throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!");
}
diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs
similarity index 85%
rename from src/ImageSharp/Common/Helpers/Vector4Utils.cs
rename to src/ImageSharp/Common/Helpers/Vector4Utilities.cs
index 594a5ff10..9fb4eb790 100644
--- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs
+++ b/src/ImageSharp/Common/Helpers/Vector4Utilities.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.
using System;
@@ -11,8 +11,20 @@ namespace SixLabors.ImageSharp
///
/// Utility methods for the struct.
///
- internal static class Vector4Utils
+ internal static class Vector4Utilities
{
+ ///
+ /// Restricts a vector between a minimum and a maximum value.
+ /// 5x Faster then .
+ ///
+ /// The vector to restrict.
+ /// The minimum value.
+ /// The maximum value.
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static Vector4 FastClamp(Vector4 x, Vector4 min, Vector4 max)
+ => Vector4.Min(Vector4.Max(x, min), max);
+
///
/// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
///
@@ -107,4 +119,4 @@ namespace SixLabors.ImageSharp
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs
deleted file mode 100644
index 7ece2ed56..000000000
--- a/src/ImageSharp/Common/Tuples/Octet.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace SixLabors.ImageSharp.Tuples
-{
- ///
- /// Contains 8 element value tuples of various types.
- ///
- internal static class Octet
- {
- ///
- /// Value tuple of -s.
- ///
- [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))]
- public struct OfUInt32
- {
- [FieldOffset(0 * sizeof(uint))]
- public uint V0;
-
- [FieldOffset(1 * sizeof(uint))]
- public uint V1;
-
- [FieldOffset(2 * sizeof(uint))]
- public uint V2;
-
- [FieldOffset(3 * sizeof(uint))]
- public uint V3;
-
- [FieldOffset(4 * sizeof(uint))]
- public uint V4;
-
- [FieldOffset(5 * sizeof(uint))]
- public uint V5;
-
- [FieldOffset(6 * sizeof(uint))]
- public uint V6;
-
- [FieldOffset(7 * sizeof(uint))]
- public uint V7;
-
- public override string ToString()
- {
- return $"{nameof(Octet)}.{nameof(OfUInt32)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})";
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- public void LoadFrom(ref OfByte src)
- {
- this.V0 = src.V0;
- this.V1 = src.V1;
- this.V2 = src.V2;
- this.V3 = src.V3;
- this.V4 = src.V4;
- this.V5 = src.V5;
- this.V6 = src.V6;
- this.V7 = src.V7;
- }
- }
-
- ///
- /// Value tuple of -s
- ///
- [StructLayout(LayoutKind.Explicit, Size = 8)]
- public struct OfByte
- {
- [FieldOffset(0)]
- public byte V0;
-
- [FieldOffset(1)]
- public byte V1;
-
- [FieldOffset(2)]
- public byte V2;
-
- [FieldOffset(3)]
- public byte V3;
-
- [FieldOffset(4)]
- public byte V4;
-
- [FieldOffset(5)]
- public byte V5;
-
- [FieldOffset(6)]
- public byte V6;
-
- [FieldOffset(7)]
- public byte V7;
-
- public override string ToString()
- {
- return $"{nameof(Octet)}.{nameof(OfByte)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})";
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- public void LoadFrom(ref OfUInt32 src)
- {
- this.V0 = (byte)src.V0;
- this.V1 = (byte)src.V1;
- this.V2 = (byte)src.V2;
- this.V3 = (byte)src.V3;
- this.V4 = (byte)src.V4;
- this.V5 = (byte)src.V5;
- this.V6 = (byte)src.V6;
- this.V7 = (byte)src.V7;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs
new file mode 100644
index 000000000..71e7da801
--- /dev/null
+++ b/src/ImageSharp/Common/Tuples/Octet{T}.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Tuples
+{
+ ///
+ /// Contains 8 element value tuples of various types.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Octet
+ where T : unmanaged
+ {
+ public T V0;
+ public T V1;
+ public T V2;
+ public T V3;
+ public T V4;
+ public T V5;
+ public T V6;
+ public T V7;
+
+ ///
+ public override readonly string ToString()
+ {
+ return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})";
+ }
+ }
+
+ ///
+ /// Extension methods for the type.
+ ///
+ internal static class OctetExtensions
+ {
+ ///
+ /// Loads the fields in a target of from one of type.
+ ///
+ /// The target of instance.
+ /// The source of instance.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void LoadFrom(ref this Octet destination, ref Octet source)
+ {
+ destination.V0 = source.V0;
+ destination.V1 = source.V1;
+ destination.V2 = source.V2;
+ destination.V3 = source.V3;
+ destination.V4 = source.V4;
+ destination.V5 = source.V5;
+ destination.V6 = source.V6;
+ destination.V7 = source.V7;
+ }
+
+ ///
+ /// Loads the fields in a target of from one of type.
+ ///
+ /// The target of instance.
+ /// The source of instance.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void LoadFrom(ref this Octet destination, ref Octet source)
+ {
+ destination.V0 = (byte)source.V0;
+ destination.V1 = (byte)source.V1;
+ destination.V2 = (byte)source.V2;
+ destination.V3 = (byte)source.V3;
+ destination.V4 = (byte)source.V4;
+ destination.V5 = (byte)source.V5;
+ destination.V6 = (byte)source.V6;
+ destination.V7 = (byte)source.V7;
+ }
+ }
+}
diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs
index b3a32deee..1fdae0d5d 100644
--- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs
+++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs
@@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tuples
/// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void RoundAndDownscalePreAvx2(float downscaleFactor)
+ internal void RoundAndDownscalePreVector8(float downscaleFactor)
{
ref Vector a = ref Unsafe.As>(ref this.A);
a = a.FastRound();
@@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tuples
/// TODO: Move it somewhere else.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void RoundAndDownscaleAvx2(float downscaleFactor)
+ internal void RoundAndDownscaleVector8(float downscaleFactor)
{
ref Vector self = ref Unsafe.As>(ref this);
Vector v = self;
@@ -79,4 +79,4 @@ namespace SixLabors.ImageSharp.Tuples
return $"{nameof(Vector4Pair)}({this.A}, {this.B})";
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 619be880a..47c7c54ea 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp
/// The default value is 1MB.
///
///
- /// Currently only used by Resize.
+ /// Currently only used by Resize. If the working buffer is expected to be discontiguous,
+ /// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used.
///
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
index a404ab418..a956f19c7 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.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.
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -28,11 +29,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public Image Decode(Configuration configuration, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
- return new BmpDecoderCore(configuration, this).Decode(stream);
+ var decoder = new BmpDecoderCore(configuration, this);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 8d82d28fb..dfdbb22fa 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -114,6 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.options = options;
}
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
+
///
/// Decodes the image from the specified this._stream and sets
/// the data to image.
@@ -126,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// The decoded image.
public Image Decode(Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
try
{
@@ -251,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The output pixel buffer containing the decoded image.
/// Whether the bitmap is inverted.
private void ReadBitFields(Buffer2D pixels, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
if (this.infoHeader.BitsPerPixel == 16)
{
@@ -291,27 +296,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRle(BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
TPixel color = default;
- using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
- using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
+ using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean))
{
Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
- if (compression == BmpCompression.RLE8)
+ Span undefinedPixelsSpan = undefinedPixels.Memory.Span;
+ Span bufferSpan = buffer.Memory.Span;
+ if (compression is BmpCompression.RLE8)
{
- this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
+ this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
else
{
- this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
+ this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
- Span bufferRow = buffer.GetRowSpan(y);
+ int rowStartIdx = y * width;
+ Span bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span pixelRow = pixels.GetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
@@ -321,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
for (int x = 0; x < width; x++)
{
byte colorIdx = bufferRow[x];
- if (undefinedPixels[x, y])
+ if (undefinedPixelsSpan[rowStartIdx + x])
{
switch (this.options.RleSkippedPixelHandling)
{
@@ -368,16 +376,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRle24(Buffer2D pixels, int width, int height, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
TPixel color = default;
using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean))
- using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
+ using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean))
using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean))
{
Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
+ Span undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span bufferSpan = buffer.GetSpan();
- this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
+
+ this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
@@ -386,11 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
- int rowStartIdx = y * width * 3;
+ var yMulWidth = y * width;
+ int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + (x * 3);
- if (undefinedPixels[x, y])
+ if (undefinedPixelsSpan[yMulWidth + x])
{
switch (this.options.RleSkippedPixelHandling)
{
@@ -803,7 +814,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// the bytes per color palette entry's can be 3 bytes instead of 4.
/// Whether the bitmap is inverted.
private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// Pixels per byte (bits per pixel).
int ppb = 8 / bitsPerPixel;
@@ -861,7 +872,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The bitmask for the green channel.
/// The bitmask for the blue channel.
private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int padding = CalculatePadding(width, 2);
int stride = (width * 2) + padding;
@@ -928,7 +939,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRgb24(Buffer2D pixels, int width, int height, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int padding = CalculatePadding(width, 3);
@@ -957,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int padding = CalculatePadding(width, 4);
@@ -987,7 +998,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The height of the bitmap.
/// Whether the bitmap is inverted.
private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int padding = CalculatePadding(width, 4);
@@ -1088,7 +1099,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The bitmask for the blue channel.
/// The bitmask for the alpha channel.
private void ReadRgb32BitFields(Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
TPixel color = default;
int padding = CalculatePadding(width, 4);
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index 612675c33..9c05ae2d5 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 1c7c606ca..7d2799503 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Bmp
@@ -87,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
- this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256);
+ this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
}
///
@@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The to encode from.
/// The to encode the image data to.
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@@ -202,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The containing pixel data.
///
private void WriteImage(Stream stream, ImageFrame image)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Buffer2D pixels = image.PixelBuffer;
switch (this.bitsPerPixel)
@@ -234,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The to write to.
/// The containing pixel data.
private void Write32Bit(Stream stream, Buffer2D pixels)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
{
@@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The to write to.
/// The containing pixel data.
private void Write24Bit(Stream stream, Buffer2D pixels)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
{
@@ -282,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The to write to.
/// The containing pixel data.
private void Write16Bit(Stream stream, Buffer2D pixels)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
{
@@ -308,7 +309,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The to write to.
/// The containing pixel data.
private void Write8Bit(Stream stream, ImageFrame image)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
bool isL8 = typeof(TPixel) == typeof(L8);
using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean))
@@ -333,38 +334,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The containing pixel data.
/// A byte span of size 1024 for the color palette.
private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
- using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image))
+ using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration);
+ using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds());
+
+ ReadOnlySpan quantizedColors = quantized.Palette.Span;
+ var color = default(Rgba32);
+
+ // TODO: Use bulk conversion here for better perf
+ int idx = 0;
+ foreach (TPixel quantizedColor in quantizedColors)
{
- ReadOnlySpan quantizedColors = quantized.Palette.Span;
- var color = default(Rgba32);
+ quantizedColor.ToRgba32(ref color);
+ colorPalette[idx] = color.B;
+ colorPalette[idx + 1] = color.G;
+ colorPalette[idx + 2] = color.R;
- // TODO: Use bulk conversion here for better perf
- int idx = 0;
- foreach (TPixel quantizedColor in quantizedColors)
- {
- quantizedColor.ToRgba32(ref color);
- colorPalette[idx] = color.B;
- colorPalette[idx + 1] = color.G;
- colorPalette[idx + 2] = color.R;
-
- // Padding byte, always 0.
- colorPalette[idx + 3] = 0;
- idx += 4;
- }
+ // Padding byte, always 0.
+ colorPalette[idx + 3] = 0;
+ idx += 4;
+ }
+
+ stream.Write(colorPalette);
- stream.Write(colorPalette);
+ for (int y = image.Height - 1; y >= 0; y--)
+ {
+ ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y);
+ stream.Write(pixelSpan);
- for (int y = image.Height - 1; y >= 0; y--)
+ for (int i = 0; i < this.padding; i++)
{
- ReadOnlySpan pixelSpan = quantized.GetRowSpan(y);
- stream.Write(pixelSpan);
-
- for (int i = 0; i < this.padding; i++)
- {
- stream.WriteByte(0);
- }
+ stream.WriteByte(0);
}
}
}
@@ -377,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The containing pixel data.
/// A byte span of size 1024 for the color palette.
private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// Create a color palette with 256 different gray values.
for (int i = 0; i <= 255; i++)
diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs
index c9d631da0..06c4b3fc6 100644
--- a/src/ImageSharp/Formats/Gif/GifConstants.cs
+++ b/src/ImageSharp/Formats/Gif/GifConstants.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
using System.Text;
@@ -21,11 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public const string FileVersion = "89a";
- ///
- /// The ASCII encoded bytes used to identify the GIF file.
- ///
- internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion);
-
///
/// The extension block introducer !.
///
@@ -51,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public const string NetscapeApplicationIdentification = "NETSCAPE2.0";
- ///
- /// The ASCII encoded application identification bytes.
- ///
- internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification);
-
///
/// The Netscape looping application sub block size.
///
@@ -110,5 +101,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The collection of file extensions that equate to a Gif.
///
public static readonly IEnumerable FileExtensions = new[] { "gif" };
+
+ ///
+ /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ).
+ ///
+ internal static ReadOnlySpan MagicNumber => new[]
+ {
+ (byte)'G', (byte)'I', (byte)'F',
+ (byte)'8', (byte)'9', (byte)'a'
+ };
+
+ ///
+ /// Gets the ASCII encoded application identification bytes (representing ).
+ ///
+ internal static ReadOnlySpan NetscapeApplicationIdentificationBytes => new[]
+ {
+ (byte)'N', (byte)'E', (byte)'T',
+ (byte)'S', (byte)'C', (byte)'A',
+ (byte)'P', (byte)'E',
+ (byte)'2', (byte)'.', (byte)'0'
+ };
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index 7691ec1aa..caa076553 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,10 +26,22 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public Image Decode(Configuration configuration, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var decoder = new GifDecoderCore(configuration, this);
- return decoder.Decode(stream);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 722c9c899..02267de1a 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -86,10 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
public bool IgnoreMetadata { get; internal set; }
///
- /// Gets the decoding mode for multi-frame images
+ /// Gets the decoding mode for multi-frame images.
///
public FrameDecodingMode DecodingMode { get; }
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height);
+
private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator;
///
@@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The stream containing image data.
/// The decoded image
public Image Decode(Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Image image = null;
ImageFrame previousFrame = null;
@@ -274,9 +279,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
// Could be XMP or something else not supported yet.
- // Back up and skip.
- this.stream.Position -= appLength + 1;
- this.SkipBlock(appLength);
+ // Skip the subblock and terminator.
+ this.SkipBlock(subBlockSize);
return;
}
@@ -344,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The image to decode the information to.
/// The previous frame.
private void ReadFrame(ref Image image, ref ImageFrame previousFrame)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
this.ReadImageDescriptor();
@@ -401,7 +405,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The color table containing the available colors.
/// The
private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Span indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
int imageWidth = this.logicalScreenDescriptor.Width;
@@ -531,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The pixel format.
/// The frame.
private void RestoreToBackground(ImageFrame frame)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
if (this.restoreArea is null)
{
diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs
index 248915cb7..53c4c6f3f 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -4,6 +4,7 @@
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif
@@ -17,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets or sets the quantizer for reducing the color count.
/// Defaults to the
///
- public IQuantizer Quantizer { get; set; } = new OctreeQuantizer();
+ public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;
///
/// Gets or sets the color table mode: Global or local.
@@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
encoder.Encode(image, stream);
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index a691e527e..887540930 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -6,8 +6,6 @@ using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Configuration bound to the encoding operation.
///
- private Configuration configuration;
+ private readonly Configuration configuration;
///
/// A reusable buffer used to reduce allocations.
@@ -70,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The to encode from.
/// The to encode the image data to.
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@@ -81,14 +79,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
// Quantize the image returning a palette.
- IQuantizedFrame quantized;
+ IndexedImageFrame quantized;
using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration))
{
- quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame);
+ quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
}
// Get the number of bits.
- this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
+ this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);
// Write the header.
this.WriteHeader(stream);
@@ -121,15 +119,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
// Clean up.
- quantized?.Dispose();
+ quantized.Dispose();
// TODO: Write extension etc
stream.WriteByte(GifConstants.EndIntroducer);
}
- private void EncodeGlobal(Image image, IQuantizedFrame quantized, int transparencyIndex, Stream stream)
- where TPixel : struct, IPixel
+ private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream)
+ where TPixel : unmanaged, IPixel
{
+ // The palette quantizer can reuse the same pixel map across multiple frames
+ // since the palette is unchanging. This allows a reduction of memory usage across
+ // multi frame gifs using a global palette.
+ EuclideanPixelMap pixelMap = default;
+ bool pixelMapSet = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame frame = image.Frames[i];
@@ -144,25 +147,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
- using (IFrameQuantizer paletteFrameQuantizer =
- new PaletteFrameQuantizer(this.configuration, this.quantizer.Diffuser, quantized.Palette))
+ if (!pixelMapSet)
{
- using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame))
- {
- this.WriteImageData(paletteQuantized, stream);
- }
+ pixelMapSet = true;
+ pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette);
}
+
+ using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, pixelMap);
+ using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
+ this.WriteImageData(paletteQuantized, stream);
}
}
}
- private void EncodeLocal(Image image, IQuantizedFrame quantized, Stream stream)
- where TPixel : struct, IPixel
+ private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream)
+ where TPixel : unmanaged, IPixel
{
ImageFrame previousFrame = null;
GifFrameMetadata previousMeta = null;
- foreach (ImageFrame frame in image.Frames)
+ for (int i = 0; i < image.Frames.Count; i++)
{
+ ImageFrame frame = image.Frames[i];
ImageFrameMetadata metadata = frame.Metadata;
GifFrameMetadata frameMetadata = metadata.GetGifMetadata();
if (quantized is null)
@@ -171,27 +176,30 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength
&& frameMetadata.ColorTableLength > 0)
{
- using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, frameMetadata.ColorTableLength))
+ var options = new QuantizerOptions
{
- quantized = frameQuantizer.QuantizeFrame(frame);
- }
+ Dither = this.quantizer.Options.Dither,
+ DitherScale = this.quantizer.Options.DitherScale,
+ MaxColors = frameMetadata.ColorTableLength
+ };
+
+ using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, options);
+ quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
else
{
- using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration))
- {
- quantized = frameQuantizer.QuantizeFrame(frame);
- }
+ using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration);
+ quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
}
- this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
+ this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length);
this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream);
this.WriteImageDescriptor(frame, true, stream);
this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream);
- quantized?.Dispose();
+ quantized.Dispose();
quantized = null; // So next frame can regenerate it
previousFrame = frame;
previousMeta = frameMetadata;
@@ -206,25 +214,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The .
///
- private int GetTransparentIndex(IQuantizedFrame quantized)
- where TPixel : struct, IPixel
+ private int GetTransparentIndex(IndexedImageFrame quantized)
+ where TPixel : unmanaged, IPixel
{
- // Transparent pixels are much more likely to be found at the end of a palette
+ // Transparent pixels are much more likely to be found at the end of a palette.
int index = -1;
- int length = quantized.Palette.Length;
+ ReadOnlySpan paletteSpan = quantized.Palette.Span;
- using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(length))
- {
- Span rgbaSpan = rgbaBuffer.GetSpan();
- ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan);
- PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan);
+ using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteSpan.Length);
+ Span rgbaSpan = rgbaOwner.GetSpan();
+ PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan);
+ ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan);
- for (int i = quantized.Palette.Length - 1; i >= 0; i--)
+ for (int i = rgbaSpan.Length - 1; i >= 0; i--)
+ {
+ if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default))
{
- if (Unsafe.Add(ref paletteRef, i).Equals(default))
- {
- index = i;
- }
+ index = i;
}
}
@@ -236,7 +242,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The stream to write to.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
+ private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber);
///
/// Writes the logical screen descriptor to the stream.
@@ -324,8 +330,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
return;
}
- foreach (string comment in metadata.Comments)
+ for (var i = 0; i < metadata.Comments.Count; i++)
{
+ string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.CommentLabel;
stream.Write(this.buffer, 0, 2);
@@ -333,7 +340,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
ReadOnlySpan commentSpan = comment.AsSpan();
int idx = 0;
- for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength)
+ for (;
+ idx <= comment.Length - GifConstants.MaxCommentSubBlockLength;
+ idx += GifConstants.MaxCommentSubBlockLength)
{
WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength);
}
@@ -389,7 +398,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The extension to write to the stream.
/// The stream to write to.
- public void WriteExtension(IGifExtension extension, Stream stream)
+ private void WriteExtension(TGifExtension extension, Stream stream)
+ where TGifExtension : struct, IGifExtension
{
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = extension.Label;
@@ -409,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Whether to use the global color table.
/// The stream to write to.
private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
byte packedValue = GifImageDescriptor.GetPackedValue(
localColorTableFlag: hasColorTable,
@@ -435,37 +445,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The pixel format.
/// The to encode.
/// The stream to write to.
- private void WriteColorTable(IQuantizedFrame image, Stream stream)
- where TPixel : struct, IPixel
+ private void WriteColorTable(IndexedImageFrame image, Stream stream)
+ where TPixel : unmanaged, IPixel
{
// The maximum number of colors for the bit depth
- int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
- int pixelCount = image.Palette.Length;
+ int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf();
- using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
- {
- PixelOperations.Instance.ToRgb24Bytes(
- this.configuration,
- image.Palette.Span,
- colorTable.GetSpan(),
- pixelCount);
- stream.Write(colorTable.Array, 0, colorTableLength);
- }
+ using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean);
+ PixelOperations.Instance.ToRgb24Bytes(
+ this.configuration,
+ image.Palette.Span,
+ colorTable.GetSpan(),
+ image.Palette.Length);
+
+ stream.Write(colorTable.Array, 0, colorTableLength);
}
///
/// Writes the image pixel data to the stream.
///
/// The pixel format.
- /// The containing indexed pixels.
+ /// The containing indexed pixels.
/// The stream to write to.
- private void WriteImageData(IQuantizedFrame image, Stream stream)
- where TPixel : struct, IPixel
+ private void WriteImageData(IndexedImageFrame image, Stream stream)
+ where TPixel : unmanaged, IPixel
{
- using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth))
- {
- encoder.Encode(image.GetPixelSpan(), stream);
- }
+ using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth);
+ encoder.Encode(image.GetPixelBufferSpan(), stream);
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs
index b00db6752..5fe86c4dd 100644
--- a/src/ImageSharp/Formats/Gif/GifMetadata.cs
+++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs
@@ -36,10 +36,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Gets or sets the number of times any animation is repeated.
///
- /// 0 means to repeat indefinitely, count is set as play n + 1 times
+ /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1.
///
///
- public ushort RepeatCount { get; set; }
+ public ushort RepeatCount { get; set; } = 1;
///
/// Gets or sets the color table mode.
diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
index eda0c5fb8..056076bf0 100644
--- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
@@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
ent = this.NextPixel(indexedPixels);
- // TODO: PERF: It looks likt hshift could be calculated once statically.
+ // TODO: PERF: It looks like hshift could be calculated once statically.
hshift = 0;
for (fcode = this.hsize; fcode < 65536; fcode *= 2)
{
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
index 8af5b81c2..5e26370ba 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
buffer[0] = GifConstants.ApplicationBlockSize;
// Write NETSCAPE2.0
- GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11));
+ GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11));
// Application Data ----
buffer[12] = 3; // Application block length (always 3)
diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs
index e8e84de7d..7a7fc4b26 100644
--- a/src/ImageSharp/Formats/IImageDecoder.cs
+++ b/src/ImageSharp/Formats/IImageDecoder.cs
@@ -18,8 +18,9 @@ namespace SixLabors.ImageSharp.Formats
/// The configuration for the image.
/// The containing image data.
/// The .
+ // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream)
- where TPixel : struct, IPixel;
+ where TPixel : unmanaged, IPixel;
///
/// Decodes the image from the specified stream to an .
@@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats
/// The configuration for the image.
/// The containing image data.
/// The .
+ // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
}
}
diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs
index 76d831d5a..d5ff4b93c 100644
--- a/src/ImageSharp/Formats/IImageEncoder.cs
+++ b/src/ImageSharp/Formats/IImageEncoder.cs
@@ -18,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats
/// The to encode from.
/// The to encode the image data to.
void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel;
+ where TPixel : unmanaged, IPixel;
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
index 0b69e3f8b..acde84c91 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
@@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public Block8x8(Span coefficients)
{
ref byte selfRef = ref Unsafe.As(ref this);
- ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(coefficients));
+ ref byte sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(coefficients));
Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short));
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
index 23b51f092..8e14ed2c3 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
@@ -99,29 +99,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var CMax4 = new Vector4(maximum);
var COff4 = new Vector4(MathF.Ceiling(maximum / 2));
- this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4);
- this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4);
- this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4);
- this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4);
- this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4);
- this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4);
- this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4);
- this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4);
- this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4);
- this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4);
- this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4);
- this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4);
- this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4);
- this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4);
- this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4);
- this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4);
+ this.V0L = Vector4Utilities.FastClamp(this.V0L + COff4, CMin4, CMax4);
+ this.V0R = Vector4Utilities.FastClamp(this.V0R + COff4, CMin4, CMax4);
+ this.V1L = Vector4Utilities.FastClamp(this.V1L + COff4, CMin4, CMax4);
+ this.V1R = Vector4Utilities.FastClamp(this.V1R + COff4, CMin4, CMax4);
+ this.V2L = Vector4Utilities.FastClamp(this.V2L + COff4, CMin4, CMax4);
+ this.V2R = Vector4Utilities.FastClamp(this.V2R + COff4, CMin4, CMax4);
+ this.V3L = Vector4Utilities.FastClamp(this.V3L + COff4, CMin4, CMax4);
+ this.V3R = Vector4Utilities.FastClamp(this.V3R + COff4, CMin4, CMax4);
+ this.V4L = Vector4Utilities.FastClamp(this.V4L + COff4, CMin4, CMax4);
+ this.V4R = Vector4Utilities.FastClamp(this.V4R + COff4, CMin4, CMax4);
+ this.V5L = Vector4Utilities.FastClamp(this.V5L + COff4, CMin4, CMax4);
+ this.V5R = Vector4Utilities.FastClamp(this.V5R + COff4, CMin4, CMax4);
+ this.V6L = Vector4Utilities.FastClamp(this.V6L + COff4, CMin4, CMax4);
+ this.V6R = Vector4Utilities.FastClamp(this.V6R + COff4, CMin4, CMax4);
+ this.V7L = Vector4Utilities.FastClamp(this.V7L + COff4, CMin4, CMax4);
+ this.V7R = Vector4Utilities.FastClamp(this.V7R + COff4, CMin4, CMax4);
}
///
/// AVX2-only variant for executing and in one step.
///
[MethodImpl(InliningOptions.ShortMethod)]
- public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
+ public void NormalizeColorsAndRoundInplaceVector8(float maximum)
{
var off = new Vector(MathF.Ceiling(maximum / 2));
var max = new Vector(maximum);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
index 176591972..a1a6b0172 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
@@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
for (int j = 0; j < 2; j++)
{
char side = j == 0 ? 'L' : 'R';
- Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n");
+ Write($"this.V{i}{side} = Vector4Utilities.FastClamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n");
}
}
PopIndent();
@@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// AVX2-only variant for executing and in one step.
///
[MethodImpl(InliningOptions.ShortMethod)]
- public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
+ public void NormalizeColorsAndRoundInplaceVector8(float maximum)
{
var off = new Vector(MathF.Ceiling(maximum / 2));
var max = new Vector(maximum);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
similarity index 78%
rename from src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
rename to src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
index 6bf9c8483..064ca7553 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
@@ -15,29 +15,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors.
///
[MethodImpl(InliningOptions.ShortMethod)]
- public void CopyTo(in BufferArea area, int horizontalScale, int verticalScale)
+ public void ScaledCopyTo(in BufferArea area, int horizontalScale, int verticalScale)
+ {
+ ref float areaOrigin = ref area.GetReferenceToOrigin();
+ this.ScaledCopyTo(ref areaOrigin, area.Stride, horizontalScale, verticalScale);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
- this.Copy1x1Scale(area);
+ this.Copy1x1Scale(ref areaOrigin, areaStride);
return;
}
if (horizontalScale == 2 && verticalScale == 2)
{
- this.Copy2x2Scale(area);
+ this.Copy2x2Scale(ref areaOrigin, areaStride);
return;
}
// TODO: Optimize: implement all cases with scale-specific, loopless code!
- this.CopyArbitraryScale(area, horizontalScale, verticalScale);
+ this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale);
}
- public void Copy1x1Scale(in BufferArea destination)
+ public void Copy1x1Scale(ref float areaOrigin, int areaStride)
{
ref byte selfBase = ref Unsafe.As(ref this);
- ref byte destBase = ref Unsafe.As(ref destination.GetReferenceToOrigin());
- int destStride = destination.Stride * sizeof(float);
+ ref byte destBase = ref Unsafe.As(ref areaOrigin);
+ int destStride = areaStride * sizeof(float);
CopyRowImpl(ref selfBase, ref destBase, destStride, 0);
CopyRowImpl(ref selfBase, ref destBase, destStride, 1);
@@ -57,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
- private void Copy2x2Scale(in BufferArea area)
+ private void Copy2x2Scale(ref float areaOrigin, int areaStride)
{
- ref Vector2 destBase = ref Unsafe.As(ref area.GetReferenceToOrigin());
- int destStride = area.Stride / 2;
+ ref Vector2 destBase = ref Unsafe.As(ref areaOrigin);
+ int destStride = areaStride / 2;
this.WidenCopyRowImpl2x2(ref destBase, 0, destStride);
this.WidenCopyRowImpl2x2(ref destBase, 1, destStride);
@@ -110,10 +117,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
[MethodImpl(InliningOptions.ColdPath)]
- private void CopyArbitraryScale(BufferArea area, int horizontalScale, int verticalScale)
+ private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale)
{
- ref float destBase = ref area.GetReferenceToOrigin();
-
for (int y = 0; y < 8; y++)
{
int yy = y * verticalScale;
@@ -127,16 +132,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
for (int i = 0; i < verticalScale; i++)
{
- int baseIdx = ((yy + i) * area.Stride) + xx;
+ int baseIdx = ((yy + i) * areaStride) + xx;
for (int j = 0; j < horizontalScale; j++)
{
// area[xx + j, yy + i] = value;
- Unsafe.Add(ref destBase, baseIdx + j) = value;
+ Unsafe.Add(ref areaOrigin, baseIdx + j) = value;
}
}
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
index f11b0f8fa..70a34ddcf 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
@@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
/// Destination
[MethodImpl(InliningOptions.ShortMethod)]
- public void CopyTo(Span dest)
+ public void ScaledCopyTo(Span dest)
{
ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest));
ref byte s = ref Unsafe.As(ref this);
@@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Pointer to block
/// Destination
[MethodImpl(InliningOptions.ShortMethod)]
- public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest)
+ public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest)
{
float* fPtr = (float*)blockPtr;
for (int i = 0; i < Size; i++)
@@ -231,9 +231,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// The block pointer.
/// The destination.
[MethodImpl(InliningOptions.ShortMethod)]
- public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest)
+ public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest)
{
- blockPtr->CopyTo(dest);
+ blockPtr->ScaledCopyTo(dest);
}
///
@@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
/// Destination
[MethodImpl(InliningOptions.ShortMethod)]
- public unsafe void CopyTo(float[] dest)
+ public unsafe void ScaledCopyTo(float[] dest)
{
fixed (void* ptr = &this.V0L)
{
@@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Copy raw 32bit floating point data to dest
///
/// Destination
- public unsafe void CopyTo(Span dest)
+ public unsafe void ScaledCopyTo(Span dest)
{
fixed (Vector4* ptr = &this.V0L)
{
@@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public float[] ToArray()
{
var result = new float[Size];
- this.CopyTo(result);
+ this.ScaledCopyTo(result);
return result;
}
@@ -471,9 +471,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
public void NormalizeColorsAndRoundInplace(float maximum)
{
- if (SimdUtils.IsAvx2CompatibleArchitecture)
+ if (SimdUtils.HasVector8)
{
- this.NormalizeColorsAndRoundInplaceAvx2(maximum);
+ this.NormalizeColorsAndRoundInplaceVector8(maximum);
}
else
{
@@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public void LoadFrom(ref Block8x8 source)
{
#if SUPPORTS_EXTENDED_INTRINSICS
- if (SimdUtils.IsAvx2CompatibleArchitecture)
+ if (SimdUtils.HasVector8)
{
this.LoadFromInt16ExtendedAvx2(ref source);
return;
@@ -513,7 +513,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public void LoadFromInt16ExtendedAvx2(ref Block8x8 source)
{
DebugGuard.IsTrue(
- SimdUtils.IsAvx2CompatibleArchitecture,
+ SimdUtils.HasVector8,
"LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!");
ref Vector sRef = ref Unsafe.As>(ref source);
@@ -589,7 +589,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
// sign(dividend) = max(min(dividend, 1), -1)
- var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One);
+ var sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * Offset);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
index 1706b4c1b..09d6a4d1d 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
@@ -90,15 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
if (Vector.Count == 4)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?)
- r.RoundAndDownscalePreAvx2(maxValue);
- g.RoundAndDownscalePreAvx2(maxValue);
- b.RoundAndDownscalePreAvx2(maxValue);
+ r.RoundAndDownscalePreVector8(maxValue);
+ g.RoundAndDownscalePreVector8(maxValue);
+ b.RoundAndDownscalePreVector8(maxValue);
}
- else if (SimdUtils.IsAvx2CompatibleArchitecture)
+ else if (SimdUtils.HasVector8)
{
- r.RoundAndDownscaleAvx2(maxValue);
- g.RoundAndDownscaleAvx2(maxValue);
- b.RoundAndDownscaleAvx2(maxValue);
+ r.RoundAndDownscaleVector8(maxValue);
+ g.RoundAndDownscaleVector8(maxValue);
+ b.RoundAndDownscaleVector8(maxValue);
}
else
{
@@ -114,4 +114,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
index 093ea2f9a..8c1b427ee 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
@@ -13,14 +13,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
internal abstract partial class JpegColorConverter
{
- internal sealed class FromYCbCrSimdAvx2 : JpegColorConverter
+ internal sealed class FromYCbCrSimdVector8 : JpegColorConverter
{
- public FromYCbCrSimdAvx2(int precision)
+ public FromYCbCrSimdVector8(int precision)
: base(JpegColorSpace.YCbCr, precision)
{
}
- public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture;
+ public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.HasVector8;
public override void ConvertToRgba(in ComponentValues values, Span result)
{
@@ -107,4 +107,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
index 61e359869..7ada1b9da 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
@@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
///
/// Returns the corresponding to the given
///
- public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision)
+ public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision)
{
JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace
&& c.Precision == precision);
@@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// Returns the for the YCbCr colorspace that matches the current CPU architecture.
///
private static JpegColorConverter GetYCbCrConverter(int precision) =>
- FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision);
+ FromYCbCrSimdVector8.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdVector8(precision) : new FromYCbCrSimd(precision);
///
/// A stack-only struct to reference the input buffers using -s.
@@ -232,4 +232,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
index 44f9048a5..db4b6a532 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
@@ -68,11 +68,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in .
///
/// The source block.
- /// The destination buffer area.
+ /// Reference to the origin of the destination pixel area.
+ /// The width of the destination pixel buffer.
/// The maximum value derived from the bitdepth.
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
- in BufferArea destArea,
+ ref float destAreaOrigin,
+ int destAreaStride,
float maximumValue)
{
ref Block8x8F b = ref this.SourceBlock;
@@ -88,7 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// To be "more accurate", we need to emulate this by rounding!
this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue);
- this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height);
+ this.WorkspaceBlock1.ScaledCopyTo(
+ ref destAreaOrigin,
+ destAreaStride,
+ this.subSamplingDivisors.Width,
+ this.subSamplingDivisors.Height);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
index 39c8be312..d9fd9ac8b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
@@ -31,12 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
this.Component = component;
this.ImagePostProcessor = imagePostProcessor;
- this.ColorBuffer = memoryAllocator.Allocate2D(
+ this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
+ this.ColorBuffer = memoryAllocator.Allocate2DOveraligned(
imagePostProcessor.PostProcessorBufferSize.Width,
- imagePostProcessor.PostProcessorBufferSize.Height);
+ imagePostProcessor.PostProcessorBufferSize.Height,
+ this.blockAreaSize.Height);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height;
- this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
}
///
@@ -78,6 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component);
float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1;
+ int destAreaStride = this.ColorBuffer.Width;
+
for (int y = 0; y < this.BlockRowsPerStep; y++)
{
int yBlock = this.currentComponentRowInBlocks + y;
@@ -89,26 +92,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int yBuffer = y * this.blockAreaSize.Height;
+ Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock);
- ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow);
+ // see: https://github.com/SixLabors/ImageSharp/issues/824
+ int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width);
- for (int xBlock = 0; xBlock < this.SizeInBlocks.Width; xBlock++)
+ for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
{
- ref Block8x8 block = ref Unsafe.Add(ref blockRowBase, xBlock);
+ ref Block8x8 block = ref blockRow[xBlock];
int xBuffer = xBlock * this.blockAreaSize.Width;
+ ref float destAreaOrigin = ref colorBufferRow[xBuffer];
- BufferArea destArea = this.ColorBuffer.GetArea(
- xBuffer,
- yBuffer,
- this.blockAreaSize.Width,
- this.blockAreaSize.Height);
-
- blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue);
+ blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
}
}
this.currentComponentRowInBlocks += this.BlockRowsPerStep;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
index 0400978d2..5352a0bff 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
@@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// The pixel type
/// The destination image
public void PostProcess(ImageFrame destination)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
this.PixelRowCounter = 0;
@@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// The pixel type
/// The destination image.
public void DoPostProcessorStep(ImageFrame destination)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
@@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// The pixel type
/// The destination image
private void ConvertColorsInto(ImageFrame destination)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
index 54633a5d7..87b486ea6 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
@@ -1,8 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
@@ -12,24 +11,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
internal static class ProfileResolver
{
///
- /// Describes the JFIF specific markers.
+ /// Gets the JFIF specific markers.
///
- public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0");
+ public static ReadOnlySpan JFifMarker => new[]
+ {
+ (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0'
+ };
///
- /// Describes the ICC specific markers.
+ /// Gets the ICC specific markers.
///
- public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0");
+ public static ReadOnlySpan IccMarker => new[]
+ {
+ (byte)'I', (byte)'C', (byte)'C', (byte)'_',
+ (byte)'P', (byte)'R', (byte)'O', (byte)'F',
+ (byte)'I', (byte)'L', (byte)'E', (byte)'\0'
+ };
///
- /// Describes the EXIF specific markers.
+ /// Gets the EXIF specific markers.
///
- public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0");
+ public static ReadOnlySpan ExifMarker => new[]
+ {
+ (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0'
+ };
///
- /// Describes Adobe specific markers .
+ /// Gets the Adobe specific markers .
///
- public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe");
+ public static ReadOnlySpan AdobeMarker => new[]
+ {
+ (byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e'
+ };
///
/// Returns a value indicating whether the passed bytes are a match to the profile identifier.
@@ -43,4 +56,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
&& bytesToCheck.Slice(0, profileIdentifier.Length).SequenceEqual(profileIdentifier);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
index 92482de2a..ba604e891 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// The pixel type to work on
internal ref struct YCbCrForwardConverter
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
///
/// The Y component
@@ -55,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
///
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , )
///
- public void Convert(ImageFrame frame, int x, int y)
+ public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows)
{
- this.pixelBlock.LoadAndStretchEdges(frame, x, y);
+ this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows);
Span rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
index 3d1e22a99..534c66b99 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
@@ -54,24 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value;
}
- public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY)
- where TPixel : struct, IPixel
- {
- if (source.PixelBuffer is Buffer2D buffer)
- {
- this.LoadAndStretchEdges(buffer, sourceX, sourceY);
- }
- else
- {
- throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !");
- }
- }
-
///
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
///
- public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY)
+ public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows)
{
int width = Math.Min(8, source.Width - sourceX);
int height = Math.Min(8, source.Height - sourceY);
@@ -85,15 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
int remainderXCount = 8 - width;
ref byte blockStart = ref Unsafe.As, byte>(ref this);
- ref byte imageStart = ref Unsafe.As(
- ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX));
-
int blockRowSizeInBytes = 8 * Unsafe.SizeOf();
- int imageRowSizeInBytes = source.Width * Unsafe.SizeOf();
for (int y = 0; y < height; y++)
{
- ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes);
+ Span row = currentRows[y];
+
+ ref byte s = ref Unsafe.As(ref row[sourceX]);
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
@@ -127,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
new file mode 100644
index 000000000..8c3daa4d5
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components
+{
+ ///
+ /// Cache 8 pixel rows on the stack, which may originate from different buffers of a .
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal readonly ref struct RowOctet
+ where T : struct
+ {
+ private readonly Span row0;
+ private readonly Span row1;
+ private readonly Span row2;
+ private readonly Span row3;
+ private readonly Span row4;
+ private readonly Span row5;
+ private readonly Span row6;
+ private readonly Span row7;
+
+ public RowOctet(Buffer2D buffer, int startY)
+ {
+ int y = startY;
+ int height = buffer.Height;
+ this.row0 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row1 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row2 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row3 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row4 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row5 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row6 = y < height ? buffer.GetRowSpan(y++) : default;
+ this.row7 = y < height ? buffer.GetRowSpan(y) : default;
+ }
+
+ public Span this[int y]
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get
+ {
+ // No unsafe tricks, since Span can't be used as a generic argument
+ return y switch
+ {
+ 0 => this.row0,
+ 1 => this.row1,
+ 2 => this.row2,
+ 3 => this.row3,
+ 4 => this.row4,
+ 5 => this.row5,
+ 6 => this.row6,
+ 7 => this.row7,
+ _ => ThrowIndexOutOfRangeException()
+ };
+ }
+ }
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ private static Span ThrowIndexOutOfRangeException()
+ {
+ throw new IndexOutOfRangeException();
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
index 059e2052b..669abad28 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
@@ -34,12 +34,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public fixed byte Data[Size];
///
- /// Unzig maps from the zigzag ordering to the natural ordering. For example,
- /// unzig[3] is the column and row of the fourth element in zigzag order. The
- /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
+ /// Gets the unzigs map, which maps from the zigzag ordering to the natural ordering.
+ /// For example, unzig[3] is the column and row of the fourth element in zigzag order.
+ /// The value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
///
- private static readonly byte[] Unzig =
- new byte[Size]
+ private static ReadOnlySpan Unzig => new byte[]
{
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
@@ -75,8 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public static ZigZag CreateUnzigTable()
{
ZigZag result = default;
- byte* unzigPtr = result.Data;
- Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, Size);
+ ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig);
+ ref byte destinationRef = ref Unsafe.AsRef(result.Data);
+
+ Unzig.CopyTo(new Span(result.Data, Size));
+
return result;
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
index 4e1c0c1be..b1144508e 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@@ -18,14 +19,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public Image Decode(Configuration configuration, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Guard.NotNull(stream, nameof(stream));
- using (var decoder = new JpegDecoderCore(configuration, this))
+ using var decoder = new JpegDecoderCore(configuration, this);
+ try
{
return decoder.Decode(stream);
}
+ catch (InvalidMemoryOperationException ex)
+ {
+ (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight);
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 9b6a72cc9..951fec1d4 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The stream, where the image should be.
/// The decoded image.
public Image Decode(Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
this.ParseStream(stream);
this.InitExifProfile();
@@ -958,7 +958,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The pixel format.
/// The .
private Image PostProcessIntoImage()
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
if (this.ImageWidth == 0 || this.ImageHeight == 0)
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index d649d3041..1c4035a98 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
@@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The to encode from.
/// The to encode the image data to.
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var encoder = new JpegEncoderCore(this);
encoder.Encode(image, stream);
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index cd3c19aa3..32f4d2287 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The image to write from.
/// The stream to write to.
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@@ -393,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
private void Encode444(Image pixels)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter)
@@ -409,12 +410,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
var pixelConverter = YCbCrForwardConverter.Create();
+ ImageFrame frame = pixels.Frames.RootFrame;
+ Buffer2D pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 8)
{
+ var currentRows = new RowOctet(pixelBuffer, y);
+
for (int x = 0; x < pixels.Width; x += 8)
{
- pixelConverter.Convert(pixels.Frames.RootFrame, x, y);
+ pixelConverter.Convert(frame, x, y, currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
@@ -886,7 +891,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
private void WriteStartOfScan(Image image)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// TODO: We should allow grayscale writing.
@@ -913,7 +918,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The pixel format.
/// The pixel accessor providing access to the image pixels.
private void Encode420(Image pixels)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Block8x8F b = default;
@@ -935,6 +940,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
+ ImageFrame frame = pixels.Frames.RootFrame;
+ Buffer2D pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 16)
{
@@ -945,7 +952,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
- pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff);
+ // TODO: Try pushing this to the outer loop!
+ var currentRows = new RowOctet(pixelBuffer, y + yOff);
+
+ pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;
diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs
index 66d04f39f..1683519c2 100644
--- a/src/ImageSharp/Formats/PixelTypeInfo.cs
+++ b/src/ImageSharp/Formats/PixelTypeInfo.cs
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats
public int BitsPerPixel { get; }
internal static PixelTypeInfo Create()
- where TPixel : struct, IPixel =>
+ where TPixel : unmanaged, IPixel =>
new PixelTypeInfo(Unsafe.SizeOf() * 8);
}
}
diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs
index 632460ec4..247bb3c75 100644
--- a/src/ImageSharp/Formats/Png/PngConstants.cs
+++ b/src/ImageSharp/Formats/Png/PngConstants.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
using System.Text;
@@ -36,21 +37,6 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public static readonly IEnumerable FileExtensions = new[] { "png" };
- ///
- /// The header bytes identifying a Png.
- ///
- public static readonly byte[] HeaderBytes =
- {
- 0x89, // Set the high bit.
- 0x50, // P
- 0x4E, // N
- 0x47, // G
- 0x0D, // Line ending CRLF
- 0x0A, // Line ending CRLF
- 0x1A, // EOF
- 0x0A // LF
- };
-
///
/// The header bytes as a big-endian coded ulong.
///
@@ -77,5 +63,20 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The minimum length of a keyword in a text chunk is 1 byte.
///
public const int MinTextKeywordLength = 1;
+
+ ///
+ /// Gets the header bytes identifying a Png.
+ ///
+ public static ReadOnlySpan HeaderBytes => new byte[]
+ {
+ 0x89, // Set the high bit.
+ 0x50, // P
+ 0x4E, // N
+ 0x47, // G
+ 0x0D, // Line ending CRLF
+ 0x0A, // Line ending CRLF
+ 0x1A, // EOF
+ 0x0A // LF
+ };
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs
index eea9e54c0..d605577e7 100644
--- a/src/ImageSharp/Formats/Png/PngDecoder.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoder.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
+using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Png
@@ -41,10 +42,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The containing image data.
/// The decoded image.
public Image Decode(Configuration configuration, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var decoder = new PngDecoderCore(configuration, this);
- return decoder.Decode(stream);
+
+ try
+ {
+ return decoder.Decode(stream);
+ }
+ catch (InvalidMemoryOperationException ex)
+ {
+ Size dims = decoder.Dimensions;
+
+ // TODO: use InvalidImageContentException here, if we decide to define it
+ // https://github.com/SixLabors/ImageSharp/issues/1110
+ throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex);
+ }
}
///
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 69b341c8d..4d7de4161 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private int currentRow = Adam7.FirstRow[0];
///
- /// The current number of bytes read in the current scanline
+ /// The current number of bytes read in the current scanline.
///
private int currentRowBytesRead;
@@ -132,20 +132,25 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ignoreMetadata = options.IgnoreMetadata;
}
+ ///
+ /// Gets the dimensions of the image.
+ ///
+ public Size Dimensions => new Size(this.header.Width, this.header.Height);
+
///
/// Decodes the stream to the image.
///
/// The pixel format.
- /// The stream containing image data.
+ /// The stream containing image data.
///
/// Thrown if the stream does not contain and end chunk.
///
///
/// Thrown if the image is larger than the maximum allowable size.
///
- /// The decoded image
+ /// The decoded image.
public Image Decode(Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
var metadata = new ImageMetadata();
PngMetadata pngMetadata = metadata.GetPngMetadata();
@@ -373,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The metadata information for the image
/// The image that we will populate
private void InitializeImage(ImageMetadata metadata, out Image image)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
image = new Image(this.configuration, this.header.Width, this.header.Height, metadata);
this.bytesPerPixel = this.CalculateBytesPerPixel();
@@ -466,7 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The pixel data.
/// The png metadata
private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
@@ -492,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The image to decode to.
/// The png metadata
private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
while (this.currentRow < this.header.Height)
{
@@ -548,7 +553,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The current image.
/// The png metadata.
private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
int pass = 0;
int width = this.header.Width;
@@ -637,7 +642,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The image
/// The png metadata.
private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Span rowSpan = pixels.GetPixelRowSpan(this.currentRow);
@@ -721,7 +726,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The column start index. Always 0 for none interlaced images.
/// The column increment. Always 1 for none interlaced images.
private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
// Trim the first marker byte from the buffer
ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
@@ -1096,10 +1101,7 @@ namespace SixLabors.ImageSharp.Formats.Png
while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
{
- // Not a valid chunk so we skip back all but one of the four bytes we have just read.
- // That lets us read one byte at a time until we reach a known chunk.
- this.currentStream.Position -= 3;
-
+ // Not a valid chunk so try again until we reach a known chunk.
if (!this.TryReadChunkLength(out length))
{
chunk = default;
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index 3e46ad29e..e654036a8 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The to encode from.
/// The to encode the image data to.
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
{
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 69a80e024..45e1ffd2d 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The to encode from.
/// The to encode the image data to.
public void Encode(Image image, Stream stream)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@@ -146,10 +146,10 @@ namespace SixLabors.ImageSharp.Formats.Png
ImageMetadata metadata = image.Metadata;
PngMetadata pngMetadata = metadata.GetPngMetadata();
PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
- IQuantizedFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
+ IndexedImageFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);
- stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
+ stream.Write(PngConstants.HeaderBytes);
this.WriteHeaderChunk(stream);
this.WritePaletteChunk(stream, quantized);
@@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The pixel format.
/// The image row span.
private void CollectGrayscaleBytes(ReadOnlySpan rowSpan)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
Span rawScanlineSpan = this.currentScanline.GetSpan();
@@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The pixel format.
/// The row span.
private void CollectTPixelBytes(ReadOnlySpan rowSpan)
- where TPixel : struct, IPixel
+ where TPixel : unmanaged, IPixel
{
Span rawScanlineSpan = this.currentScanline.GetSpan();
@@ -371,8 +371,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The row span.
/// The quantized pixels. Can be null.
/// The row.
- private void CollectPixelBytes(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row)
- where TPixel : struct, IPixel
+ private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row)
+ where TPixel : unmanaged, IPixel
{
switch (this.options.ColorType)
{
@@ -380,12 +380,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.bitDepth < 8)
{
- PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth);
+ PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth);
}
else
{
- int stride = this.currentScanline.Length();
- quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan());
+ quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan());
}
break;
@@ -440,8 +439,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The quantized pixels. Can be null.
/// The row.
/// The
- private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row)
- where TPixel : struct, IPixel
+ private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row)
+ where TPixel : unmanaged, IPixel
{
this.CollectPixelBytes(rowSpan, quantized, row);
return this.FilterPixelBytes();
@@ -546,59 +545,54 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The pixel format.
/// The containing image data.
/// The quantized frame.
- private void WritePaletteChunk(Stream stream, IQuantizedFrame quantized)
- where TPixel : struct, IPixel
+ private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized)
+ where TPixel : unmanaged, IPixel
{
- if (quantized == null)
+ if (quantized is null)
{
return;
}
// Grab the palette and write it to the stream.
ReadOnlySpan palette = quantized.Palette.Span;
- int paletteLength = Math.Min(palette.Length, 256);
- int colorTableLength = paletteLength * 3;
- bool anyAlpha = false;
+ int paletteLength = palette.Length;
+ int colorTableLength = paletteLength * Unsafe.SizeOf();
+ bool hasAlpha = false;
- using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
- using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength))
- {
- ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan());
- ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
- ReadOnlySpan quantizedSpan = quantized.GetPixelSpan();
-
- Rgba32 rgba = default;
-
- for (int i = 0; i < paletteLength; i++)
- {
- if (quantizedSpan.IndexOf((byte)i) > -1)
- {
- int offset = i * 3;
- palette[i].ToRgba32(ref rgba);
+ using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength);
+ using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength);
- byte alpha = rgba.A;
+ ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan()));
+ ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
- Unsafe.Add(ref colorTableRef, offset) = rgba.R;
- Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G;
- Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B;
+ // Bulk convert our palette to RGBA to allow assignment to tables.
+ using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteLength);
+ Span rgbaPaletteSpan = rgbaOwner.GetSpan();
+ PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan);
+ ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan);
- if (alpha > this.options.Threshold)
- {
- alpha = byte.MaxValue;
- }
+ // Loop, assign, and extract alpha values from the palette.
+ for (int i = 0; i < paletteLength; i++)
+ {
+ Rgba32 rgba = Unsafe.Add(ref rgbaPaletteRef, i);
+ byte alpha = rgba.A;
- anyAlpha = anyAlpha || alpha < byte.MaxValue;
- Unsafe.Add(ref alphaTableRef, i) = alpha;
- }
+ Unsafe.Add(ref colorTableRef, i) = rgba.Rgb;
+ if (alpha > this.options.Threshold)
+ {
+ alpha = byte.MaxValue;
}
- this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength);
+ hasAlpha = hasAlpha || alpha < byte.MaxValue;
+ Unsafe.Add(ref alphaTableRef, i) = alpha;
+ }
- // Write the transparency data
- if (anyAlpha)
- {
- this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength);
- }
+ this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength);
+
+ // Write the transparency data
+ if (hasAlpha)
+ {
+ this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength);
}
}
@@ -783,8 +777,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The image.
/// The quantized pixel data. Can be null.
/// The stream.
- private void WriteDataChunks(ImageFrame pixels, IQuantizedFrame quantized, Stream stream)
- where TPixel : struct, IPixel