Browse Source

Merge branch 'master' into js/more-efficient-base64

pull/1153/head
James Jackson-South 6 years ago
parent
commit
3e00fef440
  1. 5
      .editorconfig
  2. 4
      .gitattributes
  3. 6
      Directory.Build.props
  4. 1
      ImageSharp.sln
  5. 4
      codecov.yml
  6. 2
      shared-infrastructure
  7. 13
      src/Directory.Build.props
  8. 20
      src/Directory.Build.targets
  9. 167
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  10. 46
      src/ImageSharp/Advanced/AotCompilerTools.cs
  11. 2
      src/ImageSharp/Advanced/IImageVisitor.cs
  12. 2
      src/ImageSharp/Advanced/IPixelSource.cs
  13. 19
      src/ImageSharp/Advanced/IRowIntervalOperation.cs
  14. 23
      src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
  15. 17
      src/ImageSharp/Advanced/IRowOperation.cs
  16. 22
      src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs
  17. 8
      src/ImageSharp/Advanced/ParallelExecutionSettings.cs
  18. 198
      src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
  19. 288
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  20. 160
      src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs
  21. 205
      src/ImageSharp/Color/Color.NamedColors.cs
  22. 222
      src/ImageSharp/Color/Color.WernerPalette.cs
  23. 95
      src/ImageSharp/Color/Color.cs
  24. 2
      src/ImageSharp/ColorSpaces/Cmyk.cs
  25. 4
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
  26. 2
      src/ImageSharp/Common/Exceptions/ImageFormatException.cs
  27. 14
      src/ImageSharp/Common/Extensions/EnumerableExtensions.cs
  28. 4
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  29. 2
      src/ImageSharp/Common/Helpers/Buffer2DUtils.cs
  30. 24
      src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
  31. 29
      src/ImageSharp/Common/Helpers/Guard.cs
  32. 36
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  33. 103
      src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs
  34. 58
      src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
  35. 20
      src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
  36. 29
      src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
  37. 35
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  38. 18
      src/ImageSharp/Common/Helpers/Vector4Utilities.cs
  39. 112
      src/ImageSharp/Common/Tuples/Octet.cs
  40. 73
      src/ImageSharp/Common/Tuples/Octet{T}.cs
  41. 6
      src/ImageSharp/Common/Tuples/Vector4Pair.cs
  42. 3
      src/ImageSharp/Configuration.cs
  43. 20
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  44. 53
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  45. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  46. 69
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  47. 31
      src/ImageSharp/Formats/Gif/GifConstants.cs
  48. 18
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  49. 20
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  50. 5
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  51. 140
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  52. 4
      src/ImageSharp/Formats/Gif/GifMetadata.cs
  53. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  54. 2
      src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
  55. 4
      src/ImageSharp/Formats/IImageDecoder.cs
  56. 2
      src/ImageSharp/Formats/IImageEncoder.cs
  57. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  58. 34
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  59. 4
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
  60. 37
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  61. 24
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  62. 16
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  63. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
  64. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  65. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  66. 28
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  67. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  68. 35
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  69. 6
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  70. 25
      src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs
  71. 68
      src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs
  72. 16
      src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
  73. 14
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  74. 4
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  75. 2
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  76. 22
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  77. 2
      src/ImageSharp/Formats/PixelTypeInfo.cs
  78. 31
      src/ImageSharp/Formats/Png/PngConstants.cs
  79. 17
      src/ImageSharp/Formats/Png/PngDecoder.cs
  80. 30
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  81. 2
      src/ImageSharp/Formats/Png/PngEncoder.cs
  82. 110
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  83. 20
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  84. 20
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
  85. 43
      src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
  86. 19
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  87. 21
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  88. 2
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  89. 67
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  90. 6
      src/ImageSharp/Image.Decode.cs
  91. 149
      src/ImageSharp/Image.FromBytes.cs
  92. 111
      src/ImageSharp/Image.FromFile.cs
  93. 80
      src/ImageSharp/Image.FromStream.cs
  94. 23
      src/ImageSharp/Image.LoadPixelData.cs
  95. 18
      src/ImageSharp/Image.WrapMemory.cs
  96. 6
      src/ImageSharp/Image.cs
  97. 2
      src/ImageSharp/ImageExtensions.Internal.cs
  98. 2
      src/ImageSharp/ImageExtensions.cs
  99. 8
      src/ImageSharp/ImageFrame.LoadPixelData.cs
  100. 5
      src/ImageSharp/ImageFrame.cs

5
.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

4
.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

6
Directory.Build.props

@ -95,16 +95,16 @@
<!-- Contains RemoteExecutor. Taken from: https://github.com/dotnet/runtime/blob/master/NuGet.config -->
https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json;
</RestoreSources>
<SixLaborsPublicKey>002400000c8000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97</SixLaborsPublicKey>
<SixLaborsPublicKey>00240000048000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97</SixLaborsPublicKey>
<UseSharedCompilation>true</UseSharedCompilation>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<!-- Package references and additional files which are consumed by all projects -->
<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers.Toolset" IsImplicitlyDefined="true" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" IsImplicitlyDefined="true" />
<!--TODO: Enable this once tests Stylecop issues are fixed-->
<!--<PackageReference Include="StyleCop.Analyzers" IsImplicitlyDefined="true" />-->
<PackageReference Include="StyleCop.Analyzers" IsImplicitlyDefined="true" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)shared-infrastructure\stylecop.json" />
<!--NuGet package icon source-->
<None Include="$(MSBuildThisFileDirectory)shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="\icon.png" />

1
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

4
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

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5
Subproject commit ea561c249ba86352fe3b69e612b8072f3652eacb

13
src/Directory.Build.props

@ -27,14 +27,13 @@
</PropertyGroup>
<ItemGroup>
<!--TODO: Delete this once tests Stylecop issues are fixed-->
<PackageReference Include="StyleCop.Analyzers" IsImplicitlyDefined="true" />
<!-- DynamicProxyGenAssembly2 is needed so Moq can use our internals -->
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" PublicKey="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKeyToken=null" />
<InternalsVisibleTo Include="ImageSharp.Benchmarks" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Sandbox46" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" />
<InternalsVisibleTo Include="ImageSharp.Benchmarks" PublicKey="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" PublicKey="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" PublicKey="$(SixLaborsPublicKey)" />
</ItemGroup>
</Project>

20
src/Directory.Build.targets

@ -52,4 +52,24 @@
<!-- https://github.com/Microsoft/vstest/issues/411 -->
<Target Name="VSTest" Condition="'$(IsTestProject)' == 'true'"/>
<ItemGroup>
<!--Shared config files that have to exist at root level.-->
<ConfigFilesToCopy Include="..\..\shared-infrastructure\.editorconfig;..\..\shared-infrastructure\.gitattributes" />
</ItemGroup>
<!--Ensures our config files are up to date.-->
<Target Name="CopyFiles" BeforeTargets="Build">
<Copy SourceFiles="@(ConfigFilesToCopy)"
SkipUnchangedFiles = "true"
DestinationFolder="..\..\" />
</Target>
<!-- Allows regenerating T4-generated files at build time using MsBuild -->
<!-- Enable on Windows OS to build all T4 templates. TODO: XPlat
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
</PropertyGroup>
-->
</Project>

167
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);
/// <summary>
/// Gets the configuration .
/// Gets the configuration.
/// </summary>
/// <param name="source">The source image</param>
/// <returns>Returns the bounds of the image</returns>
@ -48,15 +49,58 @@ namespace SixLabors.ImageSharp.Advanced
=> source?.Configuration ?? Configuration.Default;
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
/// </summary>
/// <param name="source">The source image.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
/// <remarks>
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
/// </remarks>
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
=> source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source));
/// <summary>
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.
/// </summary>
/// <param name="source">The source image.</param>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <returns>The <see cref="IMemoryGroup{T}"/>.</returns>
/// <remarks>
/// Certain Image Processors may invalidate the returned <see cref="IMemoryGroup{T}"/> and all it's buffers,
/// therefore it's not recommended to mutate the image while holding a reference to it's <see cref="IMemoryGroup{T}"/>.
/// </remarks>
public static IMemoryGroup<TPixel> GetPixelMemoryGroup<TPixel>(this Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source image.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.GetPixelMemory().Span;
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
IMemoryGroup<TPixel> 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;
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
@ -65,9 +109,16 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelSpan();
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
return source.Frames.RootFrame.GetPixelSpan();
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
@ -78,8 +129,14 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.PixelBuffer.GetRowSpan(rowIndex);
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
@ -90,59 +147,13 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowSpan(rowIndex);
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image frame</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource<TPixel>)source);
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <returns>A pinnable reference the first root of the pixel buffer.</returns>
[Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")]
public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer();
/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source <see cref="ImageFrame{TPixel}"/></param>
/// <returns>The <see cref="Memory{T}"/></returns>
internal static Memory<TPixel> GetPixelMemory<TPixel>(this ImageFrame<TPixel> source)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return source.PixelBuffer.MemorySource.Memory;
}
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Memory{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The Pixel format.</typeparam>
/// <param name="source">The source <see cref="Image{TPixel}"/></param>
/// <returns>The <see cref="Memory{T}"/></returns>
internal static Memory<TPixel> GetPixelMemory<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return source.Frames.RootFrame.GetPixelMemory();
return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
@ -153,9 +164,15 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.PixelBuffer.GetRowMemory(rowIndex);
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.PixelBuffer.GetSafeRowMemory(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
@ -165,9 +182,15 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
internal static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : struct, IPixel<TPixel>
=> source.Frames.RootFrame.GetPixelRowMemory(rowIndex);
public static Memory<TPixel> GetPixelRowMemory<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
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);
}
/// <summary>
/// Gets the <see cref="MemoryAllocator"/> assigned to 'source'.
@ -176,15 +199,5 @@ namespace SixLabors.ImageSharp.Advanced
/// <returns>Returns the configuration.</returns>
internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
=> GetConfiguration(source).MemoryAllocator;
/// <summary>
/// Returns a reference to the 0th element of the Pixel buffer.
/// Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
/// <param name="source">The source image frame</param>
/// <returns>A reference to the element.</returns>
private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer<TPixel>(IPixelSource<TPixel> source)
where TPixel : struct, IPixel<TPixel>
=> ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan());
}
}

46
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
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void Seed<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// This is we actually call all the individual methods you need to seed.
AotCompileOctreeQuantizer<TPixel>();
AotCompileWuQuantizer<TPixel>();
AotCompilePaletteQuantizer<TPixel>();
AotCompileDithering<TPixel>();
AotCompilePixelOperations<TPixel>();
@ -107,11 +110,12 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer(false)))
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
{
test.AotGetPalette();
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
}
}
@ -120,12 +124,26 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer(false)))
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options))
{
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
}
}
/// <summary>
/// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompilePaletteQuantizer<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
using (var test = (PaletteFrameQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreateFrameQuantizer<TPixel>(Configuration.Default))
{
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
}
}
@ -134,13 +152,15 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileDithering<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var test = new FloydSteinbergDiffuser();
ErrorDither errorDither = ErrorDither.FloydSteinberg;
OrderedDither orderedDither = OrderedDither.Bayer2x2;
TPixel pixel = default;
using (var image = new ImageFrame<TPixel>(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
/// <param name="encoder">The image encoder to seed.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCodec<TPixel>(IImageDecoder decoder, IImageEncoder encoder)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
@ -175,7 +195,7 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompilePixelOperations<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var pixelOp = new PixelOperations<TPixel>();
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);

2
src/ImageSharp/Advanced/IImageVisitor.cs

@ -17,6 +17,6 @@ namespace SixLabors.ImageSharp.Advanced
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>;
where TPixel : unmanaged, IPixel<TPixel>;
}
}

2
src/ImageSharp/Advanced/IPixelSource.cs

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
internal interface IPixelSource<TPixel>
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Gets the pixel buffer.

19
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
{
/// <summary>
/// Defines the contract for an action that operates on a row interval.
/// </summary>
public interface IRowIntervalOperation
{
/// <summary>
/// Invokes the method passing the row interval.
/// </summary>
/// <param name="rows">The row interval.</param>
void Invoke(in RowInterval rows);
}
}

23
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
{
/// <summary>
/// Defines the contract for an action that operates on a row interval with a temporary buffer.
/// </summary>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
public interface IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Invokes the method passing the row interval and a buffer.
/// </summary>
/// <param name="rows">The row interval.</param>
/// <param name="span">The contiguous region of memory.</param>
void Invoke(in RowInterval rows, Span<TBuffer> span);
}
}

17
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
{
/// <summary>
/// Defines the contract for an action that operates on a row.
/// </summary>
public interface IRowOperation
{
/// <summary>
/// Invokes the method passing the row y coordinate.
/// </summary>
/// <param name="y">The row y coordinate.</param>
void Invoke(int y);
}
}

22
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
{
/// <summary>
/// Defines the contract for an action that operates on a row with a temporary buffer.
/// </summary>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
public interface IRowOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Invokes the method passing the row and a buffer.
/// </summary>
/// <param name="y">The row y coordinate.</param>
/// <param name="span">The contiguous region of memory.</param>
void Invoke(int y, Span<TBuffer> span);
}
}

8
src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs → 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
{
/// <summary>
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
/// Defines execution settings for methods in <see cref="ParallelRowIterator"/>.
/// </summary>
public readonly struct ParallelExecutionSettings
{
@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils
}
/// <summary>
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelUtils.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/>.</param>
/// <returns>The <see cref="ParallelExecutionSettings"/>.</returns>

198
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
{
/// <content>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </content>
public static partial class ParallelRowIterator
{
private readonly struct RowOperationWrapper<T>
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<T, TBuffer>
where T : struct, IRowOperation<TBuffer>
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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.width);
Span<TBuffer> span = buffer.Memory.Span;
for (int y = yMin; y < yMax; y++)
{
Unsafe.AsRef(this.action).Invoke(y, span);
}
}
}
private readonly struct RowIntervalOperationWrapper<T>
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<T, TBuffer>
where T : struct, IRowIntervalOperation<TBuffer>
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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.width);
Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
}
}
}
}

288
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
{
/// <summary>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </summary>
public static partial class ParallelRowIterator
{
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single row.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IterateRows<T>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowOperation
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in operation);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single row.</param>
public static void IterateRows<T>(
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<T>(top, bottom, verticalStep, in operation);
Parallel.For(
0,
numOfSteps,
parallelOptions,
wrappingOperation.Invoke);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches.
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single row.</param>
public static void IterateRows<T, TBuffer>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowOperation<TBuffer>
where TBuffer : unmanaged
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows<T, TBuffer>(rectangle, in parallelSettings, in operation);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches.
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single row.</param>
public static void IterateRows<T, TBuffer>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T operation)
where T : struct, IRowOperation<TBuffer>
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<TBuffer> buffer = allocator.Allocate<TBuffer>(width);
Span<TBuffer> 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<T, TBuffer>(top, bottom, verticalStep, width, allocator, in operation);
Parallel.For(
0,
numOfSteps,
parallelOptions,
wrappingOperation.Invoke);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void IterateRowIntervals<T>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRowIntervals(rectangle, in parallelSettings, in operation);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRowIntervals<T>(
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<T>(top, bottom, verticalStep, in operation);
Parallel.For(
0,
numOfSteps,
parallelOptions,
wrappingOperation.Invoke);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRowIntervals<T, TBuffer>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRowIntervals<T, TBuffer>(rectangle, in parallelSettings, in operation);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
/// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRowIntervals<T, TBuffer>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
in T operation)
where T : struct, IRowIntervalOperation<TBuffer>
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<TBuffer> buffer = allocator.Allocate<TBuffer>(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<T, TBuffer>(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)}");
}
}
}

160
src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs

@ -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
{
/// <summary>
/// Utility methods for batched processing of pixel row intervals.
/// Parallel execution is optimized for image processing based on values defined
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
/// </summary>
public static class ParallelHelper
{
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body)
{
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, parallelSettings, body);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval> 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);
});
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval, Memory<T>> 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<T> buffer = memoryAllocator.Allocate<T>(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<T> buffer = memoryAllocator.Allocate<T>(rectangle.Width))
{
body(rows, buffer.Memory);
}
});
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
internal static void IterateRowsWithTempBuffer<T>(
Rectangle rectangle,
Configuration configuration,
Action<RowInterval, Memory<T>> 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)}");
}
}
}

205
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
{
/// <content>
/// Contains static named color values.
/// <see href="https://www.w3.org/TR/css-color-3/"/>
/// </content>
public readonly partial struct Color
{
private static readonly Lazy<Dictionary<string, Color>> NamedColorsLookupLazy = new Lazy<Dictionary<string, Color>>(CreateNamedColorsLookup, true);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
@ -111,7 +117,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary>
public static readonly Color Cyan = FromRgba(0, 255, 255, 255);
public static readonly Color Cyan = Aqua;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00008B.
@ -138,6 +144,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #A9A9A9.
/// </summary>
public static readonly Color DarkGrey = DarkGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BDB76B.
/// </summary>
@ -188,6 +199,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #2F4F4F.
/// </summary>
public static readonly Color DarkSlateGrey = DarkSlateGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00CED1.
/// </summary>
@ -213,6 +229,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color DimGray = FromRgba(105, 105, 105, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #696969.
/// </summary>
public static readonly Color DimGrey = DimGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #1E90FF.
/// </summary>
@ -273,6 +294,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #808080.
/// </summary>
public static readonly Color Grey = Gray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0FFF0.
/// </summary>
@ -353,6 +379,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color LightGreen = FromRgba(144, 238, 144, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D3D3D3.
/// </summary>
public static readonly Color LightGrey = LightGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFB6C1.
/// </summary>
@ -378,6 +409,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #778899.
/// </summary>
public static readonly Color LightSlateGrey = LightSlateGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B0C4DE.
/// </summary>
@ -406,7 +442,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary>
public static readonly Color Magenta = FromRgba(255, 0, 255, 255);
public static readonly Color Magenta = Fuchsia;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #800000.
@ -643,6 +679,11 @@ namespace SixLabors.ImageSharp
/// </summary>
public static readonly Color SlateGray = FromRgba(112, 128, 144, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #708090.
/// </summary>
public static readonly Color SlateGrey = SlateGray;
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFAFA.
/// </summary>
@ -679,9 +720,9 @@ namespace SixLabors.ImageSharp
public static readonly Color Tomato = FromRgba(255, 99, 71, 255);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFFFF.
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00000000.
/// </summary>
public static readonly Color Transparent = FromRgba(255, 255, 255, 0);
public static readonly Color Transparent = FromRgba(0, 0, 0, 0);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #40E0D0.
@ -717,5 +758,161 @@ namespace SixLabors.ImageSharp
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9ACD32.
/// </summary>
public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255);
private static Dictionary<string, Color> CreateNamedColorsLookup()
{
return new Dictionary<string, Color>(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 }
};
}
}
}

222
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")
};
}
}
}

95
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);
/// <summary>
/// Creates a new <see cref="Color"/> instance from the string representing a color in hexadecimal form.
/// Creates a new instance of the <see cref="Color"/> struct
/// from the given hexadecimal string.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
/// </param>
/// <returns>Returns a <see cref="Color"/> that represents the color defined by the provided RGBA hex string.</returns>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
[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);
}
/// <summary>
/// Attempts to creates a new instance of the <see cref="Color"/> struct
/// from the given hexadecimal string.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
/// </param>
/// <param name="result">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
[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;
}
/// <summary>
/// Creates a new instance of the <see cref="Color"/> struct
/// from the given input string.
/// </summary>
/// <param name="input">
/// 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.
/// </param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
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;
}
/// <summary>
/// Attempts to creates a new instance of the <see cref="Color"/> struct
/// from the given input string.
/// </summary>
/// <param name="input">
/// 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.
/// </param>
/// <param name="result">When this method returns, contains the <see cref="Color"/> equivalent of the hexadecimal input.</param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
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);
}
/// <summary>
/// Alters the alpha channel of the color, returning a new instance.
/// </summary>
@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp
/// <returns>The color having it's alpha channel altered.</returns>
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
/// <returns>The pixel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public TPixel ToPixel<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
pixel.FromRgba64(this.data);
@ -158,7 +239,7 @@ namespace SixLabors.ImageSharp
Configuration configuration,
ReadOnlySpan<Color> source,
Span<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source);
PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination);

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

4
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs

@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
}
/// <summary>
/// Performs the bulk conversion from <see cref="Lms"/> into <see cref="LinearRgb"/>.
/// Performs the bulk conversion from <see cref="Rgb"/> into <see cref="LinearRgb"/>.
/// </summary>
/// <param name="source">The span to the source colors</param>
/// <param name="destination">The span to the destination colors</param>
@ -435,4 +435,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
return this.ToLinearRgb(rgb);
}
}
}
}

2
src/ImageSharp/Common/Exceptions/ImageFormatException.cs

@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp
{
/// <summary>
/// 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.
/// </summary>
public class ImageFormatException : Exception
{

14
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
{
/// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Collections.IEnumerable"/> interface.
@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common
/// <summary>
/// Generates a sequence of integral numbers within a specified range.
/// </summary>
/// <param name="fromInclusive">
/// The start index, inclusive.
/// </param>
/// <param name="fromInclusive">The start index, inclusive.</param>
/// <param name="toDelegate">
/// A method that has one parameter and returns a <see cref="bool"/> calculating the end index.
/// </param>
/// <param name="step">
/// The incremental step.
/// </param>
/// <param name="step">The incremental step.</param>
/// <returns>
/// The <see cref="IEnumerable{Int32}"/> that contains a range of sequential integral numbers.
/// </returns>
@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common
}
}
}
}
}

4
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
{

2
src/ImageSharp/Common/Helpers/Buffer2DUtils.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
ComplexVector4 vector = default;
int kernelLength = kernel.Length;

24
src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs

@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp
int maxRow,
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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;
}
}

29
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
{
/// <summary>
/// Ensures that the value is a value type.
/// </summary>
/// <param name="value">The target object, which cannot be null.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException"><paramref name="value"/> is not a value type.</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeValueType<TValue>(TValue value, string parameterName)
{
if (!value.GetType().GetTypeInfo().IsValueType)
{
ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName);
}
}
}
}

36
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -242,40 +242,6 @@ namespace SixLabors.ImageSharp
return 1F;
}
/// <summary>
/// Returns the result of a B-C filter against the given value.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
/// <param name="x">The value to process.</param>
/// <param name="b">The B-Spline curve variable.</param>
/// <param name="c">The Cardinal curve variable.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[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;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary>
@ -303,7 +269,7 @@ namespace SixLabors.ImageSharp
/// The <see cref="Rectangle"/>.
/// </returns>
public static Rectangle GetFilteredBoundingRectangle<TPixel>(ImageFrame<TPixel> bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int width = bitmap.Width;
int height = bitmap.Height;

103
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<byte> 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 };
/// <summary>
/// <see cref="NormalizedFloatToByteSaturate"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan<float> source,
ref Span<byte> 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<byte>.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);
}
}
}
/// <summary>
/// Implementation of <see cref="SimdUtils.NormalizedFloatToByteSaturate"/>, which is faster on new .NET runtime.
/// </summary>
/// <remarks>
/// Implementation is based on MagicScaler code:
/// https://github.com/saucecontrol/PhotoSauce/blob/a9bd6e5162d2160419f0cf743fd4f536c079170b/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L453-L477
/// </remarks>
internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> dest)
{
VerifySpanInput(source, dest, Vector256<byte>.Count);
int n = dest.Length / Vector256<byte>.Count;
ref Vector256<float> sourceBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref Vector256<byte> destBase = ref Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(dest));
var maxBytes = Vector256.Create(255f);
ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32);
Vector256<int> mask = Unsafe.As<byte, Vector256<int>>(ref maskBase);
for (int i = 0; i < n; i++)
{
ref Vector256<float> s = ref Unsafe.Add(ref sourceBase, i * 4);
Vector256<float> f0 = s;
Vector256<float> f1 = Unsafe.Add(ref s, 1);
Vector256<float> f2 = Unsafe.Add(ref s, 2);
Vector256<float> f3 = Unsafe.Add(ref s, 3);
Vector256<int> w0 = ConvertToInt32(f0, maxBytes);
Vector256<int> w1 = ConvertToInt32(f1, maxBytes);
Vector256<int> w2 = ConvertToInt32(f2, maxBytes);
Vector256<int> w3 = ConvertToInt32(f3, maxBytes);
Vector256<short> u0 = Avx2.PackSignedSaturate(w0, w1);
Vector256<short> u1 = Avx2.PackSignedSaturate(w2, w3);
Vector256<byte> b = Avx2.PackUnsignedSaturate(u0, u1);
b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte();
Unsafe.Add(ref destBase, i) = b;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> ConvertToInt32(Vector256<float> vf, Vector256<float> scale)
{
vf = Avx.Multiply(vf, scale);
return Avx.ConvertToVector256Int32(vf);
}
}
}
}
#endif

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

@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp
/// </summary>
public static class BasicIntrinsics256
{
public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture;
public static bool IsAvailable { get; } = HasVector8;
#if !SUPPORTS_EXTENDED_INTRINSICS
/// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertByteToNormalizedFloatReduce(
internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan<byte> source,
ref Span<float> 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
}
/// <summary>
/// <see cref="BulkConvertNormalizedFloatToByteClampOverflows"/> as many elements as possible, slicing them down (keeping the remainder).
/// <see cref="NormalizedFloatToByteSaturate"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan<float> source,
ref Span<byte> 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
/// <summary>
/// SIMD optimized implementation for <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/>.
/// SIMD optimized implementation for <see cref="SimdUtils.ByteToNormalizedFloat"/>.
/// 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
/// </summary>
internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
{
VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat));
VerifyHasVector8(nameof(ByteToNormalizedFloat));
VerifySpanInput(source, dest, 8);
var bVec = new Vector<float>(256.0f / 255.0f);
@ -94,17 +94,17 @@ namespace SixLabors.ImageSharp
var magicInt = new Vector<uint>(1191182336); // reinterpreted value of 32768.0f
var mask = new Vector<uint>(255);
ref Octet.OfByte sourceBase = ref Unsafe.As<byte, Octet.OfByte>(ref MemoryMarshal.GetReference(source));
ref Octet.OfUInt32 destBaseAsWideOctet = ref Unsafe.As<float, Octet.OfUInt32>(ref MemoryMarshal.GetReference(dest));
ref Octet<byte> sourceBase = ref Unsafe.As<byte, Octet<byte>>(ref MemoryMarshal.GetReference(source));
ref Octet<uint> destBaseAsWideOctet = ref Unsafe.As<float, Octet<uint>>(ref MemoryMarshal.GetReference(dest));
ref Vector<float> destBaseAsFloat = ref Unsafe.As<Octet.OfUInt32, Vector<float>>(ref destBaseAsWideOctet);
ref Vector<float> destBaseAsFloat = ref Unsafe.As<Octet<uint>, Vector<float>>(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<byte> s = ref Unsafe.Add(ref sourceBase, i);
ref Octet<uint> d = ref Unsafe.Add(ref destBaseAsWideOctet, i);
d.LoadFrom(ref s);
}
@ -124,11 +124,11 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Implementation of <see cref="SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows"/> which is faster on older runtimes.
/// Implementation of <see cref="SimdUtils.NormalizedFloatToByteSaturate"/> which is faster on older runtimes.
/// </summary>
internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan<float> source, Span<byte> dest)
internal static void NormalizedFloatToByteSaturate(ReadOnlySpan<float> source, Span<byte> dest)
{
VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows));
VerifyHasVector8(nameof(NormalizedFloatToByteSaturate));
VerifySpanInput(source, dest, 8);
if (source.Length == 0)
@ -137,17 +137,17 @@ namespace SixLabors.ImageSharp
}
ref Vector<float> srcBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Octet.OfByte destBase = ref Unsafe.As<byte, Octet.OfByte>(ref MemoryMarshal.GetReference(dest));
ref Octet<byte> destBase = ref Unsafe.As<byte, Octet<byte>>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 8;
var magick = new Vector<float>(32768.0f);
var scale = new Vector<float>(255f) / new Vector<float>(256f);
// need to copy to a temporary struct, because
// SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x)
// SimdUtils.Octet<uint> temp = Unsafe.As<Vector<float>, SimdUtils.Octet<uint>>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report
var temp = default(Octet.OfUInt32);
ref Vector<float> tempRef = ref Unsafe.As<Octet.OfUInt32, Vector<float>>(ref temp);
var temp = default(Octet<uint>);
ref Vector<float> tempRef = ref Unsafe.As<Octet<uint>, Vector<float>>(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<byte> d = ref Unsafe.Add(ref destBase, i);
d.LoadFrom(ref temp);
}
}
@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp
/// </summary>
internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan<float> source, Span<byte> dest)
{
VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte));
VerifyHasVector8(nameof(BulkConvertNormalizedFloatToByte));
VerifySpanInput(source, dest, 8);
if (source.Length == 0)
@ -186,17 +186,17 @@ namespace SixLabors.ImageSharp
}
ref Vector<float> srcBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Octet.OfByte destBase = ref Unsafe.As<byte, Octet.OfByte>(ref MemoryMarshal.GetReference(dest));
ref Octet<byte> destBase = ref Unsafe.As<byte, Octet<byte>>(ref MemoryMarshal.GetReference(dest));
int n = source.Length / 8;
var magick = new Vector<float>(32768.0f);
var scale = new Vector<float>(255f) / new Vector<float>(256f);
// need to copy to a temporary struct, because
// SimdUtils.Octet.OfUInt32 temp = Unsafe.As<Vector<float>, SimdUtils.Octet.OfUInt32>(ref x)
// SimdUtils.Octet<uint> temp = Unsafe.As<Vector<float>, SimdUtils.Octet<uint>>(ref x)
// does not work. TODO: This might be a CoreClr bug, need to ask/report
var temp = default(Octet.OfUInt32);
ref Vector<float> tempRef = ref Unsafe.As<Octet.OfUInt32, Vector<float>>(ref temp);
var temp = default(Octet<uint>);
ref Vector<float> tempRef = ref Unsafe.As<Octet<uint>, Vector<float>>(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<byte> d = ref Unsafe.Add(ref destBase, i);
d.LoadFrom(ref temp);
}
}

20
src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs

@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertByteToNormalizedFloatReduce(
internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan<byte> source,
ref Span<float> 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
}
/// <summary>
/// <see cref="BulkConvertNormalizedFloatToByteClampOverflows"/> as many elements as possible, slicing them down (keeping the remainder).
/// <see cref="NormalizedFloatToByteSaturate"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan<float> source,
ref Span<byte> 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
}
/// <summary>
/// Implementation <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/>, which is faster on new RyuJIT runtime.
/// Implementation <see cref="SimdUtils.ByteToNormalizedFloat"/>, which is faster on new RyuJIT runtime.
/// </summary>
internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
{
VerifySpanInput(source, dest, Vector<byte>.Count);
@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Implementation of <see cref="SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows"/>, which is faster on new .NET runtime.
/// Implementation of <see cref="SimdUtils.NormalizedFloatToByteSaturate"/>, which is faster on new .NET runtime.
/// </summary>
internal static void BulkConvertNormalizedFloatToByteClampOverflows(
internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> dest)
{

29
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
{
/// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertByteToNormalizedFloatReduce(
internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan<byte> source,
ref Span<float> 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
}
/// <summary>
/// <see cref="BulkConvertNormalizedFloatToByteClampOverflows"/> as many elements as possible, slicing them down (keeping the remainder).
/// <see cref="NormalizedFloatToByteSaturate"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce(
internal static void NormalizedFloatToByteSaturateReduce(
ref ReadOnlySpan<float> source,
ref Span<byte> 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
}
/// <summary>
/// Implementation of <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/> using <see cref="Vector4"/>.
/// Implementation of <see cref="SimdUtils.ByteToNormalizedFloat"/> using <see cref="Vector4"/>.
/// </summary>
[MethodImpl(InliningOptions.ColdPath)]
internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
{
VerifySpanInput(source, dest, 4);
@ -99,10 +99,10 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Implementation of <see cref="SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows"/> using <see cref="Vector4"/>.
/// Implementation of <see cref="SimdUtils.NormalizedFloatToByteSaturate"/> using <see cref="Vector4"/>.
/// </summary>
[MethodImpl(InliningOptions.ColdPath)]
internal static void BulkConvertNormalizedFloatToByteClampOverflows(
internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> 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
}
}
}
}
}

35
src/ImageSharp/Common/Helpers/SimdUtils.cs

@ -15,9 +15,10 @@ namespace SixLabors.ImageSharp
internal static partial class SimdUtils
{
/// <summary>
/// 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 <see cref="Vector{T}"/> code is being JIT-ed to AVX2 instructions
/// where both float and integer registers are of size 256 byte.
/// </summary>
public static bool IsAvx2CompatibleArchitecture { get; } =
public static bool HasVector8 { get; } =
Vector.IsHardwareAccelerated && Vector<float>.Count == 8 && Vector<int>.Count == 8;
/// <summary>
@ -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
/// <param name="source">The source span of bytes</param>
/// <param name="dest">The destination span of floats</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> 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
/// <param name="source">The source span of floats</param>
/// <param name="dest">The destination span of bytes</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan<float> source, Span<byte> dest)
internal static void NormalizedFloatToByteSaturate(ReadOnlySpan<float> source, Span<byte> 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!");
}

18
src/ImageSharp/Common/Helpers/Vector4Utils.cs → 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
/// <summary>
/// Utility methods for the <see cref="Vector4"/> struct.
/// </summary>
internal static class Vector4Utils
internal static class Vector4Utilities
{
/// <summary>
/// Restricts a vector between a minimum and a maximum value.
/// 5x Faster then <see cref="Vector4.Clamp(Vector4, Vector4, Vector4)"/>.
/// </summary>
/// <param name="x">The vector to restrict.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector4 FastClamp(Vector4 x, Vector4 min, Vector4 max)
=> Vector4.Min(Vector4.Max(x, min), max);
/// <summary>
/// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact.
/// </summary>
@ -107,4 +119,4 @@ namespace SixLabors.ImageSharp
}
}
}
}
}

112
src/ImageSharp/Common/Tuples/Octet.cs

@ -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
{
/// <summary>
/// Contains 8 element value tuples of various types.
/// </summary>
internal static class Octet
{
/// <summary>
/// Value tuple of <see cref="uint"/>-s.
/// </summary>
[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;
}
}
/// <summary>
/// Value tuple of <see cref="byte"/>-s
/// </summary>
[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;
}
}
}
}

73
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
{
/// <summary>
/// Contains 8 element value tuples of various types.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct Octet<T>
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;
/// <inheritdoc/>
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})";
}
}
/// <summary>
/// Extension methods for the <see cref="Octet{T}"/> type.
/// </summary>
internal static class OctetExtensions
{
/// <summary>
/// Loads the fields in a target <see cref="Octet{T}"/> of <see cref="uint"/> from one of <see cref="byte"/> type.
/// </summary>
/// <param name="destination">The target <see cref="Octet{T}"/> of <see cref="uint"/> instance.</param>
/// <param name="source">The source <see cref="Octet{T}"/> of <see cref="byte"/> instance.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void LoadFrom(ref this Octet<uint> destination, ref Octet<byte> 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;
}
/// <summary>
/// Loads the fields in a target <see cref="Octet{T}"/> of <see cref="byte"/> from one of <see cref="uint"/> type.
/// </summary>
/// <param name="destination">The target <see cref="Octet{T}"/> of <see cref="byte"/> instance.</param>
/// <param name="source">The source <see cref="Octet{T}"/> of <see cref="uint"/> instance.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void LoadFrom(ref this Octet<byte> destination, ref Octet<uint> 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;
}
}
}

6
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.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscalePreAvx2(float downscaleFactor)
internal void RoundAndDownscalePreVector8(float downscaleFactor)
{
ref Vector<float> a = ref Unsafe.As<Vector4, Vector<float>>(ref this.A);
a = a.FastRound();
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tuples
/// TODO: Move it somewhere else.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RoundAndDownscaleAvx2(float downscaleFactor)
internal void RoundAndDownscaleVector8(float downscaleFactor)
{
ref Vector<float> self = ref Unsafe.As<Vector4Pair, Vector<float>>(ref this);
Vector<float> v = self;
@ -79,4 +79,4 @@ namespace SixLabors.ImageSharp.Tuples
return $"{nameof(Vector4Pair)}({this.A}, {this.B})";
}
}
}
}

3
src/ImageSharp/Configuration.cs

@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp
/// The default value is 1MB.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024;

20
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
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
return new BmpDecoderCore(configuration, this).Decode<TPixel>(stream);
var decoder = new BmpDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(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);
}
}
/// <inheritdoc />

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

@ -114,6 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.options = options;
}
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height);
/// <summary>
/// Decodes the image from the specified this._stream and sets
/// the data to image.
@ -126,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
@ -251,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadBitFields<TPixel>(Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.infoHeader.BitsPerPixel == 16)
{
@ -291,27 +296,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle<TPixel>(BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
using (Buffer2D<byte> buffer = this.memoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean))
using (Buffer2D<bool> undefinedPixels = this.memoryAllocator.Allocate2D<bool>(width, height, AllocationOptions.Clean))
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height, AllocationOptions.Clean))
using (IMemoryOwner<bool> undefinedPixels = this.memoryAllocator.Allocate<bool>(width * height, AllocationOptions.Clean))
using (IMemoryOwner<bool> rowsWithUndefinedPixels = this.memoryAllocator.Allocate<bool>(height, AllocationOptions.Clean))
{
Span<bool> rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
if (compression == BmpCompression.RLE8)
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> 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<byte> bufferRow = buffer.GetRowSpan(y);
int rowStartIdx = y * width;
Span<byte> bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span<TPixel> 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
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * 3, AllocationOptions.Clean))
using (Buffer2D<bool> undefinedPixels = this.memoryAllocator.Allocate2D<bool>(width, height, AllocationOptions.Clean))
using (IMemoryOwner<bool> undefinedPixels = this.memoryAllocator.Allocate<bool>(width * height, AllocationOptions.Clean))
using (IMemoryOwner<bool> rowsWithUndefinedPixels = this.memoryAllocator.Allocate<bool>(height, AllocationOptions.Clean))
{
Span<bool> rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> 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.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// Pixels per byte (bits per pixel).
int ppb = 8 / bitsPerPixel;
@ -861,7 +872,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 2);
int stride = (width * 2) + padding;
@ -928,7 +939,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 3);
@ -957,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32Fast<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
@ -987,7 +998,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
@ -1088,7 +1099,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="blueMask">The bitmask for the blue channel.</param>
/// <param name="alphaMask">The bitmask for the alpha channel.</param>
private void ReadRgb32BitFields<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
int padding = CalculatePadding(width, 4);

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

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);

69
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;
}
/// <summary>
@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@ -202,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
switch (this.bitsPerPixel)
@ -234,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
{
@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
{
@ -282,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
{
@ -308,7 +309,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
using (IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean))
@ -333,38 +334,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IQuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256).QuantizeFrame(image))
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> 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<TPixel> 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<byte> 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<byte> 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
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// Create a color palette with 256 different gray values.
for (int i = 0; i <= 255; i++)

31
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
/// </summary>
public const string FileVersion = "89a";
/// <summary>
/// The ASCII encoded bytes used to identify the GIF file.
/// </summary>
internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion);
/// <summary>
/// The extension block introducer <value>!</value>.
/// </summary>
@ -51,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public const string NetscapeApplicationIdentification = "NETSCAPE2.0";
/// <summary>
/// The ASCII encoded application identification bytes.
/// </summary>
internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification);
/// <summary>
/// The Netscape looping application sub block size.
/// </summary>
@ -110,5 +101,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The collection of file extensions that equate to a Gif.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "gif" };
/// <summary>
/// Gets the ASCII encoded bytes used to identify the GIF file (combining <see cref="FileType"/> and <see cref="FileVersion"/>).
/// </summary>
internal static ReadOnlySpan<byte> MagicNumber => new[]
{
(byte)'G', (byte)'I', (byte)'F',
(byte)'8', (byte)'9', (byte)'a'
};
/// <summary>
/// Gets the ASCII encoded application identification bytes (representing <see cref="NetscapeApplicationIdentification"/>).
/// </summary>
internal static ReadOnlySpan<byte> NetscapeApplicationIdentificationBytes => new[]
{
(byte)'N', (byte)'E', (byte)'T',
(byte)'S', (byte)'C', (byte)'A',
(byte)'P', (byte)'E',
(byte)'2', (byte)'.', (byte)'0'
};
}
}

18
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
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new GifDecoderCore(configuration, this);
return decoder.Decode<TPixel>(stream);
try
{
return decoder.Decode<TPixel>(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);
}
}
/// <inheritdoc/>

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

@ -86,10 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
public bool IgnoreMetadata { get; internal set; }
/// <summary>
/// Gets the decoding mode for multi-frame images
/// Gets the decoding mode for multi-frame images.
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height);
private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator;
/// <summary>
@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="stream">The stream containing image data. </param>
/// <returns>The decoded image</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
ImageFrame<TPixel> 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
/// <param name="image">The image to decode the information to.</param>
/// <param name="previousFrame">The previous frame.</param>
private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
this.ReadImageDescriptor();
@ -401,7 +405,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
int imageWidth = this.logicalScreenDescriptor.Width;
@ -531,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The frame.</param>
private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.restoreArea is null)
{

5
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 <see cref="OctreeQuantizer"/>
/// </summary>
public IQuantizer Quantizer { get; set; } = new OctreeQuantizer();
public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;
/// <summary>
/// Gets or sets the color table mode: Global or local.
@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new GifEncoderCore(image.GetConfiguration(), this);
encoder.Encode(image, stream);

140
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
/// <summary>
/// Configuration bound to the encoding operation.
/// </summary>
private Configuration configuration;
private readonly Configuration configuration;
/// <summary>
/// A reusable buffer used to reduce allocations.
@ -70,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> quantized;
IndexedImageFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(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<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void EncodeGlobal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, int transparencyIndex, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<TPixel> pixelMap = default;
bool pixelMapSet = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
@ -144,25 +147,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
using (IFrameQuantizer<TPixel> paletteFrameQuantizer =
new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Diffuser, quantized.Palette))
if (!pixelMapSet)
{
using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame))
{
this.WriteImageData(paletteQuantized, stream);
}
pixelMapSet = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
}
using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream);
}
}
}
private void EncodeLocal<TPixel>(Image<TPixel> image, IQuantizedFrame<TPixel> quantized, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrame<TPixel> previousFrame = null;
GifFrameMetadata previousMeta = null;
foreach (ImageFrame<TPixel> frame in image.Frames)
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> 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<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(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<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
else
{
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
{
quantized = frameQuantizer.QuantizeFrame(frame);
}
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(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
/// <returns>
/// The <see cref="int"/>.
/// </returns>
private int GetTransparentIndex<TPixel>(IQuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<TPixel> paletteSpan = quantized.Palette.Span;
using (IMemoryOwner<Rgba32> rgbaBuffer = this.memoryAllocator.Allocate<Rgba32>(length))
{
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan);
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan);
using IMemoryOwner<Rgba32> rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate<Rgba32>(paletteSpan.Length);
Span<Rgba32> rgbaSpan = rgbaOwner.GetSpan();
PixelOperations<TPixel>.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
/// </summary>
/// <param name="stream">The stream to write to.</param>
[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);
/// <summary>
/// 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<char> 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
/// </summary>
/// <param name="extension">The extension to write to the stream.</param>
/// <param name="stream">The stream to write to.</param>
public void WriteExtension(IGifExtension extension, Stream stream)
private void WriteExtension<TGifExtension>(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
/// <param name="hasColorTable">Whether to use the global color table.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColorTable, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
byte packedValue = GifImageDescriptor.GetPackedValue(
localColorTableFlag: hasColorTable,
@ -435,37 +445,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteColorTable<TPixel>(IQuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void WriteColorTable<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<Rgb24>();
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
{
PixelOperations<TPixel>.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<TPixel>.Instance.ToRgb24Bytes(
this.configuration,
image.Palette.Span,
colorTable.GetSpan(),
image.Palette.Length);
stream.Write(colorTable.Array, 0, colorTableLength);
}
/// <summary>
/// Writes the image pixel data to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="IQuantizedFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="image">The <see cref="IndexedImageFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteImageData<TPixel>(IQuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void WriteImageData<TPixel>(IndexedImageFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
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);
}
}
}

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

@ -36,10 +36,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>
/// 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.
/// </remarks>
/// </summary>
public ushort RepeatCount { get; set; }
public ushort RepeatCount { get; set; } = 1;
/// <summary>
/// Gets or sets the color table mode.

2
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)
{

2
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)

4
src/ImageSharp/Formats/IImageDecoder.cs

@ -18,8 +18,9 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>;
where TPixel : unmanaged, IPixel<TPixel>;
/// <summary>
/// Decodes the image from the specified stream to an <see cref="Image"/>.
@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="configuration">The configuration for the image.</param>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110)
Image Decode(Configuration configuration, Stream stream);
}
}

2
src/ImageSharp/Formats/IImageEncoder.cs

@ -18,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>;
where TPixel : unmanaged, IPixel<TPixel>;
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public Block8x8(Span<short> coefficients)
{
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this);
ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<short, byte>(coefficients));
ref byte sourceRef = ref Unsafe.As<short, byte>(ref MemoryMarshal.GetReference(coefficients));
Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short));
}

34
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);
}
/// <summary>
/// AVX2-only variant for executing <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
public void NormalizeColorsAndRoundInplaceVector8(float maximum)
{
var off = new Vector<float>(MathF.Ceiling(maximum / 2));
var max = new Vector<float>(maximum);

4
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 <see cref="NormalizeColorsInplace"/> and <see cref="RoundInplace"/> in one step.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void NormalizeColorsAndRoundInplaceAvx2(float maximum)
public void NormalizeColorsAndRoundInplaceVector8(float maximum)
{
var off = new Vector<float>(MathF.Ceiling(maximum / 2));
var max = new Vector<float>(maximum);

37
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs → 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.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void CopyTo(in BufferArea<float> area, int horizontalScale, int verticalScale)
public void ScaledCopyTo(in BufferArea<float> 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<float> destination)
public void Copy1x1Scale(ref float areaOrigin, int areaStride)
{
ref byte selfBase = ref Unsafe.As<Block8x8F, byte>(ref this);
ref byte destBase = ref Unsafe.As<float, byte>(ref destination.GetReferenceToOrigin());
int destStride = destination.Stride * sizeof(float);
ref byte destBase = ref Unsafe.As<float, byte>(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<float> area)
private void Copy2x2Scale(ref float areaOrigin, int areaStride)
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref area.GetReferenceToOrigin());
int destStride = area.Stride / 2;
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(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<float> 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;
}
}
}
}
}
}
}
}

24
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
/// <param name="dest">Destination</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void CopyTo(Span<float> dest)
public void ScaledCopyTo(Span<float> dest)
{
ref byte d = ref Unsafe.As<float, byte>(ref MemoryMarshal.GetReference(dest));
ref byte s = ref Unsafe.As<Block8x8F, byte>(ref this);
@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="blockPtr">Pointer to block</param>
/// <param name="dest">Destination</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void CopyTo(Block8x8F* blockPtr, Span<byte> dest)
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<byte> dest)
{
float* fPtr = (float*)blockPtr;
for (int i = 0; i < Size; i++)
@ -231,9 +231,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="blockPtr">The block pointer.</param>
/// <param name="dest">The destination.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static unsafe void CopyTo(Block8x8F* blockPtr, Span<float> dest)
public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span<float> dest)
{
blockPtr->CopyTo(dest);
blockPtr->ScaledCopyTo(dest);
}
/// <summary>
@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary>
/// <param name="dest">Destination</param>
[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
/// </summary>
/// <param name="dest">Destination</param>
public unsafe void CopyTo(Span<int> dest)
public unsafe void ScaledCopyTo(Span<int> 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
/// </summary>
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<short> sRef = ref Unsafe.As<Block8x8, Vector<short>>(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);

16
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs

@ -90,15 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
if (Vector<float>.Count == 4)
{
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> 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
}
}
}
}
}

8
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<Vector4> result)
{
@ -107,4 +107,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

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

@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary>
/// Returns the <see cref="JpegColorConverter"/> corresponding to the given <see cref="JpegColorSpace"/>
/// </summary>
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 <see cref="JpegColorConverter"/> for the YCbCr colorspace that matches the current CPU architecture.
/// </summary>
private static JpegColorConverter GetYCbCrConverter(int precision) =>
FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision);
FromYCbCrSimdVector8.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdVector8(precision) : new FromYCbCrSimd(precision);
/// <summary>
/// A stack-only struct to reference the input buffers using <see cref="ReadOnlySpan{T}"/>-s.
@ -232,4 +232,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
}
}
}
}
}

14
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 <see cref="subSamplingDivisors"/>.
/// </summary>
/// <param name="sourceBlock">The source block.</param>
/// <param name="destArea">The destination buffer area.</param>
/// <param name="destAreaOrigin">Reference to the origin of the destination pixel area.</param>
/// <param name="destAreaStride">The width of the destination pixel buffer.</param>
/// <param name="maximumValue">The maximum value derived from the bitdepth.</param>
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
in BufferArea<float> 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);
}
}
}
}

28
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<float>(
this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
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;
}
/// <summary>
@ -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<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> 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<float> 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;
}
}
}
}

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

@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
public void PostProcess<TPixel>(ImageFrame<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
this.PixelRowCounter = 0;
@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image.</param>
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);

35
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
{
/// <summary>
/// Describes the JFIF specific markers.
/// Gets the JFIF specific markers.
/// </summary>
public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0");
public static ReadOnlySpan<byte> JFifMarker => new[]
{
(byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0'
};
/// <summary>
/// Describes the ICC specific markers.
/// Gets the ICC specific markers.
/// </summary>
public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0");
public static ReadOnlySpan<byte> 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'
};
/// <summary>
/// Describes the EXIF specific markers.
/// Gets the EXIF specific markers.
/// </summary>
public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0");
public static ReadOnlySpan<byte> ExifMarker => new[]
{
(byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0'
};
/// <summary>
/// Describes Adobe specific markers <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe"/>.
/// Gets the Adobe specific markers <see href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe"/>.
/// </summary>
public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe");
public static ReadOnlySpan<byte> AdobeMarker => new[]
{
(byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e'
};
/// <summary>
/// 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);
}
}
}
}

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

@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter<TPixel>
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// The Y component
@ -55,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y)
public void Convert(ImageFrame<TPixel> frame, int x, int y, in RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame, x, y);
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);

25
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<TPixel>(IPixelSource<TPixel> source, int sourceX, int sourceY)
where TPixel : struct, IPixel<TPixel>
{
if (source.PixelBuffer is Buffer2D<T> buffer)
{
this.LoadAndStretchEdges(buffer, sourceX, sourceY);
}
else
{
throw new InvalidOperationException("LoadAndStretchEdges<TPixels>() is only valid for TPixel == T !");
}
}
/// <summary>
/// Load a 8x8 region of an image into the block.
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
/// </summary>
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY)
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY, in RowOctet<T> 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<GenericBlock8x8<T>, byte>(ref this);
ref byte imageStart = ref Unsafe.As<T, byte>(
ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX));
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>();
int imageRowSizeInBytes = source.Width * Unsafe.SizeOf<T>();
for (int y = 0; y < height; y++)
{
ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes);
Span<T> row = currentRows[y];
ref byte s = ref Unsafe.As<T, byte>(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
/// </summary>
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size);
}
}
}

68
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
{
/// <summary>
/// Cache 8 pixel rows on the stack, which may originate from different buffers of a <see cref="MemoryGroup{T}"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal readonly ref struct RowOctet<T>
where T : struct
{
private readonly Span<T> row0;
private readonly Span<T> row1;
private readonly Span<T> row2;
private readonly Span<T> row3;
private readonly Span<T> row4;
private readonly Span<T> row5;
private readonly Span<T> row6;
private readonly Span<T> row7;
public RowOctet(Buffer2D<T> 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<T> this[int y]
{
[MethodImpl(InliningOptions.ShortMethod)]
get
{
// No unsafe tricks, since Span<T> 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<T> ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}
}
}

16
src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs

@ -34,12 +34,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public fixed byte Data[Size];
/// <summary>
/// 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).
/// </summary>
private static readonly byte[] Unzig =
new byte[Size]
private static ReadOnlySpan<byte> 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<byte>(result.Data);
Unzig.CopyTo(new Span<byte>(result.Data, Size));
return result;
}

14
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
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
using (var decoder = new JpegDecoderCore(configuration, this))
using var decoder = new JpegDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(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);
}
}
/// <inheritdoc />

4
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="stream">The stream, where the image should be.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
this.ParseStream(stream);
this.InitExifProfile();
@ -958,7 +958,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
private Image<TPixel> PostProcessIntoImage<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.ImageWidth == 0 || this.ImageHeight == 0)
{

2
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new JpegEncoderCore(this);
encoder.Encode(image, stream);

22
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
/// <param name="image">The image to write from.</param>
/// <param name="stream">The stream to write to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@ -393,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode444<TPixel>(Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter<TPixel>)
@ -409,12 +410,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
for (int y = 0; y < pixels.Height; y += 8)
{
var currentRows = new RowOctet<TPixel>(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
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The pixel accessor providing access to the image pixels.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> 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
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
private void Encode420<TPixel>(Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> 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<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> 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<TPixel>(pixelBuffer, y + yOff);
pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows);
cbPtr[i] = pixelConverter.Cb;
crPtr[i] = pixelConverter.Cr;

2
src/ImageSharp/Formats/PixelTypeInfo.cs

@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats
public int BitsPerPixel { get; }
internal static PixelTypeInfo Create<TPixel>()
where TPixel : struct, IPixel<TPixel> =>
where TPixel : unmanaged, IPixel<TPixel> =>
new PixelTypeInfo(Unsafe.SizeOf<TPixel>() * 8);
}
}

31
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
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "png" };
/// <summary>
/// The header bytes identifying a Png.
/// </summary>
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
};
/// <summary>
/// The header bytes as a big-endian coded ulong.
/// </summary>
@ -77,5 +63,20 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The minimum length of a keyword in a text chunk is 1 byte.
/// </summary>
public const int MinTextKeywordLength = 1;
/// <summary>
/// Gets the header bytes identifying a Png.
/// </summary>
public static ReadOnlySpan<byte> 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
};
}
}

17
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
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var decoder = new PngDecoderCore(configuration, this);
return decoder.Decode<TPixel>(stream);
try
{
return decoder.Decode<TPixel>(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);
}
}
/// <inheritdoc/>

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

@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private int currentRow = Adam7.FirstRow[0];
/// <summary>
/// The current number of bytes read in the current scanline
/// The current number of bytes read in the current scanline.
/// </summary>
private int currentRowBytesRead;
@ -132,20 +132,25 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ignoreMetadata = options.IgnoreMetadata;
}
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.header.Width, this.header.Height);
/// <summary>
/// Decodes the stream to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The stream containing image data. </param>
/// <param name="stream">The stream containing image data.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the stream does not contain and end chunk.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the image is larger than the maximum allowable size.
/// </exception>
/// <returns>The decoded image</returns>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var metadata = new ImageMetadata();
PngMetadata pngMetadata = metadata.GetPngMetadata();
@ -373,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="metadata">The metadata information for the image</param>
/// <param name="image">The image that we will populate</param>
private void InitializeImage<TPixel>(ImageMetadata metadata, out Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
this.bytesPerPixel = this.CalculateBytesPerPixel();
@ -466,7 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="image"> The pixel data.</param>
/// <param name="pngMetadata">The png metadata</param>
private void ReadScanlines<TPixel>(PngChunk chunk, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
@ -492,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="image">The image to decode to.</param>
/// <param name="pngMetadata">The png metadata</param>
private void DecodePixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
while (this.currentRow < this.header.Height)
{
@ -548,7 +553,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="image">The current image.</param>
/// <param name="pngMetadata">The png metadata.</param>
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int pass = 0;
int width = this.header.Width;
@ -637,7 +642,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The image</param>
/// <param name="pngMetadata">The png metadata.</param>
private void ProcessDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, ImageFrame<TPixel> pixels, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> rowSpan = pixels.GetPixelRowSpan(this.currentRow);
@ -721,7 +726,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>(ReadOnlySpan<byte> defilteredScanline, Span<TPixel> rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// Trim the first marker byte from the buffer
ReadOnlySpan<byte> 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;

2
src/ImageSharp/Formats/Png/PngEncoder.cs

@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this)))
{

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

@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
IQuantizedFrame<TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);
IndexedImageFrame<TPixel> 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
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The image row span.</param>
private void CollectGrayscaleBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
Span<byte> rawScanlineSpan = this.currentScanline.GetSpan();
@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param>
private void CollectTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Span<byte> rawScanlineSpan = this.currentScanline.GetSpan();
@ -371,8 +371,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="rowSpan">The row span.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, IQuantizedFrame<TPixel> quantized, int row)
where TPixel : struct, IPixel<TPixel>
private void CollectPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
where TPixel : unmanaged, IPixel<TPixel>
{
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
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IQuantizedFrame<TPixel> quantized, int row)
where TPixel : struct, IPixel<TPixel>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
where TPixel : unmanaged, IPixel<TPixel>
{
this.CollectPixelBytes(rowSpan, quantized, row);
return this.FilterPixelBytes();
@ -546,59 +545,54 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="quantized">The quantized frame.</param>
private void WritePaletteChunk<TPixel>(Stream stream, IQuantizedFrame<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
private void WritePaletteChunk<TPixel>(Stream stream, IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{
if (quantized == null)
if (quantized is null)
{
return;
}
// Grab the palette and write it to the stream.
ReadOnlySpan<TPixel> 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<Rgb24>();
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<byte> 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<byte, Rgb24>(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<Rgba32> rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate<Rgba32>(paletteLength);
Span<Rgba32> rgbaPaletteSpan = rgbaOwner.GetSpan();
PixelOperations<TPixel>.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
/// <param name="pixels">The image.</param>
/// <param name="quantized">The quantized pixel data. Can be null.</param>
/// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IQuantizedFrame<TPixel> quantized, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void WriteDataChunks<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
byte[] buffer;
int bufferLength;
@ -881,8 +875,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The pixels.</param>
/// <param name="quantized">The quantized pixels span.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IQuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : struct, IPixel<TPixel>
private void EncodePixels<TPixel>(ImageFrame<TPixel> pixels, IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = bytesPerScanline + 1;
@ -906,7 +900,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="pixels">The pixels.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7Pixels<TPixel>(ImageFrame<TPixel> pixels, ZlibDeflateStream deflateStream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int height = pixels.Height;
@ -960,8 +954,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="quantized">The quantized.</param>
/// <param name="deflateStream">The deflate stream.</param>
private void EncodeAdam7IndexedPixels<TPixel>(IQuantizedFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : struct, IPixel<TPixel>
private void EncodeAdam7IndexedPixels<TPixel>(IndexedImageFrame<TPixel> quantized, ZlibDeflateStream deflateStream)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = quantized.Width;
int height = quantized.Height;
@ -987,7 +981,7 @@ namespace SixLabors.ImageSharp.Formats.Png
row += Adam7.RowIncrement[pass])
{
// collect data
ReadOnlySpan<byte> srcRow = quantized.GetRowSpan(row);
ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])

20
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Png
PngMetadata pngMetadata,
out bool use16Bit,
out int bytesPerPixel)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
// Always take the encoder options over the metadata values.
options.Gamma ??= pngMetadata.Gamma;
@ -53,10 +53,10 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="options">The options.</param>
/// <param name="image">The image.</param>
public static IQuantizedFrame<TPixel> CreateQuantizedFrame<TPixel>(
public static IndexedImageFrame<TPixel> CreateQuantizedFrame<TPixel>(
PngEncoderOptions options,
Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
if (options.ColorType != PngColorType.Palette)
{
@ -72,13 +72,15 @@ namespace SixLabors.ImageSharp.Formats.Png
// Use the metadata to determine what quantization depth to use if no quantizer has been set.
if (options.Quantizer is null)
{
options.Quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits));
var maxColors = ImageMaths.GetColorCountForBitDepth(bits);
options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors });
}
// Create quantized frame returning the palette and set the bit depth.
using (IFrameQuantizer<TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer<TPixel>(image.GetConfiguration()))
{
return frameQuantizer.QuantizeFrame(image.Frames.RootFrame);
ImageFrame<TPixel> frame = image.Frames.RootFrame;
return frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
}
@ -92,8 +94,8 @@ namespace SixLabors.ImageSharp.Formats.Png
public static byte CalculateBitDepth<TPixel>(
PngEncoderOptions options,
Image<TPixel> image,
IQuantizedFrame<TPixel> quantizedFrame)
where TPixel : struct, IPixel<TPixel>
IndexedImageFrame<TPixel> quantizedFrame)
where TPixel : unmanaged, IPixel<TPixel>
{
byte bitDepth;
if (options.ColorType == PngColorType.Palette)
@ -151,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// This is not exhaustive but covers many common pixel formats.
/// </summary>
private static PngColorType SuggestColorType<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return typeof(TPixel) switch
{
@ -177,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// This is not exhaustive but covers many common pixel formats.
/// </summary>
private static PngBitDepth SuggestBitDepth<TPixel>()
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return typeof(TPixel) switch
{

20
src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png
bool hasTrans,
L16 luminance16Trans,
L8 luminanceTrans)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Png
bool hasTrans,
L16 luminance16Trans,
L8 luminanceTrans)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -198,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int increment,
int bytesPerPixel,
int bytesPerSample)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Span<TPixel> rowSpan,
ReadOnlySpan<byte> palette,
byte[] paletteAlpha)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -284,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int increment,
ReadOnlySpan<byte> palette,
byte[] paletteAlpha)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -331,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Png
bool hasTrans,
Rgb48 rgb48Trans,
Rgb24 rgb24Trans)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -404,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.Png
bool hasTrans,
Rgb48 rgb48Trans,
Rgb24 rgb24Trans)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
@ -482,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Span<TPixel> rowSpan,
int bytesPerPixel,
int bytesPerSample)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -515,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int increment,
int bytesPerPixel,
int bytesPerSample)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);

43
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs

@ -36,12 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private const int EofSymbol = 256;
// The lengths of the bit length codes are sent in order of decreasing
// probability, to avoid transmitting the lengths for unused bit length codes.
private static readonly int[] BitLengthOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
private static readonly byte[] Bit4Reverse = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
private static readonly short[] StaticLCodes;
private static readonly byte[] StaticLLength;
private static readonly short[] StaticDCodes;
@ -128,6 +122,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
}
/// <summary>
/// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes.
/// </summary>
private static ReadOnlySpan<byte> BitLengthOrder => new byte[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
private static ReadOnlySpan<byte> Bit4Reverse => new byte[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
/// <summary>
/// Gets the pending buffer to use.
/// </summary>
@ -158,6 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5);
this.Pending.WriteBits(this.distTree.NumCodes - 1, 5);
this.Pending.WriteBits(blTreeCodes - 4, 4);
for (int rank = 0; rank < blTreeCodes; rank++)
{
this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3);
@ -250,6 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.blTree.BuildTree();
int blTreeCodes = 4;
for (int i = 18; i > blTreeCodes; i--)
{
if (this.blTree.Length[BitLengthOrder[i]] > 0)
@ -363,10 +366,30 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
[MethodImpl(InliningOptions.ShortMethod)]
public static short BitReverse(int toReverse)
{
return (short)(Bit4Reverse[toReverse & 0xF] << 12
| Bit4Reverse[(toReverse >> 4) & 0xF] << 8
| Bit4Reverse[(toReverse >> 8) & 0xF] << 4
| Bit4Reverse[toReverse >> 12]);
/* Use unsafe offsetting and manually validate the input index to reduce the
* total number of conditional branches. There are two main cases to test here:
* 1. In the first 3, the input value (or some combination of it) is combined
* with & 0xF, which results in a maximum value of 0xF no matter what the
* input value was. That is 15, which is always in range for the target span.
* As a result, no input validation is needed at all in this case.
* 2. There are two cases where the input value might cause an invalid access:
* when it is either negative, or greater than 15 << 12. We can test both
* conditions in a single pass by casting the input value to uint and right
* shifting it by 12, which also preserves the sign. If it is a negative
* value (2-complement), the test will fail as the uint cast will result
* in a much larger value. If the value was simply too high, the test will
* fail as expected. We can't simply check whether the value is lower than
* 15 << 12, because higher values are acceptable in the first 3 accesses.
* Doing this reduces the total number of index checks from 4 down to just 1. */
int toReverseRightShiftBy12 = toReverse >> 12;
Guard.MustBeLessThanOrEqualTo<uint>((uint)toReverseRightShiftBy12, 15, nameof(toReverse));
ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse);
return (short)(Unsafe.Add(ref bit4ReverseRef, toReverse & 0xF) << 12
| Unsafe.Add(ref bit4ReverseRef, (toReverse >> 4) & 0xF) << 8
| Unsafe.Add(ref bit4ReverseRef, (toReverse >> 8) & 0xF) << 4
| Unsafe.Add(ref bit4ReverseRef, toReverseRightShiftBy12));
}
/// <inheritdoc/>

19
src/ImageSharp/Formats/Tga/TgaDecoder.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.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tga
@ -13,11 +15,24 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
return new TgaDecoderCore(configuration, this).Decode<TPixel>(stream);
var decoder = new TgaDecoderCore(configuration, this);
try
{
return decoder.Decode<TPixel>(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);
}
}
/// <inheritdoc />

21
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -61,6 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.options = options;
}
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height);
/// <summary>
/// Decodes the image from the specified stream.
/// </summary>
@ -71,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// </exception>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
try
{
@ -217,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean))
{
@ -280,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerPixel = 1;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
@ -331,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadMonoChrome<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
{
@ -358,7 +363,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
{
@ -393,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadBgr24<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
{
@ -420,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadBgra32<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
{
@ -448,7 +453,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, bool inverted)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))

2
src/ImageSharp/Formats/Tga/TgaEncoder.cs

@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
encoder.Encode(image, stream);

67
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
if (this.compression is TgaCompression.RunLength)
{
this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame);
this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame);
}
else
{
@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
switch (this.bitsPerPixel)
@ -150,19 +150,20 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The stream to write the image to.</param>
/// <param name="image">The image to encode.</param>
private void WriteRunLengthEndcodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : struct, IPixel<TPixel>
private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 color = default;
Buffer2D<TPixel> pixels = image.PixelBuffer;
Span<TPixel> pixelSpan = pixels.GetSpan();
int totalPixels = image.Width * image.Height;
int encodedPixels = 0;
while (encodedPixels < totalPixels)
{
TPixel currentPixel = pixelSpan[encodedPixels];
int x = encodedPixels % pixels.Width;
int y = encodedPixels / pixels.Width;
TPixel currentPixel = pixels[x, y];
currentPixel.ToRgba32(ref color);
byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels));
byte equalPixelCount = this.FindEqualPixels(pixels, x, y);
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
stream.WriteByte((byte)(equalPixelCount | 128));
@ -200,30 +201,40 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
/// <summary>
/// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
/// Finds consecutive pixels which have the same value.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pixelSpan">The pixel span to search in.</param>
/// <param name="pixels">The pixels of the image.</param>
/// <param name="xStart">X coordinate to start searching for the same pixels.</param>
/// <param name="yStart">Y coordinate to start searching for the same pixels.</param>
/// <returns>The number of equal pixels.</returns>
private byte FindEqualPixels<TPixel>(Span<TPixel> pixelSpan)
where TPixel : struct, IPixel<TPixel>
private byte FindEqualPixels<TPixel>(Buffer2D<TPixel> pixels, int xStart, int yStart)
where TPixel : unmanaged, IPixel<TPixel>
{
int idx = 0;
byte equalPixelCount = 0;
while (equalPixelCount < 127 && idx < pixelSpan.Length - 1)
bool firstRow = true;
TPixel startPixel = pixels[xStart, yStart];
for (int y = yStart; y < pixels.Height; y++)
{
TPixel currentPixel = pixelSpan[idx];
TPixel nextPixel = pixelSpan[idx + 1];
if (currentPixel.Equals(nextPixel))
for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++)
{
equalPixelCount++;
}
else
{
return equalPixelCount;
TPixel nextPixel = pixels[x, y];
if (startPixel.Equals(nextPixel))
{
equalPixelCount++;
}
else
{
return equalPixelCount;
}
if (equalPixelCount >= 127)
{
return equalPixelCount;
}
}
idx++;
firstRow = false;
}
return equalPixelCount;
@ -238,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1))
{
@ -262,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
{
@ -286,7 +297,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
{
@ -310,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
{
@ -333,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="sourcePixel">The pixel to get the luminance from.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetLuminance<TPixel>(TPixel sourcePixel)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var vector = sourcePixel.ToVector4();
return ImageMaths.GetBT709Luminance(ref vector, 256);

6
src/ImageSharp/Image.Decode.cs

@ -32,11 +32,11 @@ namespace SixLabors.ImageSharp
int width,
int height,
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> uninitializedMemoryBuffer =
configuration.MemoryAllocator.Allocate2D<TPixel>(width, height);
return new Image<TPixel>(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata);
return new Image<TPixel>(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata);
}
/// <summary>
@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp
/// </returns>
private static (Image<TPixel> img, IImageFormat format) Decode<TPixel>(Stream stream, Configuration config)
#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format);
if (decoder is null)

149
src/ImageSharp/Image.FromBytes.cs

@ -26,14 +26,55 @@ namespace SixLabors.ImageSharp
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, byte[] data)
public static IImageFormat DetectFormat(Configuration configuration, byte[] data)
{
using (var stream = new MemoryStream(data))
Guard.NotNull(configuration, nameof(configuration));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return DetectFormat(configuration, stream);
}
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(byte[] data, out IImageFormat format) => Identify(Configuration.Default, data, out format);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return DetectFormat(config, stream);
return Identify(configuration, stream, out format);
}
}
@ -51,7 +92,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data);
/// <summary>
@ -62,39 +103,39 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(config, stream);
return Load<TPixel>(configuration, stream);
}
}
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(config, stream, out format);
return Load<TPixel>(configuration, stream, out format);
}
}
@ -106,7 +147,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
@ -117,17 +158,17 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, byte[] data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(config, stream, decoder);
return Load<TPixel>(configuration, stream, decoder);
}
}
@ -144,18 +185,18 @@ namespace SixLabors.ImageSharp
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, ReadOnlySpan<byte> data)
public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan<byte> data)
{
int maxHeaderSize = config.MaxHeaderSize;
int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0)
{
return null;
}
foreach (IImageFormatDetector detector in config.ImageFormatsManager.FormatDetectors)
foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors)
{
IImageFormat f = detector.DetectFormat(data);
@ -175,7 +216,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data);
/// <summary>
@ -186,7 +227,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format);
/// <summary>
@ -197,24 +238,24 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, decoder);
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(Configuration config, ReadOnlySpan<byte> data)
where TPixel : struct, IPixel<TPixel>
public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (byte* ptr = &data.GetPinnableReference())
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load<TPixel>(config, stream);
return Load<TPixel>(configuration, stream);
}
}
}
@ -222,22 +263,22 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration config,
Configuration configuration,
ReadOnlySpan<byte> data,
IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (byte* ptr = &data.GetPinnableReference())
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load<TPixel>(config, stream, decoder);
return Load<TPixel>(configuration, stream, decoder);
}
}
}
@ -245,22 +286,22 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration config,
Configuration configuration,
ReadOnlySpan<byte> data,
out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
fixed (byte* ptr = &data.GetPinnableReference())
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load<TPixel>(config, stream, out format);
return Load<TPixel>(configuration, stream, out format);
}
}
}
@ -285,38 +326,38 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration config, byte[] data) => Load(config, data, out _);
public static Image Load(Configuration configuration, byte[] data) => Load(configuration, data, out _);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration config, byte[] data, IImageDecoder decoder)
public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder)
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(config, stream, decoder);
return Load(configuration, stream, decoder);
}
}
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration config, byte[] data, out IImageFormat format)
public static Image Load(Configuration configuration, byte[] data, out IImageFormat format)
{
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(config, stream, out format);
return Load(configuration, stream, out format);
}
}
@ -348,20 +389,20 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Decodes a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration config, ReadOnlySpan<byte> data) => Load(config, data, out _);
public static Image Load(Configuration configuration, ReadOnlySpan<byte> data) => Load(configuration, data, out _);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load(
Configuration config,
Configuration configuration,
ReadOnlySpan<byte> data,
IImageDecoder decoder)
{
@ -369,7 +410,7 @@ namespace SixLabors.ImageSharp
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load(config, stream, decoder);
return Load(configuration, stream, decoder);
}
}
}
@ -377,12 +418,12 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>>
/// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load(
Configuration config,
Configuration configuration,
ReadOnlySpan<byte> data,
out IImageFormat format)
{
@ -390,7 +431,7 @@ namespace SixLabors.ImageSharp
{
using (var stream = new UnmanagedMemoryStream(ptr, data.Length))
{
return Load(config, stream, out format);
return Load(configuration, stream, out format);
}
}
}

111
src/ImageSharp/Image.FromFile.cs

@ -26,15 +26,55 @@ namespace SixLabors.ImageSharp
/// <summary>
/// By reading the header on the provided file this calculates the images mime type.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, string filePath)
public static IImageFormat DetectFormat(Configuration configuration, string filePath)
{
config = config ?? Configuration.Default;
using (Stream file = config.FileSystem.OpenRead(filePath))
Guard.NotNull(configuration, nameof(configuration));
using (Stream file = configuration.FileSystem.OpenRead(filePath))
{
return DetectFormat(config, file);
return DetectFormat(configuration, file);
}
}
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(string filePath) => Identify(filePath, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(string filePath, out IImageFormat format) => Identify(Configuration.Default, filePath, out format);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration configuration, string filePath, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
using (Stream file = configuration.FileSystem.OpenRead(filePath))
{
return Identify(configuration, file, out format);
}
}
@ -62,29 +102,30 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration config, string path) => Load(config, path, out _);
public static Image Load(Configuration configuration, string path) => Load(configuration, path, out _);
/// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration config, string path, IImageDecoder decoder)
public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
{
using (Stream stream = config.FileSystem.OpenRead(path))
Guard.NotNull(configuration, nameof(configuration));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(config, stream, decoder);
return Load(configuration, stream, decoder);
}
}
@ -109,7 +150,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return Load<TPixel>(Configuration.Default, path);
}
@ -125,7 +166,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return Load<TPixel>(Configuration.Default, path, out format);
}
@ -133,26 +174,27 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, string path)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Stream stream = config.FileSystem.OpenRead(path))
Guard.NotNull(configuration, nameof(configuration));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, stream);
return Load<TPixel>(configuration, stream);
}
}
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
@ -160,12 +202,13 @@ namespace SixLabors.ImageSharp
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, string path, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Stream stream = config.FileSystem.OpenRead(path))
Guard.NotNull(configuration, nameof(configuration));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, stream, out format);
return Load<TPixel>(configuration, stream, out format);
}
}
@ -173,18 +216,19 @@ namespace SixLabors.ImageSharp
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// The pixel type is selected by the decoder.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration config, string path, out IImageFormat format)
public static Image Load(Configuration configuration, string path, out IImageFormat format)
{
using (Stream stream = config.FileSystem.OpenRead(path))
Guard.NotNull(configuration, nameof(configuration));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(config, stream, out format);
return Load(configuration, stream, out format);
}
}
@ -199,7 +243,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return Load<TPixel>(Configuration.Default, path, decoder);
}
@ -207,7 +251,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
@ -215,12 +259,13 @@ namespace SixLabors.ImageSharp
/// </exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration config, string path, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Stream stream = config.FileSystem.OpenRead(path))
Guard.NotNull(configuration, nameof(configuration));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(config, stream, decoder);
return Load<TPixel>(configuration, stream, decoder);
}
}
}

80
src/ImageSharp/Image.FromStream.cs

@ -26,15 +26,15 @@ namespace SixLabors.ImageSharp
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration config, Stream stream)
=> WithSeekableStream(config, stream, s => InternalDetectFormat(s, config));
public static IImageFormat DetectFormat(Configuration configuration, Stream stream)
=> WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration));
/// <summary>
/// By reading the header on the provided stream this reads the raw image information.
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp
public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _);
/// <summary>
/// By reading the header on the provided stream this reads the raw image information.
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param>
@ -57,16 +57,16 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="config">The configuration.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration config, Stream stream, out IImageFormat format)
public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format)
{
(IImageInfo info, IImageFormat format) data = WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default));
(IImageInfo info, IImageFormat format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default));
format = data.format;
return data.info;
@ -108,24 +108,24 @@ namespace SixLabors.ImageSharp
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image"/>.</returns>>
public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) =>
WithSeekableStream(config, stream, s => decoder.Decode(config, s));
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) =>
WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image"/>.</returns>>
public static Image Load(Configuration config, Stream stream) => Load(config, stream, out _);
public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -136,8 +136,8 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel>
=> Load<TPixel>(null, stream);
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, stream);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -149,8 +149,8 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
=> Load<TPixel>(null, stream, out format);
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, stream, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
@ -162,51 +162,51 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(Configuration.Default, stream, s => decoder.Decode<TPixel>(Configuration.Default, s));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The Configuration.</param>
/// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, IImageDecoder decoder)
where TPixel : struct, IPixel<TPixel>
=> WithSeekableStream(config, stream, s => decoder.Decode<TPixel>(config, s));
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
=> WithSeekableStream(configuration, stream, s => decoder.Decode<TPixel>(configuration, s));
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream)
where TPixel : struct, IPixel<TPixel>
=> Load<TPixel>(config, stream, out IImageFormat _);
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(configuration, stream, out IImageFormat _);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration config, Stream stream, out IImageFormat format)
where TPixel : struct, IPixel<TPixel>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
config = config ?? Configuration.Default;
(Image<TPixel> img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode<TPixel>(s, config));
Guard.NotNull(configuration, nameof(configuration));
(Image<TPixel> img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode<TPixel>(s, configuration));
format = data.format;
@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in config.ImageFormatsManager.ImageDecoders)
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
}
@ -230,16 +230,16 @@ namespace SixLabors.ImageSharp
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="config">The configuration options.</param>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration config, Stream stream, out IImageFormat format)
public static Image Load(Configuration configuration, Stream stream, out IImageFormat format)
{
config = config ?? Configuration.Default;
(Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config));
Guard.NotNull(configuration, nameof(configuration));
(Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration));
format = data.format;
@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp
var sb = new StringBuilder();
sb.AppendLine("Image cannot be loaded. Available decoders:");
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in config.ImageFormatsManager.ImageDecoders)
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders)
{
sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
}
@ -259,7 +259,7 @@ namespace SixLabors.ImageSharp
throw new UnknownImageFormatException(sb.ToString());
}
private static T WithSeekableStream<T>(Configuration config, Stream stream, Func<Stream, T> action)
private static T WithSeekableStream<T>(Configuration configuration, Stream stream, Func<Stream, T> action)
{
if (!stream.CanRead)
{
@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp
if (stream.CanSeek)
{
if (config.ReadOrigin == ReadOrigin.Begin)
if (configuration.ReadOrigin == ReadOrigin.Begin)
{
stream.Position = 0;
}

23
src/ImageSharp/Image.LoadPixelData.cs

@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(byte[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
/// <summary>
@ -58,7 +59,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
/// <summary>
@ -71,7 +72,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, byte[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(new ReadOnlySpan<byte>(data)), width, height);
/// <summary>
@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
/// <summary>
@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, TPixel[] data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return LoadPixelData(config, new ReadOnlySpan<TPixel>(data), width, height);
}
@ -112,16 +113,16 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image<TPixel>(config, width, height);
data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan());
data = data.Slice(0, count);
data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);
return image;
}
}
}
}

18
src/ImageSharp/Image.WrapMemory.cs

@ -32,9 +32,9 @@ namespace SixLabors.ImageSharp
int width,
int height,
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemory);
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory);
return new Image<TPixel>(config, memorySource, width, height, metadata);
}
@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp
Memory<TPixel> pixelMemory,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(config, pixelMemory, width, height, new ImageMetadata());
}
@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp
Memory<TPixel> pixelMemory,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(Configuration.Default, pixelMemory, width, height);
}
@ -97,9 +97,9 @@ namespace SixLabors.ImageSharp
int width,
int height,
ImageMetadata metadata)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var memorySource = new MemorySource<TPixel>(pixelMemoryOwner, false);
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
return new Image<TPixel>(config, memorySource, width, height, metadata);
}
@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata());
}
@ -142,9 +142,9 @@ namespace SixLabors.ImageSharp
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
}
}
}

6
src/ImageSharp/Image.cs

@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.GetConfiguration());
where TPixel2 : unmanaged, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.GetConfiguration());
/// <summary>
/// Returns a copy of the image in the given pixel format.
@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="Image{TPixel2}"/>.</returns>
public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>;
where TPixel2 : unmanaged, IPixel<TPixel2>;
/// <summary>
/// Update the size of the image after mutation.
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp
}
public void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
this.encoder.Encode(image, this.stream);
}

2
src/ImageSharp/ImageExtensions.Internal.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp
/// The <see cref="Buffer2D{TPixel}" />
/// </returns>
internal static Buffer2D<TPixel> GetRootFramePixelBuffer<TPixel>(this Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
return image.Frames.RootFrame.PixelBuffer;
}

2
src/ImageSharp/ImageExtensions.cs

@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp
/// <param name="format">The format.</param>
/// <returns>The <see cref="string"/></returns>
public static string ToBase64String<TPixel>(this Image<TPixel> source, IImageFormat format)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
using (var stream = new MemoryStream())
{

8
src/ImageSharp/ImageFrame.LoadPixelData.cs

@ -4,6 +4,7 @@
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
internal static ImageFrame<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
/// <summary>
@ -36,14 +37,15 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
internal static ImageFrame<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new ImageFrame<TPixel>(configuration, width, height);
data.Slice(0, count).CopyTo(image.GetPixelSpan());
data = data.Slice(0, count);
data.CopyTo(image.PixelBuffer.FastMemoryGroup);
return image;
}

5
src/ImageSharp/ImageFrame.cs

@ -3,6 +3,7 @@
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -78,8 +79,8 @@ namespace SixLabors.ImageSharp
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination)
where TDestinationPixel : struct, IPixel<TDestinationPixel>;
internal abstract void CopyPixelsTo<TDestinationPixel>(MemoryGroup<TDestinationPixel> destination)
where TDestinationPixel : unmanaged, IPixel<TDestinationPixel>;
/// <summary>
/// Updates the size of the image frame.

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

Loading…
Cancel
Save