Browse Source

Merge remote-tracking branch 'upstream/main' into avif-in-heif

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
a39182dcfa
  1. 2
      .editorconfig
  2. 13
      .github/workflows/build-and-test.yml
  3. 10
      .github/workflows/code-coverage.yml
  4. 2
      ImageSharp.sln
  5. 2
      shared-infrastructure
  6. 5
      src/ImageSharp.ruleset
  7. 240
      src/ImageSharp/Color/Color.Conversions.cs
  8. 288
      src/ImageSharp/Color/Color.NamedColors.cs
  9. 182
      src/ImageSharp/Color/Color.cs
  10. 37
      src/ImageSharp/Common/Helpers/ColorNumerics.cs
  11. 47
      src/ImageSharp/Common/Helpers/Numerics.cs
  12. 194
      src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs
  13. 28
      src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs
  14. 20
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs
  15. 178
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs
  16. 30
      src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs
  17. 78
      src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs
  18. 182
      src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
  19. 144
      src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs
  20. 801
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  21. 125
      src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs
  22. 118
      src/ImageSharp/Common/Helpers/SimdUtils.cs
  23. 250
      src/ImageSharp/Common/Helpers/Vector128Utilities.cs
  24. 115
      src/ImageSharp/Common/Helpers/Vector256Utilities.cs
  25. 115
      src/ImageSharp/Common/Helpers/Vector512Utilities.cs
  26. 4
      src/ImageSharp/Formats/AnimationUtilities.cs
  27. 210
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  28. 20
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  29. 7
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  30. 4
      src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
  31. 42
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  32. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
  33. 32
      src/ImageSharp/Formats/Jpeg/JpegComData.cs
  34. 23
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  35. 52
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  36. 7
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  37. 1
      src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs
  38. 53
      src/ImageSharp/Formats/PixelTypeInfo.cs
  39. 2
      src/ImageSharp/Formats/Png/PngBitDepth.cs
  40. 93
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  41. 6
      src/ImageSharp/Formats/Png/PngDecoderOptions.cs
  42. 182
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  43. 6
      src/ImageSharp/Formats/Png/PngMetadata.cs
  44. 122
      src/ImageSharp/Formats/Png/PngScanlineProcessor.cs
  45. 14
      src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs
  46. 108
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  47. 10
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  48. 5
      src/ImageSharp/Formats/Tga/TgaFileHeader.cs
  49. 140
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  50. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  51. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs
  52. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  53. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  54. 13
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  55. 25
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs
  56. 13
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  57. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs
  58. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs
  59. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs
  60. 13
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  61. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  62. 22
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  63. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  64. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  65. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  66. 21
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  67. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs
  68. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  69. 13
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  70. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  71. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
  72. 34
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
  73. 33
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
  74. 32
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
  75. 33
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
  76. 34
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
  77. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
  78. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  79. 18
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs
  80. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs
  81. 14
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  82. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs
  83. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  84. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  85. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  86. 25
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs
  87. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs
  88. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  89. 12
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs
  90. 11
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs
  91. 6
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  92. 120
      src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs
  93. 176
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  94. 26
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  95. 30
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  96. 1
      src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs
  97. 2
      src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
  98. 25
      src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs
  99. 21
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  100. 25
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

2
.editorconfig

@ -172,6 +172,8 @@ dotnet_diagnostic.IDE0063.severity = suggestion
csharp_using_directive_placement = outside_namespace:warning
# Modifier preferences
csharp_prefer_static_local_function = true:warning
# Primary constructor preferences
csharp_style_prefer_primary_constructors = false:none
##########################################
# Unnecessary Code Rules

13
.github/workflows/build-and-test.yml

@ -4,6 +4,7 @@ on:
push:
branches:
- main
- release/*
tags:
- "v*"
pull_request:
@ -67,7 +68,7 @@ jobs:
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Git Setup LFS Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: lfs-cache
with:
path: .git/lfs
@ -77,10 +78,10 @@ jobs:
run: git lfs pull
- name: NuGet Install
uses: NuGet/setup-nuget@v1
uses: NuGet/setup-nuget@v2
- name: NuGet Setup Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: nuget-cache
with:
path: ~/.nuget
@ -132,7 +133,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
@ -159,10 +160,10 @@ jobs:
submodules: recursive
- name: NuGet Install
uses: NuGet/setup-nuget@v1
uses: NuGet/setup-nuget@v2
- name: NuGet Setup Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: nuget-cache
with:
path: ~/.nuget

10
.github/workflows/code-coverage.yml

@ -34,7 +34,7 @@ jobs:
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Git Setup LFS Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: lfs-cache
with:
path: .git/lfs
@ -44,10 +44,10 @@ jobs:
run: git lfs pull
- name: NuGet Install
uses: NuGet/setup-nuget@v1
uses: NuGet/setup-nuget@v2
- name: NuGet Setup Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: nuget-cache
with:
path: ~/.nuget
@ -74,14 +74,14 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/
- name: Codecov Update
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests

2
ImageSharp.sln

@ -38,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3
src\Directory.Build.props = src\Directory.Build.props
src\Directory.Build.targets = src\Directory.Build.targets
src\README.md = src\README.md
src\ImageSharp.ruleset = src\ImageSharp.ruleset
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}"
@ -237,6 +238,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68
tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg
tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg
tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg
tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}"

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 1c526a97eea8bcbc7c79de095676f7fb975a9fb1
Subproject commit 1dbfb576c83507645265c79e03369b66cdc0379f

5
src/ImageSharp.ruleset

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="ImageSharp" ToolsVersion="17.0">
<Include Path="..\shared-infrastructure\sixlabors.ruleset" Action="Default" />
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<Rule Id="SA1011" Action="None" />
</Rules>
</RuleSet>
</RuleSet>

240
src/ImageSharp/Color/Color.Conversions.cs

@ -1,240 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp;
/// <content>
/// Contains constructors and implicit conversion methods.
/// </content>
public readonly partial struct Color
{
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba64 pixel)
{
this.data = pixel;
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgb48"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb48 pixel)
{
this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="La32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(La32 pixel)
{
this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="L16"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(L16 pixel)
{
this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Argb32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgra32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Abgr32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Abgr32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgr24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W);
this.data = default;
}
/// <summary>
/// Converts a <see cref="Color"/> to <see cref="Vector4"/>.
/// </summary>
/// <param name="color">The <see cref="Color"/>.</param>
/// <returns>The <see cref="Vector4"/>.</returns>
public static explicit operator Vector4(Color color) => color.ToScaledVector4();
/// <summary>
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
/// </summary>
/// <param name="source">The <see cref="Vector4"/>.</param>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static explicit operator Color(Vector4 source) => new(source);
[MethodImpl(InliningOptions.ShortMethod)]
internal Rgba32 ToRgba32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgba32();
}
Rgba32 value = default;
this.boxedHighPrecisionPixel.ToRgba32(ref value);
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Bgra32 ToBgra32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgra32();
}
Bgra32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Argb32 ToArgb32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToArgb32();
}
Argb32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Abgr32 ToAbgr32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToAbgr32();
}
Abgr32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Rgb24 ToRgb24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgb24();
}
Rgb24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Bgr24 ToBgr24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgr24();
}
Bgr24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal Vector4 ToScaledVector4()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToScaledVector4();
}
return this.boxedHighPrecisionPixel.ToScaledVector4();
}
}

288
src/ImageSharp/Color/Color.NamedColors.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp;
/// <content>
@ -9,107 +11,107 @@ namespace SixLabors.ImageSharp;
/// </content>
public readonly partial struct Color
{
private static readonly Lazy<Dictionary<string, Color>> NamedColorsLookupLazy = new Lazy<Dictionary<string, Color>>(CreateNamedColorsLookup, true);
private static readonly Lazy<Dictionary<string, Color>> NamedColorsLookupLazy = new(CreateNamedColorsLookup, true);
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
public static readonly Color AliceBlue = FromRgba(240, 248, 255, 255);
public static readonly Color AliceBlue = FromPixel(new Rgba32(240, 248, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FAEBD7.
/// </summary>
public static readonly Color AntiqueWhite = FromRgba(250, 235, 215, 255);
public static readonly Color AntiqueWhite = FromPixel(new Rgba32(250, 235, 215, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary>
public static readonly Color Aqua = FromRgba(0, 255, 255, 255);
public static readonly Color Aqua = FromPixel(new Rgba32(0, 255, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #7FFFD4.
/// </summary>
public static readonly Color Aquamarine = FromRgba(127, 255, 212, 255);
public static readonly Color Aquamarine = FromPixel(new Rgba32(127, 255, 212, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0FFFF.
/// </summary>
public static readonly Color Azure = FromRgba(240, 255, 255, 255);
public static readonly Color Azure = FromPixel(new Rgba32(240, 255, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F5F5DC.
/// </summary>
public static readonly Color Beige = FromRgba(245, 245, 220, 255);
public static readonly Color Beige = FromPixel(new Rgba32(245, 245, 220, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFE4C4.
/// </summary>
public static readonly Color Bisque = FromRgba(255, 228, 196, 255);
public static readonly Color Bisque = FromPixel(new Rgba32(255, 228, 196, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #000000.
/// </summary>
public static readonly Color Black = FromRgba(0, 0, 0, 255);
public static readonly Color Black = FromPixel(new Rgba32(0, 0, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFEBCD.
/// </summary>
public static readonly Color BlanchedAlmond = FromRgba(255, 235, 205, 255);
public static readonly Color BlanchedAlmond = FromPixel(new Rgba32(255, 235, 205, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #0000FF.
/// </summary>
public static readonly Color Blue = FromRgba(0, 0, 255, 255);
public static readonly Color Blue = FromPixel(new Rgba32(0, 0, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8A2BE2.
/// </summary>
public static readonly Color BlueViolet = FromRgba(138, 43, 226, 255);
public static readonly Color BlueViolet = FromPixel(new Rgba32(138, 43, 226, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #A52A2A.
/// </summary>
public static readonly Color Brown = FromRgba(165, 42, 42, 255);
public static readonly Color Brown = FromPixel(new Rgba32(165, 42, 42, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DEB887.
/// </summary>
public static readonly Color BurlyWood = FromRgba(222, 184, 135, 255);
public static readonly Color BurlyWood = FromPixel(new Rgba32(222, 184, 135, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #5F9EA0.
/// </summary>
public static readonly Color CadetBlue = FromRgba(95, 158, 160, 255);
public static readonly Color CadetBlue = FromPixel(new Rgba32(95, 158, 160, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #7FFF00.
/// </summary>
public static readonly Color Chartreuse = FromRgba(127, 255, 0, 255);
public static readonly Color Chartreuse = FromPixel(new Rgba32(127, 255, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D2691E.
/// </summary>
public static readonly Color Chocolate = FromRgba(210, 105, 30, 255);
public static readonly Color Chocolate = FromPixel(new Rgba32(210, 105, 30, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF7F50.
/// </summary>
public static readonly Color Coral = FromRgba(255, 127, 80, 255);
public static readonly Color Coral = FromPixel(new Rgba32(255, 127, 80, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #6495ED.
/// </summary>
public static readonly Color CornflowerBlue = FromRgba(100, 149, 237, 255);
public static readonly Color CornflowerBlue = FromPixel(new Rgba32(100, 149, 237, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFF8DC.
/// </summary>
public static readonly Color Cornsilk = FromRgba(255, 248, 220, 255);
public static readonly Color Cornsilk = FromPixel(new Rgba32(255, 248, 220, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DC143C.
/// </summary>
public static readonly Color Crimson = FromRgba(220, 20, 60, 255);
public static readonly Color Crimson = FromPixel(new Rgba32(220, 20, 60, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FFFF.
@ -119,27 +121,27 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00008B.
/// </summary>
public static readonly Color DarkBlue = FromRgba(0, 0, 139, 255);
public static readonly Color DarkBlue = FromPixel(new Rgba32(0, 0, 139, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #008B8B.
/// </summary>
public static readonly Color DarkCyan = FromRgba(0, 139, 139, 255);
public static readonly Color DarkCyan = FromPixel(new Rgba32(0, 139, 139, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B8860B.
/// </summary>
public static readonly Color DarkGoldenrod = FromRgba(184, 134, 11, 255);
public static readonly Color DarkGoldenrod = FromPixel(new Rgba32(184, 134, 11, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #A9A9A9.
/// </summary>
public static readonly Color DarkGray = FromRgba(169, 169, 169, 255);
public static readonly Color DarkGray = FromPixel(new Rgba32(169, 169, 169, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #006400.
/// </summary>
public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255);
public static readonly Color DarkGreen = FromPixel(new Rgba32(0, 100, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #A9A9A9.
@ -149,52 +151,52 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BDB76B.
/// </summary>
public static readonly Color DarkKhaki = FromRgba(189, 183, 107, 255);
public static readonly Color DarkKhaki = FromPixel(new Rgba32(189, 183, 107, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8B008B.
/// </summary>
public static readonly Color DarkMagenta = FromRgba(139, 0, 139, 255);
public static readonly Color DarkMagenta = FromPixel(new Rgba32(139, 0, 139, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #556B2F.
/// </summary>
public static readonly Color DarkOliveGreen = FromRgba(85, 107, 47, 255);
public static readonly Color DarkOliveGreen = FromPixel(new Rgba32(85, 107, 47, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF8C00.
/// </summary>
public static readonly Color DarkOrange = FromRgba(255, 140, 0, 255);
public static readonly Color DarkOrange = FromPixel(new Rgba32(255, 140, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9932CC.
/// </summary>
public static readonly Color DarkOrchid = FromRgba(153, 50, 204, 255);
public static readonly Color DarkOrchid = FromPixel(new Rgba32(153, 50, 204, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8B0000.
/// </summary>
public static readonly Color DarkRed = FromRgba(139, 0, 0, 255);
public static readonly Color DarkRed = FromPixel(new Rgba32(139, 0, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #E9967A.
/// </summary>
public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255);
public static readonly Color DarkSalmon = FromPixel(new Rgba32(233, 150, 122, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8FBC8F.
/// </summary>
public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255);
public static readonly Color DarkSeaGreen = FromPixel(new Rgba32(143, 188, 143, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #483D8B.
/// </summary>
public static readonly Color DarkSlateBlue = FromRgba(72, 61, 139, 255);
public static readonly Color DarkSlateBlue = FromPixel(new Rgba32(72, 61, 139, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #2F4F4F.
/// </summary>
public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255);
public static readonly Color DarkSlateGray = FromPixel(new Rgba32(47, 79, 79, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #2F4F4F.
@ -204,27 +206,27 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00CED1.
/// </summary>
public static readonly Color DarkTurquoise = FromRgba(0, 206, 209, 255);
public static readonly Color DarkTurquoise = FromPixel(new Rgba32(0, 206, 209, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9400D3.
/// </summary>
public static readonly Color DarkViolet = FromRgba(148, 0, 211, 255);
public static readonly Color DarkViolet = FromPixel(new Rgba32(148, 0, 211, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF1493.
/// </summary>
public static readonly Color DeepPink = FromRgba(255, 20, 147, 255);
public static readonly Color DeepPink = FromPixel(new Rgba32(255, 20, 147, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00BFFF.
/// </summary>
public static readonly Color DeepSkyBlue = FromRgba(0, 191, 255, 255);
public static readonly Color DeepSkyBlue = FromPixel(new Rgba32(0, 191, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #696969.
/// </summary>
public static readonly Color DimGray = FromRgba(105, 105, 105, 255);
public static readonly Color DimGray = FromPixel(new Rgba32(105, 105, 105, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #696969.
@ -234,62 +236,62 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #1E90FF.
/// </summary>
public static readonly Color DodgerBlue = FromRgba(30, 144, 255, 255);
public static readonly Color DodgerBlue = FromPixel(new Rgba32(30, 144, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B22222.
/// </summary>
public static readonly Color Firebrick = FromRgba(178, 34, 34, 255);
public static readonly Color Firebrick = FromPixel(new Rgba32(178, 34, 34, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFAF0.
/// </summary>
public static readonly Color FloralWhite = FromRgba(255, 250, 240, 255);
public static readonly Color FloralWhite = FromPixel(new Rgba32(255, 250, 240, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #228B22.
/// </summary>
public static readonly Color ForestGreen = FromRgba(34, 139, 34, 255);
public static readonly Color ForestGreen = FromPixel(new Rgba32(34, 139, 34, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary>
public static readonly Color Fuchsia = FromRgba(255, 0, 255, 255);
public static readonly Color Fuchsia = FromPixel(new Rgba32(255, 0, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DCDCDC.
/// </summary>
public static readonly Color Gainsboro = FromRgba(220, 220, 220, 255);
public static readonly Color Gainsboro = FromPixel(new Rgba32(220, 220, 220, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F8F8FF.
/// </summary>
public static readonly Color GhostWhite = FromRgba(248, 248, 255, 255);
public static readonly Color GhostWhite = FromPixel(new Rgba32(248, 248, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFD700.
/// </summary>
public static readonly Color Gold = FromRgba(255, 215, 0, 255);
public static readonly Color Gold = FromPixel(new Rgba32(255, 215, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DAA520.
/// </summary>
public static readonly Color Goldenrod = FromRgba(218, 165, 32, 255);
public static readonly Color Goldenrod = FromPixel(new Rgba32(218, 165, 32, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #808080.
/// </summary>
public static readonly Color Gray = FromRgba(128, 128, 128, 255);
public static readonly Color Gray = FromPixel(new Rgba32(128, 128, 128, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #008000.
/// </summary>
public static readonly Color Green = FromRgba(0, 128, 0, 255);
public static readonly Color Green = FromPixel(new Rgba32(0, 128, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #ADFF2F.
/// </summary>
public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255);
public static readonly Color GreenYellow = FromPixel(new Rgba32(173, 255, 47, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #808080.
@ -299,82 +301,82 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0FFF0.
/// </summary>
public static readonly Color Honeydew = FromRgba(240, 255, 240, 255);
public static readonly Color Honeydew = FromPixel(new Rgba32(240, 255, 240, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF69B4.
/// </summary>
public static readonly Color HotPink = FromRgba(255, 105, 180, 255);
public static readonly Color HotPink = FromPixel(new Rgba32(255, 105, 180, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #CD5C5C.
/// </summary>
public static readonly Color IndianRed = FromRgba(205, 92, 92, 255);
public static readonly Color IndianRed = FromPixel(new Rgba32(205, 92, 92, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #4B0082.
/// </summary>
public static readonly Color Indigo = FromRgba(75, 0, 130, 255);
public static readonly Color Indigo = FromPixel(new Rgba32(75, 0, 130, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFFF0.
/// </summary>
public static readonly Color Ivory = FromRgba(255, 255, 240, 255);
public static readonly Color Ivory = FromPixel(new Rgba32(255, 255, 240, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F0E68C.
/// </summary>
public static readonly Color Khaki = FromRgba(240, 230, 140, 255);
public static readonly Color Khaki = FromPixel(new Rgba32(240, 230, 140, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #E6E6FA.
/// </summary>
public static readonly Color Lavender = FromRgba(230, 230, 250, 255);
public static readonly Color Lavender = FromPixel(new Rgba32(230, 230, 250, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFF0F5.
/// </summary>
public static readonly Color LavenderBlush = FromRgba(255, 240, 245, 255);
public static readonly Color LavenderBlush = FromPixel(new Rgba32(255, 240, 245, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #7CFC00.
/// </summary>
public static readonly Color LawnGreen = FromRgba(124, 252, 0, 255);
public static readonly Color LawnGreen = FromPixel(new Rgba32(124, 252, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFACD.
/// </summary>
public static readonly Color LemonChiffon = FromRgba(255, 250, 205, 255);
public static readonly Color LemonChiffon = FromPixel(new Rgba32(255, 250, 205, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #ADD8E6.
/// </summary>
public static readonly Color LightBlue = FromRgba(173, 216, 230, 255);
public static readonly Color LightBlue = FromPixel(new Rgba32(173, 216, 230, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F08080.
/// </summary>
public static readonly Color LightCoral = FromRgba(240, 128, 128, 255);
public static readonly Color LightCoral = FromPixel(new Rgba32(240, 128, 128, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #E0FFFF.
/// </summary>
public static readonly Color LightCyan = FromRgba(224, 255, 255, 255);
public static readonly Color LightCyan = FromPixel(new Rgba32(224, 255, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FAFAD2.
/// </summary>
public static readonly Color LightGoldenrodYellow = FromRgba(250, 250, 210, 255);
public static readonly Color LightGoldenrodYellow = FromPixel(new Rgba32(250, 250, 210, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D3D3D3.
/// </summary>
public static readonly Color LightGray = FromRgba(211, 211, 211, 255);
public static readonly Color LightGray = FromPixel(new Rgba32(211, 211, 211, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #90EE90.
/// </summary>
public static readonly Color LightGreen = FromRgba(144, 238, 144, 255);
public static readonly Color LightGreen = FromPixel(new Rgba32(144, 238, 144, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D3D3D3.
@ -384,27 +386,27 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFB6C1.
/// </summary>
public static readonly Color LightPink = FromRgba(255, 182, 193, 255);
public static readonly Color LightPink = FromPixel(new Rgba32(255, 182, 193, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFA07A.
/// </summary>
public static readonly Color LightSalmon = FromRgba(255, 160, 122, 255);
public static readonly Color LightSalmon = FromPixel(new Rgba32(255, 160, 122, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #20B2AA.
/// </summary>
public static readonly Color LightSeaGreen = FromRgba(32, 178, 170, 255);
public static readonly Color LightSeaGreen = FromPixel(new Rgba32(32, 178, 170, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #87CEFA.
/// </summary>
public static readonly Color LightSkyBlue = FromRgba(135, 206, 250, 255);
public static readonly Color LightSkyBlue = FromPixel(new Rgba32(135, 206, 250, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #778899.
/// </summary>
public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255);
public static readonly Color LightSlateGray = FromPixel(new Rgba32(119, 136, 153, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #778899.
@ -414,27 +416,27 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B0C4DE.
/// </summary>
public static readonly Color LightSteelBlue = FromRgba(176, 196, 222, 255);
public static readonly Color LightSteelBlue = FromPixel(new Rgba32(176, 196, 222, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFFE0.
/// </summary>
public static readonly Color LightYellow = FromRgba(255, 255, 224, 255);
public static readonly Color LightYellow = FromPixel(new Rgba32(255, 255, 224, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FF00.
/// </summary>
public static readonly Color Lime = FromRgba(0, 255, 0, 255);
public static readonly Color Lime = FromPixel(new Rgba32(0, 255, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #32CD32.
/// </summary>
public static readonly Color LimeGreen = FromRgba(50, 205, 50, 255);
public static readonly Color LimeGreen = FromPixel(new Rgba32(50, 205, 50, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FAF0E6.
/// </summary>
public static readonly Color Linen = FromRgba(250, 240, 230, 255);
public static readonly Color Linen = FromPixel(new Rgba32(250, 240, 230, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF00FF.
@ -444,237 +446,237 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #800000.
/// </summary>
public static readonly Color Maroon = FromRgba(128, 0, 0, 255);
public static readonly Color Maroon = FromPixel(new Rgba32(128, 0, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #66CDAA.
/// </summary>
public static readonly Color MediumAquamarine = FromRgba(102, 205, 170, 255);
public static readonly Color MediumAquamarine = FromPixel(new Rgba32(102, 205, 170, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #0000CD.
/// </summary>
public static readonly Color MediumBlue = FromRgba(0, 0, 205, 255);
public static readonly Color MediumBlue = FromPixel(new Rgba32(0, 0, 205, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BA55D3.
/// </summary>
public static readonly Color MediumOrchid = FromRgba(186, 85, 211, 255);
public static readonly Color MediumOrchid = FromPixel(new Rgba32(186, 85, 211, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #9370DB.
/// </summary>
public static readonly Color MediumPurple = FromRgba(147, 112, 219, 255);
public static readonly Color MediumPurple = FromPixel(new Rgba32(147, 112, 219, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #3CB371.
/// </summary>
public static readonly Color MediumSeaGreen = FromRgba(60, 179, 113, 255);
public static readonly Color MediumSeaGreen = FromPixel(new Rgba32(60, 179, 113, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #7B68EE.
/// </summary>
public static readonly Color MediumSlateBlue = FromRgba(123, 104, 238, 255);
public static readonly Color MediumSlateBlue = FromPixel(new Rgba32(123, 104, 238, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FA9A.
/// </summary>
public static readonly Color MediumSpringGreen = FromRgba(0, 250, 154, 255);
public static readonly Color MediumSpringGreen = FromPixel(new Rgba32(0, 250, 154, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #48D1CC.
/// </summary>
public static readonly Color MediumTurquoise = FromRgba(72, 209, 204, 255);
public static readonly Color MediumTurquoise = FromPixel(new Rgba32(72, 209, 204, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #C71585.
/// </summary>
public static readonly Color MediumVioletRed = FromRgba(199, 21, 133, 255);
public static readonly Color MediumVioletRed = FromPixel(new Rgba32(199, 21, 133, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #191970.
/// </summary>
public static readonly Color MidnightBlue = FromRgba(25, 25, 112, 255);
public static readonly Color MidnightBlue = FromPixel(new Rgba32(25, 25, 112, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F5FFFA.
/// </summary>
public static readonly Color MintCream = FromRgba(245, 255, 250, 255);
public static readonly Color MintCream = FromPixel(new Rgba32(245, 255, 250, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFE4E1.
/// </summary>
public static readonly Color MistyRose = FromRgba(255, 228, 225, 255);
public static readonly Color MistyRose = FromPixel(new Rgba32(255, 228, 225, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFE4B5.
/// </summary>
public static readonly Color Moccasin = FromRgba(255, 228, 181, 255);
public static readonly Color Moccasin = FromPixel(new Rgba32(255, 228, 181, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFDEAD.
/// </summary>
public static readonly Color NavajoWhite = FromRgba(255, 222, 173, 255);
public static readonly Color NavajoWhite = FromPixel(new Rgba32(255, 222, 173, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #000080.
/// </summary>
public static readonly Color Navy = FromRgba(0, 0, 128, 255);
public static readonly Color Navy = FromPixel(new Rgba32(0, 0, 128, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FDF5E6.
/// </summary>
public static readonly Color OldLace = FromRgba(253, 245, 230, 255);
public static readonly Color OldLace = FromPixel(new Rgba32(253, 245, 230, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #808000.
/// </summary>
public static readonly Color Olive = FromRgba(128, 128, 0, 255);
public static readonly Color Olive = FromPixel(new Rgba32(128, 128, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #6B8E23.
/// </summary>
public static readonly Color OliveDrab = FromRgba(107, 142, 35, 255);
public static readonly Color OliveDrab = FromPixel(new Rgba32(107, 142, 35, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFA500.
/// </summary>
public static readonly Color Orange = FromRgba(255, 165, 0, 255);
public static readonly Color Orange = FromPixel(new Rgba32(255, 165, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF4500.
/// </summary>
public static readonly Color OrangeRed = FromRgba(255, 69, 0, 255);
public static readonly Color OrangeRed = FromPixel(new Rgba32(255, 69, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DA70D6.
/// </summary>
public static readonly Color Orchid = FromRgba(218, 112, 214, 255);
public static readonly Color Orchid = FromPixel(new Rgba32(218, 112, 214, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #EEE8AA.
/// </summary>
public static readonly Color PaleGoldenrod = FromRgba(238, 232, 170, 255);
public static readonly Color PaleGoldenrod = FromPixel(new Rgba32(238, 232, 170, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #98FB98.
/// </summary>
public static readonly Color PaleGreen = FromRgba(152, 251, 152, 255);
public static readonly Color PaleGreen = FromPixel(new Rgba32(152, 251, 152, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #AFEEEE.
/// </summary>
public static readonly Color PaleTurquoise = FromRgba(175, 238, 238, 255);
public static readonly Color PaleTurquoise = FromPixel(new Rgba32(175, 238, 238, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DB7093.
/// </summary>
public static readonly Color PaleVioletRed = FromRgba(219, 112, 147, 255);
public static readonly Color PaleVioletRed = FromPixel(new Rgba32(219, 112, 147, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFEFD5.
/// </summary>
public static readonly Color PapayaWhip = FromRgba(255, 239, 213, 255);
public static readonly Color PapayaWhip = FromPixel(new Rgba32(255, 239, 213, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFDAB9.
/// </summary>
public static readonly Color PeachPuff = FromRgba(255, 218, 185, 255);
public static readonly Color PeachPuff = FromPixel(new Rgba32(255, 218, 185, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #CD853F.
/// </summary>
public static readonly Color Peru = FromRgba(205, 133, 63, 255);
public static readonly Color Peru = FromPixel(new Rgba32(205, 133, 63, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFC0CB.
/// </summary>
public static readonly Color Pink = FromRgba(255, 192, 203, 255);
public static readonly Color Pink = FromPixel(new Rgba32(255, 192, 203, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #DDA0DD.
/// </summary>
public static readonly Color Plum = FromRgba(221, 160, 221, 255);
public static readonly Color Plum = FromPixel(new Rgba32(221, 160, 221, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #B0E0E6.
/// </summary>
public static readonly Color PowderBlue = FromRgba(176, 224, 230, 255);
public static readonly Color PowderBlue = FromPixel(new Rgba32(176, 224, 230, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #800080.
/// </summary>
public static readonly Color Purple = FromRgba(128, 0, 128, 255);
public static readonly Color Purple = FromPixel(new Rgba32(128, 0, 128, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #663399.
/// </summary>
public static readonly Color RebeccaPurple = FromRgba(102, 51, 153, 255);
public static readonly Color RebeccaPurple = FromPixel(new Rgba32(102, 51, 153, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF0000.
/// </summary>
public static readonly Color Red = FromRgba(255, 0, 0, 255);
public static readonly Color Red = FromPixel(new Rgba32(255, 0, 0, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #BC8F8F.
/// </summary>
public static readonly Color RosyBrown = FromRgba(188, 143, 143, 255);
public static readonly Color RosyBrown = FromPixel(new Rgba32(188, 143, 143, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #4169E1.
/// </summary>
public static readonly Color RoyalBlue = FromRgba(65, 105, 225, 255);
public static readonly Color RoyalBlue = FromPixel(new Rgba32(65, 105, 225, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #8B4513.
/// </summary>
public static readonly Color SaddleBrown = FromRgba(139, 69, 19, 255);
public static readonly Color SaddleBrown = FromPixel(new Rgba32(139, 69, 19, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FA8072.
/// </summary>
public static readonly Color Salmon = FromRgba(250, 128, 114, 255);
public static readonly Color Salmon = FromPixel(new Rgba32(250, 128, 114, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F4A460.
/// </summary>
public static readonly Color SandyBrown = FromRgba(244, 164, 96, 255);
public static readonly Color SandyBrown = FromPixel(new Rgba32(244, 164, 96, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #2E8B57.
/// </summary>
public static readonly Color SeaGreen = FromRgba(46, 139, 87, 255);
public static readonly Color SeaGreen = FromPixel(new Rgba32(46, 139, 87, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFF5EE.
/// </summary>
public static readonly Color SeaShell = FromRgba(255, 245, 238, 255);
public static readonly Color SeaShell = FromPixel(new Rgba32(255, 245, 238, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #A0522D.
/// </summary>
public static readonly Color Sienna = FromRgba(160, 82, 45, 255);
public static readonly Color Sienna = FromPixel(new Rgba32(160, 82, 45, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #C0C0C0.
/// </summary>
public static readonly Color Silver = FromRgba(192, 192, 192, 255);
public static readonly Color Silver = FromPixel(new Rgba32(192, 192, 192, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #87CEEB.
/// </summary>
public static readonly Color SkyBlue = FromRgba(135, 206, 235, 255);
public static readonly Color SkyBlue = FromPixel(new Rgba32(135, 206, 235, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #6A5ACD.
/// </summary>
public static readonly Color SlateBlue = FromRgba(106, 90, 205, 255);
public static readonly Color SlateBlue = FromPixel(new Rgba32(106, 90, 205, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #708090.
/// </summary>
public static readonly Color SlateGray = FromRgba(112, 128, 144, 255);
public static readonly Color SlateGray = FromPixel(new Rgba32(112, 128, 144, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #708090.
@ -684,81 +686,80 @@ public readonly partial struct Color
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFAFA.
/// </summary>
public static readonly Color Snow = FromRgba(255, 250, 250, 255);
public static readonly Color Snow = FromPixel(new Rgba32(255, 250, 250, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00FF7F.
/// </summary>
public static readonly Color SpringGreen = FromRgba(0, 255, 127, 255);
public static readonly Color SpringGreen = FromPixel(new Rgba32(0, 255, 127, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #4682B4.
/// </summary>
public static readonly Color SteelBlue = FromRgba(70, 130, 180, 255);
public static readonly Color SteelBlue = FromPixel(new Rgba32(70, 130, 180, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D2B48C.
/// </summary>
public static readonly Color Tan = FromRgba(210, 180, 140, 255);
public static readonly Color Tan = FromPixel(new Rgba32(210, 180, 140, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #008080.
/// </summary>
public static readonly Color Teal = FromRgba(0, 128, 128, 255);
public static readonly Color Teal = FromPixel(new Rgba32(0, 128, 128, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #D8BFD8.
/// </summary>
public static readonly Color Thistle = FromRgba(216, 191, 216, 255);
public static readonly Color Thistle = FromPixel(new Rgba32(216, 191, 216, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FF6347.
/// </summary>
public static readonly Color Tomato = FromRgba(255, 99, 71, 255);
public static readonly Color Tomato = FromPixel(new Rgba32(255, 99, 71, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #00000000.
/// </summary>
public static readonly Color Transparent = FromRgba(0, 0, 0, 0);
public static readonly Color Transparent = FromPixel(new Rgba32(0, 0, 0, 0));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #40E0D0.
/// </summary>
public static readonly Color Turquoise = FromRgba(64, 224, 208, 255);
public static readonly Color Turquoise = FromPixel(new Rgba32(64, 224, 208, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #EE82EE.
/// </summary>
public static readonly Color Violet = FromRgba(238, 130, 238, 255);
public static readonly Color Violet = FromPixel(new Rgba32(238, 130, 238, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F5DEB3.
/// </summary>
public static readonly Color Wheat = FromRgba(245, 222, 179, 255);
public static readonly Color Wheat = FromPixel(new Rgba32(245, 222, 179, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFFFF.
/// </summary>
public static readonly Color White = FromRgba(255, 255, 255, 255);
public static readonly Color White = FromPixel(new Rgba32(255, 255, 255, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #F5F5F5.
/// </summary>
public static readonly Color WhiteSmoke = FromRgba(245, 245, 245, 255);
public static readonly Color WhiteSmoke = FromPixel(new Rgba32(245, 245, 245, 255));
/// <summary>
/// Represents a <see paramref="Color"/> matching the W3C definition that has an hex value of #FFFF00.
/// </summary>
public static readonly Color Yellow = FromRgba(255, 255, 0, 255);
public static readonly Color Yellow = FromPixel(new Rgba32(255, 255, 0, 255));
/// <summary>
/// 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);
public static readonly Color YellowGreen = FromPixel(new Rgba32(154, 205, 50, 255));
private static Dictionary<string, Color> CreateNamedColorsLookup()
{
return new Dictionary<string, Color>(StringComparer.OrdinalIgnoreCase)
=> new(StringComparer.OrdinalIgnoreCase)
{
{ nameof(AliceBlue), AliceBlue },
{ nameof(AntiqueWhite), AntiqueWhite },
@ -910,5 +911,4 @@ public readonly partial struct Color
{ nameof(Yellow), Yellow },
{ nameof(YellowGreen), YellowGreen }
};
}
}

182
src/ImageSharp/Color/Color.cs

@ -18,34 +18,25 @@ namespace SixLabors.ImageSharp;
/// </remarks>
public readonly partial struct Color : IEquatable<Color>
{
private readonly Rgba64 data;
private readonly Vector4 data;
private readonly IPixel? boxedHighPrecisionPixel;
[MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b, byte a)
{
this.data = new Rgba64(
ColorNumerics.UpscaleFrom8BitTo16Bit(r),
ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ColorNumerics.UpscaleFrom8BitTo16Bit(a));
this.boxedHighPrecisionPixel = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b)
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Color(Vector4 vector)
{
this.data = new Rgba64(
ColorNumerics.UpscaleFrom8BitTo16Bit(r),
ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ushort.MaxValue);
this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The pixel containing color information.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Color(IPixel pixel)
{
this.boxedHighPrecisionPixel = pixel;
@ -61,7 +52,7 @@ public readonly partial struct Color : IEquatable<Color>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Color left, Color right) => left.Equals(right);
/// <summary>
@ -73,66 +64,64 @@ public readonly partial struct Color : IEquatable<Color>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Color left, Color right) => !left.Equals(right);
/// <summary>
/// Creates a <see cref="Color"/> from RGBA bytes.
/// Creates a <see cref="Color"/> from the given <typeparamref name="TPixel"/>.
/// </summary>
/// <param name="r">The red component (0-255).</param>
/// <param name="g">The green component (0-255).</param>
/// <param name="b">The blue component (0-255).</param>
/// <param name="a">The alpha component (0-255).</param>
/// <param name="source">The pixel to convert from.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color FromPixel<TPixel>(TPixel source)
where TPixel : unmanaged, IPixel<TPixel>
{
// Avoid boxing in case we can convert to Vector4 safely and efficiently
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
{
return new(source.ToScaledVector4());
}
return new(source);
}
/// <summary>
/// Creates a <see cref="Color"/> from RGB bytes.
/// Creates a <see cref="Color"/> from a generic scaled <see cref="Vector4"/>.
/// </summary>
/// <param name="r">The red component (0-255).</param>
/// <param name="g">The green component (0-255).</param>
/// <param name="b">The blue component (0-255).</param>
/// <param name="source">The vector to load the pixel from.</param>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color FromScaledVector(Vector4 source) => new(source);
/// <summary>
/// Creates a <see cref="Color"/> from the given <typeparamref name="TPixel"/>.
/// Bulk converts a span of a specified <typeparamref name="TPixel"/> type to a span of <see cref="Color"/>.
/// </summary>
/// <param name="pixel">The pixel to convert from.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromPixel<TPixel>(TPixel pixel)
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="source">The source pixel span.</param>
/// <param name="destination">The destination color span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FromPixel<TPixel>(ReadOnlySpan<TPixel> source, Span<Color> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
// Avoid boxing in case we can convert to Rgba64 safely and efficently
if (typeof(TPixel) == typeof(Rgba64))
{
return new((Rgba64)(object)pixel);
}
else if (typeof(TPixel) == typeof(Rgb48))
{
return new((Rgb48)(object)pixel);
}
else if (typeof(TPixel) == typeof(La32))
{
return new((La32)(object)pixel);
}
else if (typeof(TPixel) == typeof(L16))
{
return new((L16)(object)pixel);
}
else if (Unsafe.SizeOf<TPixel>() <= Unsafe.SizeOf<Rgba32>())
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
// Avoid boxing in case we can convert to Vector4 safely and efficiently
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
{
Rgba32 p = default;
pixel.ToRgba32(ref p);
return new(p);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = FromScaledVector(source[i].ToScaledVector4());
}
}
else
{
return new(pixel);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = new(source[i]);
}
}
}
@ -147,12 +136,11 @@ public readonly partial struct Color : IEquatable<Color>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color ParseHex(string hex)
{
Rgba32 rgba = Rgba32.ParseHex(hex);
return new Color(rgba);
return FromPixel(rgba);
}
/// <summary>
@ -167,14 +155,14 @@ public readonly partial struct Color : IEquatable<Color>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseHex(string hex, out Color result)
{
result = default;
if (Rgba32.TryParseHex(hex, out Rgba32 rgba))
{
result = new Color(rgba);
result = FromPixel(rgba);
return true;
}
@ -241,26 +229,24 @@ public readonly partial struct Color : IEquatable<Color>
/// <returns>The color having it's alpha channel altered.</returns>
public Color WithAlpha(float alpha)
{
Vector4 v = (Vector4)this;
Vector4 v = this.ToScaledVector4();
v.W = alpha;
return new Color(v);
return FromScaledVector(v);
}
/// <summary>
/// Gets the hexadecimal representation of the color instance in rrggbbaa form.
/// </summary>
/// <returns>A hexadecimal string representation of the value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ToHex()
{
if (this.boxedHighPrecisionPixel is not null)
{
Rgba32 rgba = default;
this.boxedHighPrecisionPixel.ToRgba32(ref rgba);
return rgba.ToHex();
return this.boxedHighPrecisionPixel.ToRgba32().ToHex();
}
return this.data.ToRgba32().ToHex();
return Rgba32.FromScaledVector4(this.data).ToHex();
}
/// <inheritdoc />
@ -270,8 +256,8 @@ public readonly partial struct Color : IEquatable<Color>
/// Converts the color instance to a specified <typeparamref name="TPixel"/> type.
/// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <returns>The pixel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
/// <returns>The <typeparamref name="TPixel"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TPixel ToPixel<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
@ -282,14 +268,27 @@ public readonly partial struct Color : IEquatable<Color>
if (this.boxedHighPrecisionPixel is null)
{
pixel = default;
pixel.FromRgba64(this.data);
return pixel;
return TPixel.FromScaledVector4(this.data);
}
return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
}
/// <summary>
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
/// The vector components are typically expanded in least to greatest significance order.
/// </summary>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data;
}
pixel = default;
pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return pixel;
return this.boxedHighPrecisionPixel.ToScaledVector4();
}
/// <summary>
@ -298,11 +297,12 @@ public readonly partial struct Color : IEquatable<Color>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="source">The source color span.</param>
/// <param name="destination">The destination pixel span.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToPixel<TPixel>(ReadOnlySpan<Color> source, Span<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Investigate bulk operations utilizing configuration parameter here.
// We cannot use bulk pixel operations here as there is no guarantee that the source colors are
// created from pixel formats which fit into the unboxed vector data.
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
for (int i = 0; i < source.Length; i++)
{
@ -311,12 +311,12 @@ public readonly partial struct Color : IEquatable<Color>
}
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Color other)
{
if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue == other.data.PackedValue;
return this.data == other.data;
}
return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
@ -326,12 +326,12 @@ public readonly partial struct Color : IEquatable<Color>
public override bool Equals(object? obj) => obj is Color other && this.Equals(other);
/// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue.GetHashCode();
return this.data.GetHashCode();
}
return this.boxedHighPrecisionPixel.GetHashCode();

37
src/ImageSharp/Common/Helpers/ColorNumerics.cs

@ -41,6 +41,34 @@ internal static class ColorNumerics
public static byte Get8BitBT709Luminance(byte r, byte g, byte b)
=> (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F);
/// <summary>
/// Gets the luminance from the rgb components using the formula
/// as specified by ITU-R Recommendation BT.709.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="byte"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Get8BitBT709Luminance(ushort r, ushort g, ushort b)
=> (byte)((From16BitTo8Bit(r) * .2126F) +
(From16BitTo8Bit(g) * .7152F) +
(From16BitTo8Bit(b) * .0722F) + 0.5F);
/// <summary>
/// Gets the luminance from the rgb components using the formula as
/// specified by ITU-R Recommendation BT.709.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="ushort"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort Get16BitBT709Luminance(byte r, byte g, byte b)
=> (ushort)((From8BitTo16Bit(r) * .2126F) +
(From8BitTo16Bit(g) * .7152F) +
(From8BitTo16Bit(b) * .0722F) + 0.5F);
/// <summary>
/// Gets the luminance from the rgb components using the formula as
/// specified by ITU-R Recommendation BT.709.
@ -72,8 +100,8 @@ internal static class ColorNumerics
/// <param name="component">The 8 bit component value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte DownScaleFrom16BitTo8Bit(ushort component)
{
public static byte From16BitTo8Bit(ushort component) =>
// To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is:
//
// (V * 255) / 65535
@ -102,8 +130,7 @@ internal static class ColorNumerics
// An alternative arithmetic calculation which also gives no errors is:
//
// (V * 255 + 32895) >> 16
return (byte)(((component * 255) + 32895) >> 16);
}
(byte)(((component * 255) + 32895) >> 16);
/// <summary>
/// Scales a value from an 8 bit <see cref="byte"/> to
@ -112,7 +139,7 @@ internal static class ColorNumerics
/// <param name="component">The 8 bit component value.</param>
/// <returns>The <see cref="ushort"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort UpscaleFrom8BitTo16Bit(byte component)
public static ushort From8BitTo16Bit(byte component)
=> (ushort)(component * 257);
/// <summary>

47
src/ImageSharp/Common/Helpers/Numerics.cs

@ -5,7 +5,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp;
@ -61,6 +60,12 @@ internal static class Numerics
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo4(nint x) => x & 3;
/// <summary>
/// Calculates <paramref name="x"/> % 4
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nuint Modulo4(nuint x) => x & 3;
/// <summary>
/// Calculates <paramref name="x"/> % 8
/// </summary>
@ -1005,6 +1010,26 @@ internal static class Numerics
where TVector : struct
=> (uint)span.Length / (uint)Vector256<TVector>.Count;
/// <summary>
/// Gets the count of vectors that safely fit into the given span.
/// </summary>
/// <typeparam name="TVector">The type of the vector.</typeparam>
/// <param name="span">The given span.</param>
/// <returns>Count of vectors that safely fit into the span.</returns>
public static nuint Vector512Count<TVector>(this Span<byte> span)
where TVector : struct
=> (uint)span.Length / (uint)Vector512<TVector>.Count;
/// <summary>
/// Gets the count of vectors that safely fit into the given span.
/// </summary>
/// <typeparam name="TVector">The type of the vector.</typeparam>
/// <param name="span">The given span.</param>
/// <returns>Count of vectors that safely fit into the span.</returns>
public static nuint Vector512Count<TVector>(this ReadOnlySpan<byte> span)
where TVector : struct
=> (uint)span.Length / (uint)Vector512<TVector>.Count;
/// <summary>
/// Gets the count of vectors that safely fit into the given span.
/// </summary>
@ -1044,4 +1069,24 @@ internal static class Numerics
public static nuint Vector256Count<TVector>(int length)
where TVector : struct
=> (uint)length / (uint)Vector256<TVector>.Count;
/// <summary>
/// Gets the count of vectors that safely fit into the given span.
/// </summary>
/// <typeparam name="TVector">The type of the vector.</typeparam>
/// <param name="span">The given span.</param>
/// <returns>Count of vectors that safely fit into the span.</returns>
public static nuint Vector512Count<TVector>(this Span<float> span)
where TVector : struct
=> (uint)span.Length / (uint)Vector512<TVector>.Count;
/// <summary>
/// Gets the count of vectors that safely fit into length.
/// </summary>
/// <typeparam name="TVector">The type of the vector.</typeparam>
/// <param name="length">The given length.</param>
/// <returns>Count of vectors that safely fit into the length.</returns>
public static nuint Vector512Count<TVector>(int length)
where TVector : struct
=> (uint)length / (uint)Vector512<TVector>.Count;
}

194
src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs

@ -1,12 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
// The JIT can detect and optimize rotation idioms ROTL (Rotate Left)
// and ROTR (Rotate Right) emitting efficient CPU instructions:
// https://github.com/dotnet/coreclr/pull/1830
@ -19,190 +13,24 @@ namespace SixLabors.ImageSharp;
internal interface IComponentShuffle
{
/// <summary>
/// Shuffles then slices 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// Shuffles then slices 8-bit integers in <paramref name="source"/>
/// using a byte control and store the results in <paramref name="destination"/>.
/// If successful, this method will reduce the length of <paramref name="source"/> length
/// by the shuffle amount.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest);
/// <param name="destination">The destination span of bytes.</param>
void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination);
/// <summary>
/// Shuffle 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// Shuffle 8-bit integers in <paramref name="source"/>
/// using the control and store the results in <paramref name="destination"/>.
/// </summary>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="destination">The destination span of bytes.</param>
/// <remarks>
/// Implementation can assume that source.Length is less or equal than dest.Length.
/// Implementation can assume that source.Length is less or equal than destination.Length.
/// Loops should iterate using source.Length.
/// </remarks>
void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest);
}
/// <inheritdoc/>
internal interface IShuffle4 : IComponentShuffle
{
}
internal readonly struct DefaultShuffle4 : IShuffle4
{
public DefaultShuffle4(byte control)
=> this.Control = control;
public byte Control { get; }
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, this.Control);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0);
for (nuint i = 0; i < (uint)source.Length; i += 4)
{
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i);
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i);
}
}
}
internal readonly struct WXYZShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle2103);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(dest));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// ROTL(8, packed) = [Z Y X W]
Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24);
}
}
}
internal readonly struct WZYXShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle0123);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(dest));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// REVERSE(packedArgb) = [X Y Z W]
Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed);
}
}
}
internal readonly struct YZWXShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle0321);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(dest));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// ROTR(8, packedArgb) = [Y Z W X]
Unsafe.Add(ref dBase, i) = BitOperations.RotateRight(packed, 8);
}
}
}
internal readonly struct ZYXWShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle3012);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(dest));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// tmp1 = [W 0 Y 0]
// tmp2 = [0 Z 0 X]
// tmp3=ROTL(16, tmp2) = [0 X 0 Z]
// tmp1 + tmp3 = [W X Y Z]
uint tmp1 = packed & 0xFF00FF00;
uint tmp2 = packed & 0x00FF00FF;
uint tmp3 = BitOperations.RotateLeft(tmp2, 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}
}
}
internal readonly struct XWZYShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle1230);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(dest));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// tmp1 = [0 Z 0 X]
// tmp2 = [W 0 Y 0]
// tmp3=ROTL(16, tmp2) = [Y 0 W 0]
// tmp1 + tmp3 = [Y Z W X]
uint tmp1 = packed & 0x00FF00FF;
uint tmp2 = packed & 0xFF00FF00;
uint tmp3 = BitOperations.RotateLeft(tmp2, 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}
}
void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination);
}

28
src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
@ -12,24 +13,23 @@ internal interface IPad3Shuffle4 : IComponentShuffle
{
}
internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4
internal readonly struct DefaultPad3Shuffle4([ConstantExpected] byte control) : IPad3Shuffle4
{
public DefaultPad3Shuffle4(byte control)
=> this.Control = control;
public byte Control { get; }
public byte Control { get; } = control;
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, this.Control);
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
#pragma warning disable CA1857 // A constant is expected for the parameter
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, this.Control);
#pragma warning restore CA1857 // A constant is expected for the parameter
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0);
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0);
Span<byte> temp = stackalloc byte[4];
ref byte t = ref MemoryMarshal.GetReference(temp);
@ -51,14 +51,14 @@ internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4
internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, Shuffle.MMShuffle3210);
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
ref byte sEnd = ref Unsafe.Add(ref sBase, (uint)source.Length);
ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4);

20
src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
@ -12,24 +13,23 @@ internal interface IShuffle3 : IComponentShuffle
{
}
internal readonly struct DefaultShuffle3 : IShuffle3
internal readonly struct DefaultShuffle3([ConstantExpected] byte control) : IShuffle3
{
public DefaultShuffle3(byte control)
=> this.Control = control;
public byte Control { get; }
public byte Control { get; } = control;
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle3Reduce(ref source, ref dest, this.Control);
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
#pragma warning disable CA1857 // A constant is expected for the parameter
=> HwIntrinsics.Shuffle3Reduce(ref source, ref destination, this.Control);
#pragma warning restore CA1857 // A constant is expected for the parameter
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0);
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0);
for (nuint i = 0; i < (uint)source.Length; i += 3)
{

178
src/ImageSharp/Common/Helpers/Shuffle/IShuffle4.cs

@ -0,0 +1,178 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
namespace SixLabors.ImageSharp;
/// <inheritdoc/>
internal interface IShuffle4 : IComponentShuffle
{
}
internal readonly struct DefaultShuffle4([ConstantExpected] byte control) : IShuffle4
{
public byte Control { get; } = control;
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
#pragma warning disable CA1857 // A constant is expected for the parameter
=> HwIntrinsics.Shuffle4Reduce(ref source, ref destination, this.Control);
#pragma warning restore CA1857 // A constant is expected for the parameter
[MethodImpl(InliningOptions.ShortMethod)]
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0);
for (nuint i = 0; i < (uint)source.Length; i += 4)
{
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i);
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i);
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i);
Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i);
}
}
}
internal readonly struct WXYZShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle2103);
[MethodImpl(InliningOptions.ShortMethod)]
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(destination));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// ROTL(8, packed) = [Z Y X W]
Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24);
}
}
}
internal readonly struct WZYXShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle0123);
[MethodImpl(InliningOptions.ShortMethod)]
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(destination));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// REVERSE(packedArgb) = [X Y Z W]
Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed);
}
}
}
internal readonly struct YZWXShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle0321);
[MethodImpl(InliningOptions.ShortMethod)]
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(destination));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// ROTR(8, packedArgb) = [Y Z W X]
Unsafe.Add(ref dBase, i) = BitOperations.RotateRight(packed, 8);
}
}
}
internal readonly struct ZYXWShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3012);
[MethodImpl(InliningOptions.ShortMethod)]
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(destination));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// tmp1 = [W 0 Y 0]
// tmp2 = [0 Z 0 X]
// tmp3=ROTL(16, tmp2) = [0 X 0 Z]
// tmp1 + tmp3 = [W X Y Z]
uint tmp1 = packed & 0xFF00FF00;
uint tmp2 = packed & 0x00FF00FF;
uint tmp3 = BitOperations.RotateLeft(tmp2, 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}
}
}
internal readonly struct XWZYShuffle4 : IShuffle4
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle1230);
[MethodImpl(InliningOptions.ShortMethod)]
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref uint dBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(destination));
uint n = (uint)source.Length / 4;
for (nuint i = 0; i < n; i++)
{
uint packed = Unsafe.Add(ref sBase, i);
// packed = [W Z Y X]
// tmp1 = [0 Z 0 X]
// tmp2 = [W 0 Y 0]
// tmp3=ROTL(16, tmp2) = [Y 0 W 0]
// tmp1 + tmp3 = [Y Z W X]
uint tmp1 = packed & 0x00FF00FF;
uint tmp2 = packed & 0xFF00FF00;
uint tmp3 = BitOperations.RotateLeft(tmp2, 16);
Unsafe.Add(ref dBase, i) = tmp1 + tmp3;
}
}
}

30
src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static SixLabors.ImageSharp.SimdUtils;
@ -12,26 +13,25 @@ internal interface IShuffle4Slice3 : IComponentShuffle
{
}
internal readonly struct DefaultShuffle4Slice3 : IShuffle4Slice3
internal readonly struct DefaultShuffle4Slice3([ConstantExpected] byte control) : IShuffle4Slice3
{
public DefaultShuffle4Slice3(byte control)
=> this.Control = control;
public byte Control { get; }
public byte Control { get; } = control;
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, this.Control);
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
#pragma warning disable CA1857 // A constant is expected for the parameter
=> HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref destination, this.Control);
#pragma warning restore CA1857 // A constant is expected for the parameter
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0);
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0);
for (nuint i = 0, j = 0; i < (uint)dest.Length; i += 3, j += 4)
for (nuint i = 0, j = 0; i < (uint)destination.Length; i += 3, j += 4)
{
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + j);
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + j);
@ -43,14 +43,14 @@ internal readonly struct DefaultShuffle4Slice3 : IShuffle4Slice3
internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3
{
[MethodImpl(InliningOptions.ShortMethod)]
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> dest)
=> HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, Shuffle.MMShuffle3210);
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination)
=> HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210);
[MethodImpl(InliningOptions.ShortMethod)]
public void RunFallbackShuffle(ReadOnlySpan<byte> source, Span<byte> dest)
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination)
{
ref uint sBase = ref Unsafe.As<byte, uint>(ref MemoryMarshal.GetReference(source));
ref Byte3 dBase = ref Unsafe.As<byte, Byte3>(ref MemoryMarshal.GetReference(dest));
ref Byte3 dBase = ref Unsafe.As<byte, Byte3>(ref MemoryMarshal.GetReference(destination));
nint n = (nint)(uint)source.Length / 4;
nint m = Numerics.Modulo4(n);

78
src/ImageSharp/Common/Helpers/SimdUtils.Convert.cs

@ -0,0 +1,78 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp;
internal static partial class SimdUtils
{
/// <summary>
/// Converts all input <see cref="byte"/>-s to <see cref="float"/>-s normalized into [0..1].
/// <paramref name="source"/> should be the of the same size as <paramref name="destination"/>,
/// but there are no restrictions on the span's length.
/// </summary>
/// <param name="source">The source span of bytes</param>
/// <param name="destination">The destination span of floats</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> destination)
{
DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!");
HwIntrinsics.ByteToNormalizedFloatReduce(ref source, ref destination);
if (source.Length > 0)
{
ConvertByteToNormalizedFloatRemainder(source, destination);
}
}
/// <summary>
/// Convert all <see cref="float"/> values normalized into [0..1] from 'source' into 'destination' buffer of <see cref="byte"/>.
/// The values are scaled up into [0-255] and rounded, overflows are clamped.
/// <paramref name="source"/> should be the of the same size as <paramref name="destination"/>,
/// but there are no restrictions on the span's length.
/// </summary>
/// <param name="source">The source span of floats</param>
/// <param name="destination">The destination span of bytes</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void NormalizedFloatToByteSaturate(ReadOnlySpan<float> source, Span<byte> destination)
{
DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!");
HwIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref destination);
if (source.Length > 0)
{
ConvertNormalizedFloatToByteRemainder(source, destination);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan<byte> source, Span<float> destination)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref float dBase = ref MemoryMarshal.GetReference(destination);
for (int i = 0; i < source.Length; i++)
{
Unsafe.Add(ref dBase, (uint)i) = Unsafe.Add(ref sBase, (uint)i) / 255f;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan<float> source, Span<byte> destination)
{
ref float sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(destination);
for (int i = 0; i < source.Length; i++)
{
Unsafe.Add(ref dBase, (uint)i) = ConvertToByte(Unsafe.Add(ref sBase, (uint)i));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255f) + 0.5f, 0, 255f);
}

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

@ -1,182 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable MemberHidesStaticFromOuterClass
namespace SixLabors.ImageSharp;
internal static partial class SimdUtils
{
/// <summary>
/// Implementation methods based on newer <see cref="Vector{T}"/> API-s (Vector.Widen, Vector.Narrow, Vector.ConvertTo*).
/// Only accelerated only on RyuJIT having dotnet/coreclr#10662 merged (.NET Core 2.1+ .NET 4.7.2+)
/// See:
/// https://github.com/dotnet/coreclr/pull/10662
/// API Proposal:
/// https://github.com/dotnet/corefx/issues/15957
/// </summary>
public static class ExtendedIntrinsics
{
public static bool IsAvailable { get; } = Vector.IsHardwareAccelerated;
/// <summary>
/// Widen and convert a vector of <see cref="short"/> values into 2 vectors of <see cref="float"/>-s.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ConvertToSingle(
Vector<short> source,
out Vector<float> dest1,
out Vector<float> dest2)
{
Vector.Widen(source, out Vector<int> i1, out Vector<int> i2);
dest1 = Vector.ConvertToSingle(i1);
dest2 = Vector.ConvertToSingle(i2);
}
/// <summary>
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan<byte> source,
ref Span<float> dest)
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
if (!IsAvailable)
{
return;
}
int remainder = Numerics.ModuloP2(source.Length, Vector<byte>.Count);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
{
ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]);
source = source[adjustedCount..];
dest = dest[adjustedCount..];
}
}
/// <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 (!IsAvailable)
{
return;
}
int remainder = Numerics.ModuloP2(source.Length, Vector<byte>.Count);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
{
NormalizedFloatToByteSaturate(source[..adjustedCount], dest[..adjustedCount]);
source = source[adjustedCount..];
dest = dest[adjustedCount..];
}
}
/// <summary>
/// Implementation <see cref="SimdUtils.ByteToNormalizedFloat"/>, which is faster on new RyuJIT runtime.
/// </summary>
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
{
VerifySpanInput(source, dest, Vector<byte>.Count);
nuint n = dest.VectorCount<byte>();
ref Vector<byte> sourceBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference(source));
ref Vector<float> destBase = ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(dest));
for (nuint i = 0; i < n; i++)
{
Vector<byte> b = Unsafe.Add(ref sourceBase, i);
Vector.Widen(b, out Vector<ushort> s0, out Vector<ushort> s1);
Vector.Widen(s0, out Vector<uint> w0, out Vector<uint> w1);
Vector.Widen(s1, out Vector<uint> w2, out Vector<uint> w3);
Vector<float> f0 = ConvertToSingle(w0);
Vector<float> f1 = ConvertToSingle(w1);
Vector<float> f2 = ConvertToSingle(w2);
Vector<float> f3 = ConvertToSingle(w3);
ref Vector<float> d = ref Unsafe.Add(ref destBase, i * 4);
d = f0;
Unsafe.Add(ref d, 1) = f1;
Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 3) = f3;
}
}
/// <summary>
/// Implementation of <see cref="SimdUtils.NormalizedFloatToByteSaturate"/>, which is faster on new .NET runtime.
/// </summary>
internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> dest)
{
VerifySpanInput(source, dest, Vector<byte>.Count);
nuint n = dest.VectorCount<byte>();
ref Vector<float> sourceBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(source));
ref Vector<byte> destBase = ref Unsafe.As<byte, Vector<byte>>(ref MemoryMarshal.GetReference(dest));
for (nuint i = 0; i < n; i++)
{
ref Vector<float> s = ref Unsafe.Add(ref sourceBase, i * 4);
Vector<float> f0 = s;
Vector<float> f1 = Unsafe.Add(ref s, 1);
Vector<float> f2 = Unsafe.Add(ref s, 2);
Vector<float> f3 = Unsafe.Add(ref s, 3);
Vector<uint> w0 = ConvertToUInt32(f0);
Vector<uint> w1 = ConvertToUInt32(f1);
Vector<uint> w2 = ConvertToUInt32(f2);
Vector<uint> w3 = ConvertToUInt32(f3);
var u0 = Vector.Narrow(w0, w1);
var u1 = Vector.Narrow(w2, w3);
Unsafe.Add(ref destBase, i) = Vector.Narrow(u0, u1);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<uint> ConvertToUInt32(Vector<float> vf)
{
var maxBytes = new Vector<float>(255f);
vf *= maxBytes;
vf += new Vector<float>(0.5f);
vf = Vector.Min(Vector.Max(vf, Vector<float>.Zero), maxBytes);
var vi = Vector.ConvertToInt32(vf);
return Vector.AsVectorUInt32(vi);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector<float> ConvertToSingle(Vector<uint> u)
{
var vi = Vector.AsVectorInt32(u);
var v = Vector.ConvertToSingle(vi);
v *= new Vector<float>(1f / 255f);
return v;
}
}
}

144
src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs

@ -1,144 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// ReSharper disable MemberHidesStaticFromOuterClass
namespace SixLabors.ImageSharp;
internal static partial class SimdUtils
{
/// <summary>
/// Fallback implementation based on <see cref="Vector4"/> (128bit).
/// For <see cref="Vector4"/>, efficient software fallback implementations are present,
/// and we hope that even mono's JIT is able to emit SIMD instructions for that type :P
/// </summary>
public static class FallbackIntrinsics128
{
/// <summary>
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal static void ByteToNormalizedFloatReduce(
ref ReadOnlySpan<byte> source,
ref Span<float> dest)
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
int remainder = Numerics.Modulo4(source.Length);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
{
ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]);
source = source[adjustedCount..];
dest = dest[adjustedCount..];
}
}
/// <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!");
int remainder = Numerics.Modulo4(source.Length);
int adjustedCount = source.Length - remainder;
if (adjustedCount > 0)
{
NormalizedFloatToByteSaturate(
source[..adjustedCount],
dest[..adjustedCount]);
source = source[adjustedCount..];
dest = dest[adjustedCount..];
}
}
/// <summary>
/// Implementation of <see cref="SimdUtils.ByteToNormalizedFloat"/> using <see cref="Vector4"/>.
/// </summary>
[MethodImpl(InliningOptions.ColdPath)]
internal static void ByteToNormalizedFloat(ReadOnlySpan<byte> source, Span<float> dest)
{
VerifySpanInput(source, dest, 4);
uint count = (uint)dest.Length / 4;
if (count == 0)
{
return;
}
ref ByteVector4 sBase = ref Unsafe.As<byte, ByteVector4>(ref MemoryMarshal.GetReference(source));
ref Vector4 dBase = ref Unsafe.As<float, Vector4>(ref MemoryMarshal.GetReference(dest));
const float scale = 1f / 255f;
Vector4 d = default;
for (nuint i = 0; i < count; i++)
{
ref ByteVector4 s = ref Unsafe.Add(ref sBase, i);
d.X = s.X;
d.Y = s.Y;
d.Z = s.Z;
d.W = s.W;
d *= scale;
Unsafe.Add(ref dBase, i) = d;
}
}
/// <summary>
/// Implementation of <see cref="SimdUtils.NormalizedFloatToByteSaturate"/> using <see cref="Vector4"/>.
/// </summary>
[MethodImpl(InliningOptions.ColdPath)]
internal static void NormalizedFloatToByteSaturate(
ReadOnlySpan<float> source,
Span<byte> dest)
{
VerifySpanInput(source, dest, 4);
uint count = (uint)source.Length / 4;
if (count == 0)
{
return;
}
ref Vector4 sBase = ref Unsafe.As<float, Vector4>(ref MemoryMarshal.GetReference(source));
ref ByteVector4 dBase = ref Unsafe.As<byte, ByteVector4>(ref MemoryMarshal.GetReference(dest));
var half = new Vector4(0.5f);
var maxBytes = new Vector4(255f);
for (nuint i = 0; i < count; i++)
{
Vector4 s = Unsafe.Add(ref sBase, i);
s *= maxBytes;
s += half;
s = Numerics.Clamp(s, Vector4.Zero, maxBytes);
ref ByteVector4 d = ref Unsafe.Add(ref dBase, i);
d.X = (byte)s.X;
d.Y = (byte)s.Y;
d.Z = (byte)s.Z;
d.W = (byte)s.W;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct ByteVector4
{
public byte X;
public byte Y;
public byte Z;
public byte W;
}
}
}

801
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

File diff suppressed because it is too large

125
src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs

@ -12,140 +12,140 @@ internal static partial class SimdUtils
{
/// <summary>
/// Shuffle single-precision (32-bit) floating-point elements in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// using the control and store the results in <paramref name="destination"/>.
/// </summary>
/// <param name="source">The source span of floats.</param>
/// <param name="dest">The destination span of floats.</param>
/// <param name="destination">The destination span of floats.</param>
/// <param name="control">The byte control.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4(
ReadOnlySpan<float> source,
Span<float> dest,
Span<float> destination,
[ConstantExpected] byte control)
{
VerifyShuffle4SpanInput(source, dest);
VerifyShuffle4SpanInput(source, destination);
HwIntrinsics.Shuffle4Reduce(ref source, ref dest, control);
HwIntrinsics.Shuffle4Reduce(ref source, ref destination, control);
// Deal with the remainder:
if (source.Length > 0)
{
Shuffle4Remainder(source, dest, control);
Shuffle4Remainder(source, destination, control);
}
}
/// <summary>
/// Shuffle 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// using the control and store the results in <paramref name="destination"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="destination">The destination span of bytes.</param>
/// <param name="shuffle">The type of shuffle to perform.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4<TShuffle>(
ReadOnlySpan<byte> source,
Span<byte> dest,
Span<byte> destination,
TShuffle shuffle)
where TShuffle : struct, IShuffle4
{
VerifyShuffle4SpanInput(source, dest);
VerifyShuffle4SpanInput(source, destination);
shuffle.ShuffleReduce(ref source, ref dest);
shuffle.ShuffleReduce(ref source, ref destination);
// Deal with the remainder:
if (source.Length > 0)
{
shuffle.RunFallbackShuffle(source, dest);
shuffle.Shuffle(source, destination);
}
}
/// <summary>
/// Shuffle 8-bit integer triplets within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// using the control and store the results in <paramref name="destination"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="destination">The destination span of bytes.</param>
/// <param name="shuffle">The type of shuffle to perform.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle3<TShuffle>(
ReadOnlySpan<byte> source,
Span<byte> dest,
Span<byte> destination,
TShuffle shuffle)
where TShuffle : struct, IShuffle3
{
// Source length should be smaller than dest length, and divisible by 3.
VerifyShuffle3SpanInput(source, dest);
// Source length should be smaller than destination length, and divisible by 3.
VerifyShuffle3SpanInput(source, destination);
shuffle.ShuffleReduce(ref source, ref dest);
shuffle.ShuffleReduce(ref source, ref destination);
// Deal with the remainder:
if (source.Length > 0)
{
shuffle.RunFallbackShuffle(source, dest);
shuffle.Shuffle(source, destination);
}
}
/// <summary>
/// Pads then shuffles 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// using the control and store the results in <paramref name="destination"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="destination">The destination span of bytes.</param>
/// <param name="shuffle">The type of shuffle to perform.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Pad3Shuffle4<TShuffle>(
ReadOnlySpan<byte> source,
Span<byte> dest,
Span<byte> destination,
TShuffle shuffle)
where TShuffle : struct, IPad3Shuffle4
{
VerifyPad3Shuffle4SpanInput(source, dest);
VerifyPad3Shuffle4SpanInput(source, destination);
shuffle.ShuffleReduce(ref source, ref dest);
shuffle.ShuffleReduce(ref source, ref destination);
// Deal with the remainder:
if (source.Length > 0)
{
shuffle.RunFallbackShuffle(source, dest);
shuffle.Shuffle(source, destination);
}
}
/// <summary>
/// Shuffles then slices 8-bit integers within 128-bit lanes in <paramref name="source"/>
/// using the control and store the results in <paramref name="dest"/>.
/// using the control and store the results in <paramref name="destination"/>.
/// </summary>
/// <typeparam name="TShuffle">The type of shuffle struct.</typeparam>
/// <param name="source">The source span of bytes.</param>
/// <param name="dest">The destination span of bytes.</param>
/// <param name="destination">The destination span of bytes.</param>
/// <param name="shuffle">The type of shuffle to perform.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Shuffle4Slice3<TShuffle>(
ReadOnlySpan<byte> source,
Span<byte> dest,
Span<byte> destination,
TShuffle shuffle)
where TShuffle : struct, IShuffle4Slice3
{
VerifyShuffle4Slice3SpanInput(source, dest);
VerifyShuffle4Slice3SpanInput(source, destination);
shuffle.ShuffleReduce(ref source, ref dest);
shuffle.ShuffleReduce(ref source, ref destination);
// Deal with the remainder:
if (source.Length > 0)
{
shuffle.RunFallbackShuffle(source, dest);
shuffle.Shuffle(source, destination);
}
}
private static void Shuffle4Remainder(
ReadOnlySpan<float> source,
Span<float> dest,
Span<float> destination,
byte control)
{
ref float sBase = ref MemoryMarshal.GetReference(source);
ref float dBase = ref MemoryMarshal.GetReference(dest);
ref float dBase = ref MemoryMarshal.GetReference(destination);
Shuffle.InverseMMShuffle(control, out uint p3, out uint p2, out uint p1, out uint p0);
for (nuint i = 0; i < (uint)source.Length; i += 4)
@ -158,69 +158,69 @@ internal static partial class SimdUtils
}
[Conditional("DEBUG")]
internal static void VerifyShuffle4SpanInput<T>(ReadOnlySpan<T> source, Span<T> dest)
internal static void VerifyShuffle4SpanInput<T>(ReadOnlySpan<T> source, Span<T> destination)
where T : struct
{
DebugGuard.IsTrue(
source.Length == dest.Length,
source.Length == destination.Length,
nameof(source),
"Input spans must be of same length!");
DebugGuard.IsTrue(
source.Length % 4 == 0,
nameof(source),
"Input spans must be divisable by 4!");
"Input spans must be divisible by 4!");
}
[Conditional("DEBUG")]
private static void VerifyShuffle3SpanInput<T>(ReadOnlySpan<T> source, Span<T> dest)
private static void VerifyShuffle3SpanInput<T>(ReadOnlySpan<T> source, Span<T> destination)
where T : struct
{
DebugGuard.IsTrue(
source.Length <= dest.Length,
source.Length <= destination.Length,
nameof(source),
"Source should fit into dest!");
"Source should fit into destination!");
DebugGuard.IsTrue(
source.Length % 3 == 0,
nameof(source),
"Input spans must be divisable by 3!");
"Input spans must be divisible by 3!");
}
[Conditional("DEBUG")]
private static void VerifyPad3Shuffle4SpanInput(ReadOnlySpan<byte> source, Span<byte> dest)
private static void VerifyPad3Shuffle4SpanInput(ReadOnlySpan<byte> source, Span<byte> destination)
{
DebugGuard.IsTrue(
source.Length % 3 == 0,
nameof(source),
"Input span must be divisable by 3!");
"Input span must be divisible by 3!");
DebugGuard.IsTrue(
dest.Length % 4 == 0,
nameof(dest),
"Output span must be divisable by 4!");
destination.Length % 4 == 0,
nameof(destination),
"Output span must be divisible by 4!");
DebugGuard.IsTrue(
source.Length == dest.Length * 3 / 4,
source.Length == destination.Length * 3 / 4,
nameof(source),
"Input span must be 3/4 the length of the output span!");
}
[Conditional("DEBUG")]
private static void VerifyShuffle4Slice3SpanInput(ReadOnlySpan<byte> source, Span<byte> dest)
private static void VerifyShuffle4Slice3SpanInput(ReadOnlySpan<byte> source, Span<byte> destination)
{
DebugGuard.IsTrue(
source.Length % 4 == 0,
nameof(source),
"Input span must be divisable by 4!");
"Input span must be divisible by 4!");
DebugGuard.IsTrue(
dest.Length % 3 == 0,
nameof(dest),
"Output span must be divisable by 3!");
destination.Length % 3 == 0,
nameof(destination),
"Output span must be divisible by 3!");
DebugGuard.IsTrue(
dest.Length >= source.Length * 3 / 4,
destination.Length >= source.Length * 3 / 4,
nameof(source),
"Output span must be at least 3/4 the length of the input span!");
}
@ -509,6 +509,27 @@ internal static partial class SimdUtils
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void MMShuffleSpan(ref Span<int> span, byte control)
{
InverseMMShuffle(
control,
out uint p3,
out uint p2,
out uint p1,
out uint p0);
ref int spanBase = ref MemoryMarshal.GetReference(span);
for (nuint i = 0; i < (uint)span.Length; i += 4)
{
Unsafe.Add(ref spanBase, i + 0) = (int)(p0 + i);
Unsafe.Add(ref spanBase, i + 1) = (int)(p1 + i);
Unsafe.Add(ref spanBase, i + 2) = (int)(p2 + i);
Unsafe.Add(ref spanBase, i + 3) = (int)(p3 + i);
}
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void InverseMMShuffle(
byte control,

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

@ -22,13 +22,6 @@ internal static partial class SimdUtils
public static bool HasVector8 { get; } =
Vector.IsHardwareAccelerated && Vector<float>.Count == 8 && Vector<int>.Count == 8;
/// <summary>
/// Gets a value indicating whether <see cref="Vector{T}"/> code is being JIT-ed to SSE instructions
/// where float and integer registers are of size 128 byte.
/// </summary>
public static bool HasVector4 { get; } =
Vector.IsHardwareAccelerated && Vector<float>.Count == 4;
/// <summary>
/// Transform all scalars in 'v' in a way that converting them to <see cref="int"/> would have rounding semantics.
/// </summary>
@ -69,111 +62,8 @@ internal static partial class SimdUtils
}
}
/// <summary>
/// Converts all input <see cref="byte"/>-s to <see cref="float"/>-s normalized into [0..1].
/// <paramref name="source"/> should be the of the same size as <paramref name="dest"/>,
/// but there are no restrictions on the span's length.
/// </summary>
/// <param name="source">The source span of bytes</param>
/// <param name="dest">The destination span of floats</param>
[MethodImpl(InliningOptions.ShortMethod)]
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!");
HwIntrinsics.ByteToNormalizedFloatReduce(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)
{
ConvertByteToNormalizedFloatRemainder(source, dest);
}
}
/// <summary>
/// Convert all <see cref="float"/> values normalized into [0..1] from 'source' into 'dest' buffer of <see cref="byte"/>.
/// The values are scaled up into [0-255] and rounded, overflows are clamped.
/// <paramref name="source"/> should be the of the same size as <paramref name="dest"/>,
/// but there are no restrictions on the span's length.
/// </summary>
/// <param name="source">The source span of floats</param>
/// <param name="dest">The destination span of bytes</param>
[MethodImpl(InliningOptions.ShortMethod)]
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!");
HwIntrinsics.NormalizedFloatToByteSaturateReduce(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)
{
ConvertNormalizedFloatToByteRemainder(source, dest);
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan<byte> source, Span<float> dest)
{
ref byte sBase = ref MemoryMarshal.GetReference(source);
ref float dBase = ref MemoryMarshal.GetReference(dest);
// There are at most 3 elements at this point, having a for loop is overkill.
// Let's minimize the no. of instructions!
switch (source.Length)
{
case 3:
Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2) / 255f;
goto case 2;
case 2:
Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1) / 255f;
goto case 1;
case 1:
dBase = sBase / 255f;
break;
}
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan<float> source, Span<byte> dest)
{
ref float sBase = ref MemoryMarshal.GetReference(source);
ref byte dBase = ref MemoryMarshal.GetReference(dest);
switch (source.Length)
{
case 3:
Unsafe.Add(ref dBase, 2) = ConvertToByte(Unsafe.Add(ref sBase, 2));
goto case 2;
case 2:
Unsafe.Add(ref dBase, 1) = ConvertToByte(Unsafe.Add(ref sBase, 1));
goto case 1;
case 1:
dBase = ConvertToByte(sBase);
break;
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255F) + 0.5F, 0, 255F);
[Conditional("DEBUG")]
private static void VerifyHasVector8(string operation)
{
if (!HasVector8)
{
throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!");
}
}
[Conditional("DEBUG")]
private static void VerifySpanInput(ReadOnlySpan<byte> source, Span<float> dest, int shouldBeDivisibleBy)
private static void DebugVerifySpanInput(ReadOnlySpan<byte> source, Span<float> dest, int shouldBeDivisibleBy)
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
DebugGuard.IsTrue(
@ -183,11 +73,11 @@ internal static partial class SimdUtils
}
[Conditional("DEBUG")]
private static void VerifySpanInput(ReadOnlySpan<float> source, Span<byte> dest, int shouldBeDivisibleBy)
private static void DebugVerifySpanInput(ReadOnlySpan<float> source, Span<byte> destination, int shouldBeDivisibleBy)
{
DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!");
DebugGuard.IsTrue(source.Length == destination.Length, nameof(source), "Input spans must be of same length!");
DebugGuard.IsTrue(
Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0,
Numerics.ModuloP2(destination.Length, shouldBeDivisibleBy) == 0,
nameof(source),
$"length should be divisible by {shouldBeDivisibleBy}!");
}

250
src/ImageSharp/Common/Helpers/Vector128Utilities.cs

@ -0,0 +1,250 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Common.Helpers;
/// <summary>
/// Defines utility methods for <see cref="Vector128{T}"/> that have either:
/// <list type="number">
/// <item>Not yet been normalized in the runtime.</item>
/// <item>Produce codegen that is poorly optimized by the runtime.</item>
/// </list>
/// Should only be used if the intrinsics are available.
/// </summary>
internal static class Vector128Utilities
{
/// <summary>
/// Gets a value indicating whether shuffle operations are supported.
/// </summary>
public static bool SupportsShuffleFloat
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Sse.IsSupported;
}
/// <summary>
/// Gets a value indicating whether shuffle operations are supported.
/// </summary>
public static bool SupportsShuffleByte
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Ssse3.IsSupported || AdvSimd.Arm64.IsSupported;
}
/// <summary>
/// Gets a value indicating whether right align operations are supported.
/// </summary>
public static bool SupportsRightAlign
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Ssse3.IsSupported || AdvSimd.IsSupported;
}
/// <summary>
/// Gets a value indicating whether right or left byte shift operations are supported.
/// </summary>
public static bool SupportsShiftByte
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Sse2.IsSupported || AdvSimd.IsSupported;
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using the control.
/// </summary>
/// <param name="vector">The input vector from which values are selected.</param>
/// <param name="control">The shuffle control byte.</param>
/// <returns>The <see cref="Vector128{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<float> Shuffle(Vector128<float> vector, [ConstantExpected] byte control)
{
if (Sse.IsSupported)
{
return Sse.Shuffle(vector, vector, control);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.
/// </summary>
/// <param name="vector">
/// The input vector from which values are selected.</param>
/// <param name="indices">
/// The per-element indices used to select a value from <paramref name="vector" />.
/// </param>
/// <returns>
/// A new vector containing the values from <paramref name="vector" /> selected by the given <paramref name="indices" />.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Shuffle(Vector128<byte> vector, Vector128<byte> indices)
{
if (Ssse3.IsSupported)
{
return Ssse3.Shuffle(vector, indices);
}
if (AdvSimd.Arm64.IsSupported)
{
return AdvSimd.Arm64.VectorTableLookup(vector, indices);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Shifts a 128-bit value right by a specified number of bytes while shifting in zeros.
/// </summary>
/// <param name="value">The value to shift.</param>
/// <param name="numBytes">The number of bytes to shift by.</param>
/// <returns>The <see cref="Vector128{Byte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> ShiftRightBytesInVector(Vector128<byte> value, [ConstantExpected(Max = (byte)15)] byte numBytes)
{
if (Sse2.IsSupported)
{
return Sse2.ShiftRightLogical128BitLane(value, numBytes);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ExtractVector128(value, Vector128<byte>.Zero, numBytes);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Shifts a 128-bit value left by a specified number of bytes while shifting in zeros.
/// </summary>
/// <param name="value">The value to shift.</param>
/// <param name="numBytes">The number of bytes to shift by.</param>
/// <returns>The <see cref="Vector128{Byte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> ShiftLeftBytesInVector(Vector128<byte> value, [ConstantExpected(Max = (byte)15)] byte numBytes)
{
if (Sse2.IsSupported)
{
return Sse2.ShiftLeftLogical128BitLane(value, numBytes);
}
if (AdvSimd.IsSupported)
{
#pragma warning disable CA1857 // A constant is expected for the parameter
return AdvSimd.ExtractVector128(Vector128<byte>.Zero, value, (byte)(Vector128<byte>.Count - numBytes));
#pragma warning restore CA1857 // A constant is expected for the parameter
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Right aligns elements of two source 128-bit values depending on bits in a mask.
/// </summary>
/// <param name="left">The left hand source vector.</param>
/// <param name="right">The right hand source vector.</param>
/// <param name="mask">An 8-bit mask used for the operation.</param>
/// <returns>The <see cref="Vector128{Byte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> AlignRight(Vector128<byte> left, Vector128<byte> right, [ConstantExpected(Max = (byte)15)] byte mask)
{
if (Ssse3.IsSupported)
{
return Ssse3.AlignRight(left, right, mask);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ExtractVector128(right, left, mask);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Performs a conversion from a 128-bit vector of 4 single-precision floating-point values to a 128-bit vector of 4 signed 32-bit integer values.
/// Rounding is equivalent to <see cref="MidpointRounding.ToEven"/>.
/// </summary>
/// <param name="vector">The value to convert.</param>
/// <returns>The <see cref="Vector128{Int32}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<int> ConvertToInt32RoundToEven(Vector128<float> vector)
{
if (Sse2.IsSupported)
{
return Sse2.ConvertToVector128Int32(vector);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ConvertToInt32RoundToEven(vector);
}
Vector128<float> sign = vector & Vector128.Create(-0.0f);
Vector128<float> val_2p23_f32 = sign | Vector128.Create(8388608.0f);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return Vector128.ConvertToInt32(val_2p23_f32 | sign);
}
/// <summary>
/// Packs signed 16-bit integers to unsigned 8-bit integers and saturates.
/// </summary>
/// <param name="left">The left hand source vector.</param>
/// <param name="right">The right hand source vector.</param>
/// <returns>The <see cref="Vector128{Int16}"/>.</returns>
public static Vector128<byte> PackUnsignedSaturate(Vector128<short> left, Vector128<short> right)
{
if (Sse2.IsSupported)
{
return Sse2.PackUnsignedSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ExtractNarrowingSaturateUnsignedUpper(AdvSimd.ExtractNarrowingSaturateUnsignedLower(left), right);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Packs signed 32-bit integers to signed 16-bit integers and saturates.
/// </summary>
/// <param name="left">The left hand source vector.</param>
/// <param name="right">The right hand source vector.</param>
/// <returns>The <see cref="Vector128{Int16}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> PackSignedSaturate(Vector128<int> left, Vector128<int> right)
{
if (Sse2.IsSupported)
{
return Sse2.PackSignedSaturate(left, right);
}
if (AdvSimd.IsSupported)
{
return AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(left), right);
}
ThrowUnreachableException();
return default;
}
[DoesNotReturn]
private static void ThrowUnreachableException() => throw new UnreachableException();
}

115
src/ImageSharp/Common/Helpers/Vector256Utilities.cs

@ -0,0 +1,115 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Common.Helpers;
/// <summary>
/// Defines utility methods for <see cref="Vector256{T}"/> that have either:
/// <list type="number">
/// <item>Not yet been normalized in the runtime.</item>
/// <item>Produce codegen that is poorly optimized by the runtime.</item>
/// </list>
/// Should only be used if the intrinsics are available.
/// </summary>
internal static class Vector256Utilities
{
/// <summary>
/// Gets a value indicating whether shuffle byte operations are supported.
/// </summary>
public static bool SupportsShuffleFloat
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Avx.IsSupported || Sse.IsSupported;
}
/// <summary>
/// Gets a value indicating whether shuffle byte operations are supported.
/// </summary>
public static bool SupportsShuffleByte
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Avx2.IsSupported;
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.
/// </summary>
/// <param name="vector">The input vector from which values are selected.</param>
/// <param name="control">The shuffle control byte.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Shuffle(Vector256<float> vector, [ConstantExpected] byte control)
{
if (Avx.IsSupported)
{
return Avx.Shuffle(vector, vector, control);
}
if (Sse.IsSupported)
{
Vector128<float> lower = vector.GetLower();
Vector128<float> upper = vector.GetUpper();
return Vector256.Create(Sse.Shuffle(lower, lower, control), Sse.Shuffle(upper, upper, control));
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.</summary>
/// <param name="vector">
/// The input vector from which values are selected.</param>
/// <param name="indices">
/// The per-element indices used to select a value from <paramref name="vector" />.
/// </param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Shuffle(Vector256<byte> vector, Vector256<byte> indices)
{
if (Avx2.IsSupported)
{
return Avx2.Shuffle(vector, indices);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Performs a conversion from a 256-bit vector of 8 single-precision floating-point values to a 256-bit vector of 8 signed 32-bit integer values.
/// Rounding is equivalent to <see cref="MidpointRounding.ToEven"/>.
/// </summary>
/// <param name="vector">The value to convert.</param>
/// <returns>The <see cref="Vector256{Int32}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<int> ConvertToInt32RoundToEven(Vector256<float> vector)
{
if (Avx.IsSupported)
{
return Avx.ConvertToVector256Int32(vector);
}
if (Sse2.IsSupported)
{
Vector128<int> lower = Sse2.ConvertToVector128Int32(vector.GetLower());
Vector128<int> upper = Sse2.ConvertToVector128Int32(vector.GetUpper());
return Vector256.Create(lower, upper);
}
Vector256<float> sign = vector & Vector256.Create(-0.0f);
Vector256<float> val_2p23_f32 = sign | Vector256.Create(8388608.0f);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return Vector256.ConvertToInt32(val_2p23_f32 | sign);
}
[DoesNotReturn]
private static void ThrowUnreachableException() => throw new UnreachableException();
}

115
src/ImageSharp/Common/Helpers/Vector512Utilities.cs

@ -0,0 +1,115 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Common.Helpers;
/// <summary>
/// Defines utility methods for <see cref="Vector512{T}"/> that have either:
/// <list type="number">
/// <item>Not yet been normalized in the runtime.</item>
/// <item>Produce codegen that is poorly optimized by the runtime.</item>
/// </list>
/// Should only be used if the intrinsics are available.
/// </summary>
internal static class Vector512Utilities
{
/// <summary>
/// Gets a value indicating whether shuffle float operations are supported.
/// </summary>
public static bool SupportsShuffleFloat
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Avx512F.IsSupported || Avx.IsSupported;
}
/// <summary>
/// Gets a value indicating whether shuffle byte operations are supported.
/// </summary>
public static bool SupportsShuffleByte
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Avx512BW.IsSupported;
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using the control.
/// </summary>
/// <param name="vector">The input vector from which values are selected.</param>
/// <param name="control">The shuffle control byte.</param>
/// <returns>The <see cref="Vector512{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<float> Shuffle(Vector512<float> vector, [ConstantExpected] byte control)
{
if (Avx512F.IsSupported)
{
return Avx512F.Shuffle(vector, vector, control);
}
if (Avx.IsSupported)
{
Vector256<float> lower = vector.GetLower();
Vector256<float> upper = vector.GetUpper();
return Vector512.Create(Avx.Shuffle(lower, lower, control), Avx.Shuffle(upper, upper, control));
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Creates a new vector by selecting values from an input vector using a set of indices.
/// </summary>
/// <param name="vector">The input vector from which values are selected.</param>
/// <param name="indices">
/// The per-element indices used to select a value from <paramref name="vector" />.
/// </param>
/// <returns>The <see cref="Vector512{Byte}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Shuffle(Vector512<byte> vector, Vector512<byte> indices)
{
if (Avx512BW.IsSupported)
{
return Avx512BW.Shuffle(vector, indices);
}
ThrowUnreachableException();
return default;
}
/// <summary>
/// Performs a conversion from a 512-bit vector of 16 single-precision floating-point values to a 512-bit vector of 16 signed 32-bit integer values.
/// Rounding is equivalent to <see cref="MidpointRounding.ToEven"/>.
/// </summary>
/// <param name="vector">The value to convert.</param>
/// <returns>The <see cref="Vector128{Int32}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<int> ConvertToInt32RoundToEven(Vector512<float> vector)
{
if (Avx512F.IsSupported)
{
return Avx512F.ConvertToVector512Int32(vector);
}
if (Avx.IsSupported)
{
Vector256<int> lower = Avx.ConvertToVector256Int32(vector.GetLower());
Vector256<int> upper = Avx.ConvertToVector256Int32(vector.GetUpper());
return Vector512.Create(lower, upper);
}
Vector512<float> sign = vector & Vector512.Create(-0.0f);
Vector512<float> val_2p23_f32 = sign | Vector512.Create(8388608.0f);
val_2p23_f32 = (vector + val_2p23_f32) - val_2p23_f32;
return Vector512.ConvertToInt32(val_2p23_f32 | sign);
}
[DoesNotReturn]
private static void ThrowUnreachableException() => throw new UnreachableException();
}

4
src/ImageSharp/Formats/AnimationUtilities.cs

@ -50,7 +50,7 @@ internal static class AnimationUtilities
Span<Rgba32> next = buffers.GetSpan().Slice(currentFrame.Width * 2, currentFrame.Width);
Span<Rgba32> result = buffers.GetSpan()[(currentFrame.Width * 3)..];
Rgba32 bg = replacement;
Rgba32 bg = replacement.ToPixel<Rgba32>();
int top = int.MinValue;
int bottom = int.MaxValue;
@ -232,7 +232,7 @@ internal static class AnimationUtilities
ref Rgba32 r = ref Unsafe.Add(ref MemoryMarshal.GetReference(result), x);
bool peq = c.Rgba == (previousFrame != null ? p.Rgba : bg.Rgba);
Rgba32 val = (blend & peq) ? replacement : c;
Rgba32 val = (blend & peq) ? bg : c;
peq &= nextFrame == null || (n.Rgba >> 24 >= c.Rgba >> 24);
r = val;

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

@ -294,72 +294,60 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
private void ReadRle<TPixel>(BufferedReadStream stream, BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
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))
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;
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> bufferSpan = buffer.Memory.Span;
if (compression is BmpCompression.RLE8)
{
Span<bool> rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> bufferSpan = buffer.Memory.Span;
if (compression is BmpCompression.RLE8)
{
this.UncompressRle8(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
else
{
this.UncompressRle4(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
this.UncompressRle8(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
else
{
this.UncompressRle4(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
int rowStartIdx = y * width;
Span<byte> bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
int rowStartIdx = y * width;
Span<byte> bufferRow = bufferSpan.Slice(rowStartIdx, width);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
for (int x = 0; x < width; x++)
{
// Slow path with undefined pixels.
for (int x = 0; x < width; x++)
byte colorIdx = bufferRow[x];
if (undefinedPixelsSpan[rowStartIdx + x])
{
byte colorIdx = bufferRow[x];
if (undefinedPixelsSpan[rowStartIdx + x])
{
switch (this.rleSkippedPixelHandling)
{
case RleSkippedPixelHandling.FirstColorOfPalette:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
break;
case RleSkippedPixelHandling.Transparent:
color.FromScaledVector4(Vector4.Zero);
break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default:
color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break;
}
}
else
pixelRow[x] = this.rleSkippedPixelHandling switch
{
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
}
RleSkippedPixelHandling.FirstColorOfPalette => TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4])),
RleSkippedPixelHandling.Transparent => TPixel.FromScaledVector4(Vector4.Zero),
pixelRow[x] = color;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
_ => TPixel.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)),
};
}
}
else
{
// Fast path without any undefined pixels.
for (int x = 0; x < width; x++)
else
{
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[bufferRow[x] * 4]));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
}
}
}
else
{
// Fast path without any undefined pixels.
for (int x = 0; x < width; x++)
{
pixelRow[x] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[bufferRow[x] * 4]));
}
}
}
}
@ -375,66 +363,54 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
private void ReadRle24<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * 3, 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();
using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * 3, 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(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
for (int y = 0; y < height; y++)
this.UncompressRle24(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
{
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
if (rowHasUndefinedPixels)
// Slow path with undefined pixels.
int yMulWidth = y * width;
int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
{
// Slow path with undefined pixels.
int yMulWidth = y * width;
int rowStartIdx = yMulWidth * 3;
for (int x = 0; x < width; x++)
int idx = rowStartIdx + (x * 3);
if (undefinedPixelsSpan[yMulWidth + x])
{
int idx = rowStartIdx + (x * 3);
if (undefinedPixelsSpan[yMulWidth + x])
{
switch (this.rleSkippedPixelHandling)
{
case RleSkippedPixelHandling.FirstColorOfPalette:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break;
case RleSkippedPixelHandling.Transparent:
color.FromScaledVector4(Vector4.Zero);
break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default:
color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break;
}
}
else
pixelRow[x] = this.rleSkippedPixelHandling switch
{
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
}
RleSkippedPixelHandling.FirstColorOfPalette => TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx])),
RleSkippedPixelHandling.Transparent => TPixel.FromScaledVector4(Vector4.Zero),
pixelRow[x] = color;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
_ => TPixel.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)),
};
}
}
else
{
// Fast path without any undefined pixels.
int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
else
{
int idx = rowStartIdx + (x * 3);
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
}
}
}
else
{
// Fast path without any undefined pixels.
int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
int idx = rowStartIdx + (x * 3);
pixelRow[x] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
}
}
}
}
@ -492,7 +468,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
int max = cmd[1];
int bytesToRead = (int)(((uint)max + 1) / 2);
Span<byte> run = bytesToRead <= 128 ? scratchBuffer.Slice(0, bytesToRead) : new byte[bytesToRead];
Span<byte> run = bytesToRead <= 128 ? scratchBuffer[..bytesToRead] : new byte[bytesToRead];
stream.Read(run);
@ -598,7 +574,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Take this number of bytes from the stream as uncompressed data.
int length = cmd[1];
Span<byte> run = length <= 128 ? scratchBuffer.Slice(0, length) : new byte[length];
Span<byte> run = length <= 128 ? scratchBuffer[..length] : new byte[length];
stream.Read(run);
@ -680,7 +656,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
int length = cmd[1];
int length3 = length * 3;
Span<byte> run = length3 <= 128 ? scratchBuffer.Slice(0, length3) : new byte[length3];
Span<byte> run = length3 <= 128 ? scratchBuffer[..length3] : new byte[length3];
stream.Read(run);
@ -835,7 +811,6 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
using IMemoryOwner<byte> row = this.memoryAllocator.Allocate<byte>(arrayWidth + padding, AllocationOptions.Clean);
TPixel color = default;
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
@ -856,8 +831,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
pixelRow[newX] = color;
pixelRow[newX] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
}
offset++;
@ -882,8 +856,6 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
int padding = CalculatePadding(width, 2);
int stride = (width * 2) + padding;
TPixel color = default;
int rightShiftRedMask = CalculateRightShift((uint)redMask);
int rightShiftGreenMask = CalculateRightShift((uint)greenMask);
int rightShiftBlueMask = CalculateRightShift((uint)blueMask);
@ -917,8 +889,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask);
Rgb24 rgb = new((byte)r, (byte)g, (byte)b);
color.FromRgb24(rgb);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromRgb24(rgb);
offset += 2;
}
}
@ -1107,8 +1078,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
pixelSpan[x] = TPixel.FromBgra32(bgra);
}
}
}
@ -1129,7 +1099,6 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
private void ReadRgb32BitFields<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
int padding = CalculatePadding(width, 4);
int stride = (width * 4) + padding;
@ -1179,18 +1148,17 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromScaledVector4(vector4);
pixelRow[x] = TPixel.FromScaledVector4(vector4);
}
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : byte.MaxValue;
pixelRow[x] = TPixel.FromRgba32(new(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
}
}
@ -1468,7 +1436,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry;
}
palette = Array.Empty<byte>();
palette = [];
if (colorMapSizeBytes > 0)
{

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

@ -185,7 +185,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
{
uint frameCount = 0;
ImageFrameMetadata? previousFrame = null;
List<ImageFrameMetadata> framesMetadata = new();
List<ImageFrameMetadata> framesMetadata = [];
try
{
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream);
@ -595,9 +595,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
Unsafe.Add(ref rowRef, (uint)x) = TPixel.FromRgb24(colorTable[index]);
}
}
else
@ -613,9 +611,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
int index = Numerics.Clamp(rawIndex, 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
Unsafe.Add(ref rowRef, (uint)x) = TPixel.FromRgb24(colorTable[index]);
}
}
}
@ -711,10 +707,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
Color[] colorTable = new Color[this.imageDescriptor.LocalColorTableSize];
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize]);
for (int i = 0; i < colorTable.Length; i++)
{
colorTable[i] = new Color(rgbTable[i]);
}
Color.FromPixel(rgbTable, colorTable);
gifMeta.LocalColorTable = colorTable;
}
@ -789,10 +782,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
Color[] colorTable = new Color[this.logicalScreenDescriptor.GlobalColorTableSize];
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(globalColorTableSpan);
for (int i = 0; i < colorTable.Length; i++)
{
colorTable[i] = new Color(rgbTable[i]);
}
Color.FromPixel(rgbTable, colorTable);
this.gifMetadata.GlobalColorTable = colorTable;
}

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

@ -169,7 +169,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
this.EncodeFirstFrame(stream, frameMetadata, quantized);
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
TPixel[] globalPalette = image.Frames.Count == 1 ? Array.Empty<TPixel>() : quantized.Palette.ToArray();
TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMethod);
@ -488,8 +488,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
int index = -1;
if (quantized != null)
{
TPixel transparentPixel = default;
transparentPixel.FromScaledVector4(Vector4.Zero);
TPixel transparentPixel = TPixel.FromScaledVector4(Vector4.Zero);
ReadOnlySpan<TPixel> palette = quantized.Palette.Span;
// Transparent pixels are much more likely to be found at the end of a palette.
@ -693,7 +692,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
}
IMemoryOwner<byte>? owner = null;
Span<byte> extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
scoped Span<byte> extensionBuffer = []; // workaround compiler limitation
if (extensionSize > 128)
{
owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3);

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

@ -82,13 +82,13 @@ public class GifFrameMetadata : IDeepCloneable
{
// TODO: v4 How do I link the parent metadata to the frame metadata to get the global color table?
int index = -1;
float background = 1f;
const float background = 1f;
if (metadata.ColorTable.HasValue)
{
ReadOnlySpan<Color> colorTable = metadata.ColorTable.Value.Span;
for (int i = 0; i < colorTable.Length; i++)
{
Vector4 vector = (Vector4)colorTable[i];
Vector4 vector = colorTable[i].ToScaledVector4();
if (vector.W < background)
{
index = i;

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

@ -386,29 +386,33 @@ internal partial struct Block8x8F : IEquatable<Block8x8F>
public void LoadFromInt16ExtendedAvx2(ref Block8x8 source)
{
DebugGuard.IsTrue(
SimdUtils.HasVector8,
Avx2.IsSupported,
"LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!");
ref Vector<short> sRef = ref Unsafe.As<Block8x8, Vector<short>>(ref source);
ref Vector<float> dRef = ref Unsafe.As<Block8x8F, Vector<float>>(ref this);
ref short sRef = ref Unsafe.As<Block8x8, short>(ref source);
ref Vector256<float> dRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref this);
// Vector<ushort>.Count == 16 on AVX2
// Vector256<ushort>.Count == 16 on AVX2
// We can process 2 block rows in a single step
SimdUtils.ExtendedIntrinsics.ConvertToSingle(sRef, out Vector<float> top, out Vector<float> bottom);
dRef = top;
Unsafe.Add(ref dRef, 1) = bottom;
SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 1), out top, out bottom);
Unsafe.Add(ref dRef, 2) = top;
Unsafe.Add(ref dRef, 3) = bottom;
SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 2), out top, out bottom);
Unsafe.Add(ref dRef, 4) = top;
Unsafe.Add(ref dRef, 5) = bottom;
SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 3), out top, out bottom);
Unsafe.Add(ref dRef, 6) = top;
Unsafe.Add(ref dRef, 7) = bottom;
Vector256<int> top = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef));
Vector256<int> bottom = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)Vector256<int>.Count));
dRef = Avx.ConvertToVector256Single(top);
Unsafe.Add(ref dRef, 1) = Avx.ConvertToVector256Single(bottom);
top = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256<int>.Count * 2)));
bottom = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256<int>.Count * 3)));
Unsafe.Add(ref dRef, 2) = Avx.ConvertToVector256Single(top);
Unsafe.Add(ref dRef, 3) = Avx.ConvertToVector256Single(bottom);
top = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256<int>.Count * 4)));
bottom = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256<int>.Count * 5)));
Unsafe.Add(ref dRef, 4) = Avx.ConvertToVector256Single(top);
Unsafe.Add(ref dRef, 5) = Avx.ConvertToVector256Single(bottom);
top = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256<int>.Count * 6)));
bottom = Avx2.ConvertToVector256Int32(Vector128.LoadUnsafe(ref sRef, (nuint)(Vector256<int>.Count * 7)));
Unsafe.Add(ref dRef, 6) = Avx.ConvertToVector256Single(top);
Unsafe.Add(ref dRef, 7) = Avx.ConvertToVector256Single(bottom);
}
/// <summary>

17
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs

@ -22,6 +22,9 @@ internal struct JpegBitReader
// Whether there is no more good data to pull from the stream for the current mcu.
private bool badData;
// How many times have we hit the eof.
private int eofHitCount;
public JpegBitReader(BufferedReadStream stream)
{
this.stream = stream;
@ -31,6 +34,7 @@ internal struct JpegBitReader
this.MarkerPosition = 0;
this.badData = false;
this.NoData = false;
this.eofHitCount = 0;
}
/// <summary>
@ -219,11 +223,16 @@ internal struct JpegBitReader
// we know we have hit the EOI and completed decoding the scan buffer.
if (value == -1 || (this.badData && this.data == 0 && this.stream.Position >= this.stream.Length))
{
// We've encountered the end of the file stream which means there's no EOI marker
// We've hit the end of the file stream more times than allowed which means there's no EOI marker
// in the image or the SOS marker has the wrong dimensions set.
this.badData = true;
this.NoData = true;
value = 0;
if (this.eofHitCount > JpegConstants.Huffman.FetchLoop)
{
this.badData = true;
this.NoData = true;
value = 0;
}
this.eofHitCount++;
}
return value;

32
src/ImageSharp/Formats/Jpeg/JpegComData.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jpeg;
/// <summary>
/// Represents a JPEG comment
/// </summary>
public readonly struct JpegComData
{
/// <summary>
/// Initializes a new instance of the <see cref="JpegComData"/> struct.
/// </summary>
/// <param name="value">The comment buffer.</param>
public JpegComData(ReadOnlyMemory<char> value)
=> this.Value = value;
/// <summary>
/// Gets the value.
/// </summary>
public ReadOnlyMemory<char> Value { get; }
/// <summary>
/// Converts string to <see cref="JpegComData"/>
/// </summary>
/// <param name="value">The comment string.</param>
/// <returns>The <see cref="JpegComData"/></returns>
public static JpegComData FromString(string value) => new(value.AsMemory());
/// <inheritdoc/>
public override string ToString() => this.Value.ToString();
}

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

@ -480,9 +480,11 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
break;
case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM:
stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.COM:
this.ProcessComMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DAC:
if (metadataOnly)
@ -515,6 +517,25 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.scanDecoder = null;
}
/// <summary>
/// Assigns COM marker bytes to comment property
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="markerContentByteSize">The remaining bytes in the segment block.</param>
private void ProcessComMarker(BufferedReadStream stream, int markerContentByteSize)
{
char[] chars = new char[markerContentByteSize];
JpegMetadata metadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance);
for (int i = 0; i < markerContentByteSize; i++)
{
int read = stream.ReadByte();
chars[i] = (char)read;
}
metadata.Comments.Add(new JpegComData(chars));
}
/// <summary>
/// Returns encoded colorspace based on the adobe APP14 marker.
/// </summary>

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -25,6 +26,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
/// </summary>
private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs();
/// <summary>
/// The current calling encoder.
/// </summary>
private readonly JpegEncoder encoder;
/// <summary>
@ -89,6 +93,9 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
// Write Exif, XMP, ICC and IPTC profiles
this.WriteProfiles(metadata, buffer);
// Write comments
this.WriteComments(image.Configuration, jpegMetadata);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, frameConfig, buffer);
@ -167,6 +174,51 @@ internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals
this.outputStream.Write(buffer, 0, 18);
}
/// <summary>
/// Writes the COM tags.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="metadata">The image metadata.</param>
private void WriteComments(Configuration configuration, JpegMetadata metadata)
{
if (metadata.Comments.Count == 0)
{
return;
}
const int maxCommentLength = 65533;
using IMemoryOwner<byte> bufferOwner = configuration.MemoryAllocator.Allocate<byte>(maxCommentLength);
Span<byte> buffer = bufferOwner.Memory.Span;
foreach (JpegComData comment in metadata.Comments)
{
int totalLength = comment.Value.Length;
if (totalLength == 0)
{
continue;
}
// Loop through and split the comment into multiple comments if the comment length
// is greater than the maximum allowed length.
while (totalLength > 0)
{
int currentLength = Math.Min(totalLength, maxCommentLength);
// Write the marker header.
this.WriteMarkerHeader(JpegConstants.Markers.COM, currentLength + 2, buffer);
ReadOnlySpan<char> commentValue = comment.Value.Span.Slice(comment.Value.Length - totalLength, currentLength);
for (int i = 0; i < commentValue.Length; i++)
{
buffer[i] = (byte)commentValue[i];
}
// Write the comment.
this.outputStream.Write(buffer, 0, currentLength);
totalLength -= currentLength;
}
}
}
/// <summary>
/// Writes the Define Huffman Table marker and tables.
/// </summary>

7
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -15,6 +15,7 @@ public class JpegMetadata : IDeepCloneable
/// </summary>
public JpegMetadata()
{
this.Comments = new List<JpegComData>();
}
/// <summary>
@ -25,6 +26,7 @@ public class JpegMetadata : IDeepCloneable
{
this.ColorType = other.ColorType;
this.Comments = other.Comments;
this.LuminanceQuality = other.LuminanceQuality;
this.ChrominanceQuality = other.ChrominanceQuality;
}
@ -101,6 +103,11 @@ public class JpegMetadata : IDeepCloneable
/// </remarks>
public bool? Progressive { get; internal set; }
/// <summary>
/// Gets the comments.
/// </summary>
public IList<JpegComData> Comments { get; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetadata(this);
}

1
src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Text;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;

53
src/ImageSharp/Formats/PixelTypeInfo.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
// TODO: Review this class as it's used to represent 2 different things.
// 1.The encoded image pixel format.
// 2. The pixel format of the decoded image.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Contains information about the pixels that make up an images visual data.
/// </summary>
public class PixelTypeInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelTypeInfo"/> class.
/// </summary>
/// <param name="bitsPerPixel">Color depth, in number of bits per pixel.</param>
public PixelTypeInfo(int bitsPerPixel)
=> this.BitsPerPixel = bitsPerPixel;
/// <summary>
/// Initializes a new instance of the <see cref="PixelTypeInfo"/> class.
/// </summary>
/// <param name="bitsPerPixel">Color depth, in number of bits per pixel.</param>
/// <param name="alpha">The pixel alpha transparency behavior.</param>
public PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation alpha)
{
this.BitsPerPixel = bitsPerPixel;
this.AlphaRepresentation = alpha;
}
/// <summary>
/// Gets color depth, in number of bits per pixel.
/// </summary>
public int BitsPerPixel { get; }
/// <summary>
/// Gets the pixel alpha transparency behavior.
/// <see langword="null"/> means unknown, unspecified.
/// </summary>
public PixelAlphaRepresentation? AlphaRepresentation { get; }
internal static PixelTypeInfo Create<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
=> new(Unsafe.SizeOf<TPixel>() * 8);
internal static PixelTypeInfo Create<TPixel>(PixelAlphaRepresentation alpha)
where TPixel : unmanaged, IPixel<TPixel>
=> new(Unsafe.SizeOf<TPixel>() * 8, alpha);
}

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// Note the value assignment, This will allow us to add 1, 2, and 4 bit encoding when we support it.

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

@ -126,6 +126,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// </summary>
private readonly Crc32 crc32 = new();
/// <summary>
/// The maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
/// </summary>
private readonly int maxUncompressedLength;
/// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
/// </summary>
@ -138,6 +143,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.skipMetadata = options.GeneralOptions.SkipMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
}
internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
@ -149,6 +155,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
}
/// <inheritdoc/>
@ -227,8 +234,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowMissingFrameControl();
}
previousFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
this.InitializeFrame(previousFrameControl.Value, currentFrameControl.Value, image, previousFrame, out currentFrame);
this.InitializeFrame(previousFrameControl, currentFrameControl.Value, image, previousFrame, out currentFrame);
this.currentStream.Position += 4;
this.ReadScanlines(
@ -239,11 +245,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
currentFrameControl.Value,
cancellationToken);
previousFrame = currentFrame;
previousFrameControl = currentFrameControl;
// if current frame dispose is restore to previous, then from future frame's perspective, it never happened
if (currentFrameControl.Value.DisposeOperation != PngDisposalMethod.RestoreToPrevious)
{
previousFrame = currentFrame;
previousFrameControl = currentFrameControl;
}
break;
case PngChunkType.Data:
pngMetadata.AnimateRootFrame = currentFrameControl != null;
currentFrameControl ??= new((uint)this.header.Width, (uint)this.header.Height);
if (image is null)
{
@ -260,9 +271,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.ReadNextDataChunk,
currentFrameControl.Value,
cancellationToken);
if (pngMetadata.AnimateRootFrame)
{
previousFrame = currentFrame;
previousFrameControl = currentFrameControl;
}
previousFrame = currentFrame;
previousFrameControl = currentFrameControl;
break;
case PngChunkType.Palette:
this.palette = chunk.Data.GetSpan().ToArray();
@ -602,23 +616,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared.
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
{
image = new Image<TPixel>(
this.configuration,
this.header.Width,
this.header.Height,
metadata);
}
else
{
image = Image.CreateUninitialized<TPixel>(
this.configuration,
this.header.Width,
this.header.Height,
metadata);
}
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata();
frameMetadata.FromChunk(in frameControl);
@ -647,7 +645,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <param name="previousFrame">The previous frame.</param>
/// <param name="frame">The created frame</param>
private void InitializeFrame<TPixel>(
FrameControl previousFrameControl,
FrameControl? previousFrameControl,
FrameControl currentFrameControl,
Image<TPixel> image,
ImageFrame<TPixel>? previousFrame,
@ -660,12 +658,16 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
frame = image.Frames.AddFrame(previousFrame ?? image.Frames.RootFrame);
// If the first `fcTL` chunk uses a `dispose_op` of APNG_DISPOSE_OP_PREVIOUS it should be treated as APNG_DISPOSE_OP_BACKGROUND.
if (previousFrameControl.DisposeOperation == PngDisposalMethod.RestoreToBackground
|| (previousFrame is null && previousFrameControl.DisposeOperation == PngDisposalMethod.RestoreToPrevious))
// So, if restoring to before first frame, clear entire area. Same if first frame (previousFrameControl null).
if (previousFrameControl == null || (previousFrame is null && previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToPrevious))
{
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion();
pixelRegion.Clear();
}
else if (previousFrameControl.Value.DisposeOperation == PngDisposalMethod.RestoreToBackground)
{
Rectangle restoreArea = previousFrameControl.Bounds;
Rectangle interest = Rectangle.Intersect(frame.Bounds(), restoreArea);
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(interest);
Rectangle restoreArea = previousFrameControl.Value.Bounds;
Buffer2DRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(restoreArea);
pixelRegion.Clear();
}
@ -1240,10 +1242,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
Color[] colorTable = new Color[palette.Length / Unsafe.SizeOf<Rgb24>()];
ReadOnlySpan<Rgb24> rgbTable = MemoryMarshal.Cast<byte, Rgb24>(palette);
for (int i = 0; i < colorTable.Length; i++)
{
colorTable[i] = new Color(rgbTable[i]);
}
Color.FromPixel(rgbTable, colorTable);
if (alpha.Length > 0)
{
@ -1276,14 +1275,14 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2));
ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2));
pngMetadata.TransparentColor = new(new Rgb48(rc, gc, bc));
pngMetadata.TransparentColor = Color.FromPixel(new Rgb48(rc, gc, bc));
return;
}
byte r = ReadByteLittleEndian(alpha, 0);
byte g = ReadByteLittleEndian(alpha, 2);
byte b = ReadByteLittleEndian(alpha, 4);
pngMetadata.TransparentColor = new(new Rgb24(r, g, b));
pngMetadata.TransparentColor = Color.FromPixel(new Rgb24(r, g, b));
}
}
else if (this.pngColorType == PngColorType.Grayscale)
@ -1578,7 +1577,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ReadOnlySpan<byte> compressedData = data[(zeroIndex + 2)..];
if (this.TryDecompressZlibData(compressedData, out byte[] iccpProfileBytes))
if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] iccpProfileBytes))
{
metadata.IccProfile = new IccProfile(iccpProfileBytes);
}
@ -1588,9 +1587,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// Tries to decompress zlib compressed data.
/// </summary>
/// <param name="compressedData">The compressed data.</param>
/// <param name="maxLength">The maximum uncompressed length.</param>
/// <param name="uncompressedBytesArray">The uncompressed bytes array.</param>
/// <returns>True, if de-compressing was successful.</returns>
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, out byte[] uncompressedBytesArray)
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, int maxLength, out byte[] uncompressedBytesArray)
{
fixed (byte* compressedDataBase = compressedData)
{
@ -1610,6 +1610,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
while (bytesRead != 0)
{
if (memoryStreamOutput.Length > maxLength)
{
uncompressedBytesArray = Array.Empty<byte>();
return false;
}
memoryStreamOutput.Write(destUncompressedData[..bytesRead]);
bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
}
@ -1752,7 +1758,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <returns>The <see cref="bool"/>.</returns>
private bool TryDecompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, [NotNullWhen(true)] out string? value)
{
if (this.TryDecompressZlibData(compressedData, out byte[] uncompressedData))
if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] uncompressedData))
{
value = encoding.GetString(uncompressedData);
return true;
@ -1877,8 +1883,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngChunkType type = this.ReadChunkType(buffer);
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
// We can skip all other chunk data in the stream for better performance.
if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency && type != PngChunkType.Palette)
// We can skip most other chunk data in the stream for better performance.
if (this.colorMetadataOnly &&
type != PngChunkType.Header &&
type != PngChunkType.Transparency &&
type != PngChunkType.Palette &&
type != PngChunkType.AnimationControl &&
type != PngChunkType.FrameControl)
{
chunk = new PngChunk(length, type);
return true;

6
src/ImageSharp/Formats/Png/PngDecoderOptions.cs

@ -15,4 +15,10 @@ public sealed class PngDecoderOptions : ISpecializedDecoderOptions
/// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG.
/// </summary>
public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical;
/// <summary>
/// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
/// Defaults to 8MB
/// </summary>
public int MaxUncompressedAncillaryChunkSizeBytes { get; init; } = 8 * 1024 * 1024; // 8MB
}

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

@ -4,6 +4,7 @@
using System.Buffers;
using System.Buffers.Binary;
using System.IO.Hashing;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
@ -166,6 +167,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
ImageFrame<TPixel>? clonedFrame = null;
ImageFrame<TPixel> currentFrame = image.Frames.RootFrame;
int currentFrameIndex = 0;
bool clearTransparency = this.encoder.TransparentColorMode is PngTransparentColorMode.Clear;
if (clearTransparency)
@ -194,29 +196,50 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
if (image.Frames.Count > 1)
{
this.WriteAnimationControlChunk(stream, (uint)image.Frames.Count, pngMetadata.RepeatCount);
this.WriteAnimationControlChunk(stream, (uint)(image.Frames.Count - (pngMetadata.AnimateRootFrame ? 0 : 1)), pngMetadata.RepeatCount);
}
// If the first frame isn't animated, write it as usual and skip it when writing animated frames
if (!pngMetadata.AnimateRootFrame || image.Frames.Count == 1)
{
FrameControl frameControl = new((uint)this.width, (uint)this.height);
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
currentFrameIndex++;
}
// Write the first frame.
if (image.Frames.Count > 1)
{
// Write the first animated frame.
currentFrame = image.Frames[currentFrameIndex];
PngFrameMetadata frameMetadata = GetPngFrameMetadata(currentFrame);
PngDisposalMethod previousDisposal = frameMetadata.DisposalMethod;
FrameControl frameControl = this.WriteFrameControlChunk(stream, frameMetadata, currentFrame.Bounds(), 0);
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
uint sequenceNumber = 1;
if (pngMetadata.AnimateRootFrame)
{
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
}
else
{
sequenceNumber += this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, true);
}
currentFrameIndex++;
// Capture the global palette for reuse on subsequent frames.
ReadOnlyMemory<TPixel>? previousPalette = quantized?.Palette.ToArray();
// Write following frames.
uint increment = 0;
ImageFrame<TPixel> previousFrame = image.Frames.RootFrame;
// This frame is reused to store de-duplicated pixel buffers.
using ImageFrame<TPixel> encodingFrame = new(image.Configuration, previousFrame.Size());
for (int i = 1; i < image.Frames.Count; i++)
for (; currentFrameIndex < image.Frames.Count; currentFrameIndex++)
{
ImageFrame<TPixel>? prev = previousDisposal == PngDisposalMethod.RestoreToBackground ? null : previousFrame;
currentFrame = image.Frames[i];
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
currentFrame = image.Frames[currentFrameIndex];
ImageFrame<TPixel>? nextFrame = currentFrameIndex < image.Frames.Count - 1 ? image.Frames[currentFrameIndex + 1] : null;
frameMetadata = GetPngFrameMetadata(currentFrame);
bool blend = frameMetadata.BlendMethod == PngBlendMethod.Over;
@ -237,22 +260,17 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
}
// Each frame control sequence number must be incremented by the number of frame data chunks that follow.
frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, (uint)i + increment);
frameControl = this.WriteFrameControlChunk(stream, frameMetadata, bounds, sequenceNumber);
// Dispose of previous quantized frame and reassign.
quantized?.Dispose();
quantized = this.CreateQuantizedImageAndUpdateBitDepth(pngMetadata, encodingFrame, bounds, previousPalette);
increment += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true);
sequenceNumber += this.WriteDataChunks(frameControl, encodingFrame.PixelBuffer.GetRegion(bounds), quantized, stream, true) + 1;
previousFrame = currentFrame;
previousDisposal = frameMetadata.DisposalMethod;
}
}
else
{
FrameControl frameControl = new((uint)this.width, (uint)this.height);
this.WriteDataChunks(frameControl, currentFrame.PixelBuffer.GetRegion(), quantized, stream, false);
}
this.WriteEndChunk(stream);
@ -328,18 +346,17 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
=> clone.ProcessPixelRows(accessor =>
{
// TODO: We should be able to speed this up with SIMD and masking.
Rgba32 rgba32 = default;
Rgba32 transparent = Color.Transparent;
Rgba32 transparent = Color.Transparent.ToPixel<Rgba32>();
for (int y = 0; y < accessor.Height; y++)
{
Span<TPixel> span = accessor.GetRowSpan(y);
for (int x = 0; x < accessor.Width; x++)
{
span[x].ToRgba32(ref rgba32);
if (rgba32.A is 0)
ref TPixel pixel = ref span[x];
Rgba32 rgba = pixel.ToRgba32();
if (rgba.A is 0)
{
span[x].FromRgba32(transparent);
pixel = TPixel.FromRgba32(transparent);
}
}
}
@ -1066,7 +1083,7 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
else
{
alpha.Clear();
Rgb24 rgb = pngMetadata.TransparentColor.Value.ToRgb24();
Rgb24 rgb = pngMetadata.TransparentColor.Value.ToPixel<Rgb24>();
alpha[1] = rgb.R;
alpha[3] = rgb.G;
alpha[5] = rgb.B;
@ -1466,23 +1483,48 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
// Use options, then check metadata, if nothing set there then we suggest
// a sensible default based upon the pixel format.
this.colorType = encoder.ColorType ?? pngMetadata.ColorType ?? SuggestColorType<TPixel>();
if (!encoder.FilterMethod.HasValue)
PngColorType? colorType = encoder.ColorType ?? pngMetadata.ColorType;
byte? bits = (byte?)(encoder.BitDepth ?? pngMetadata.BitDepth);
if (colorType is null || bits is null)
{
// Specification recommends default filter method None for paletted images and Paeth for others.
this.filterMethod = this.colorType is PngColorType.Palette ? PngFilterMethod.None : PngFilterMethod.Paeth;
PixelTypeInfo info = TPixel.GetPixelTypeInfo();
PixelComponentInfo? componentInfo = info.ComponentInfo;
colorType ??= SuggestColorType<TPixel>(in info);
if (bits is null)
{
// TODO: Update once we stop abusing PixelTypeInfo in decoders.
if (componentInfo.HasValue)
{
PixelComponentInfo c = componentInfo.Value;
bits = (byte)SuggestBitDepth<TPixel>(in c);
}
else
{
bits = (byte)PngBitDepth.Bit8;
}
}
}
// Ensure bit depth and color type are a supported combination.
// Bit8 is the only bit depth supported by all color types.
byte bits = (byte)(encoder.BitDepth ?? pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>());
byte[] validBitDepths = PngConstants.ColorTypes[this.colorType];
byte[] validBitDepths = PngConstants.ColorTypes[colorType.Value];
if (Array.IndexOf(validBitDepths, bits) == -1)
{
bits = (byte)PngBitDepth.Bit8;
}
this.bitDepth = bits;
this.colorType = colorType.Value;
this.bitDepth = bits.Value;
if (!encoder.FilterMethod.HasValue)
{
// Specification recommends default filter method None for paletted images and Paeth for others.
this.filterMethod = this.colorType is PngColorType.Palette ? PngFilterMethod.None : PngFilterMethod.Paeth;
}
use16Bit = bits == (byte)PngBitDepth.Bit16;
bytesPerPixel = CalculateBytesPerPixel(this.colorType, use16Bit);
@ -1535,7 +1577,24 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
{
// We can use the color data from the decoded metadata here.
// We avoid dithering by default to preserve the original colors.
this.derivedTransparencyIndex = metadata.ColorTable.Value.Span.IndexOf(Color.Transparent);
ReadOnlySpan<Color> palette = metadata.ColorTable.Value.Span;
// Certain operations perform alpha premultiplication, which can cause the color to change so we
// must search for the transparency index in the palette.
// Transparent pixels are much more likely to be found at the end of a palette.
int index = -1;
for (int i = palette.Length - 1; i >= 0; i--)
{
Vector4 instance = palette[i].ToScaledVector4();
if (instance.W == 0f)
{
index = i;
break;
}
}
this.derivedTransparencyIndex = index;
this.quantizer = new PaletteQuantizer(metadata.ColorTable.Value, new() { Dither = null }, this.derivedTransparencyIndex);
}
else
@ -1611,53 +1670,44 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
/// <summary>
/// Returns a suggested <see cref="PngColorType"/> for the given <typeparamref name="TPixel"/>
/// This is not exhaustive but covers many common pixel formats.
/// </summary>
/// <param name="info">The pixel type info.</param>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
private static PngColorType SuggestColorType<TPixel>()
private static PngColorType SuggestColorType<TPixel>(in PixelTypeInfo info)
where TPixel : unmanaged, IPixel<TPixel>
=> default(TPixel) switch
{
A8 => PngColorType.GrayscaleWithAlpha,
Argb32 => PngColorType.RgbWithAlpha,
Bgr24 => PngColorType.Rgb,
Bgra32 => PngColorType.RgbWithAlpha,
L8 => PngColorType.Grayscale,
L16 => PngColorType.Grayscale,
La16 => PngColorType.GrayscaleWithAlpha,
La32 => PngColorType.GrayscaleWithAlpha,
Rgb24 => PngColorType.Rgb,
Rgba32 => PngColorType.RgbWithAlpha,
Rgb48 => PngColorType.Rgb,
Rgba64 => PngColorType.RgbWithAlpha,
RgbaVector => PngColorType.RgbWithAlpha,
_ => PngColorType.RgbWithAlpha
{
if (info.AlphaRepresentation == PixelAlphaRepresentation.None)
{
return info.ColorType switch
{
PixelColorType.Grayscale => PngColorType.Grayscale,
_ => PngColorType.Rgb,
};
}
return info.ColorType switch
{
PixelColorType.Grayscale | PixelColorType.Alpha or PixelColorType.Alpha => PngColorType.GrayscaleWithAlpha,
_ => PngColorType.RgbWithAlpha,
};
}
/// <summary>
/// Returns a suggested <see cref="PngBitDepth"/> for the given <typeparamref name="TPixel"/>
/// This is not exhaustive but covers many common pixel formats.
/// </summary>
/// <param name="info">The pixel type info.</param>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
private static PngBitDepth SuggestBitDepth<TPixel>()
private static PngBitDepth SuggestBitDepth<TPixel>(in PixelComponentInfo info)
where TPixel : unmanaged, IPixel<TPixel>
=> default(TPixel) switch
{
A8 => PngBitDepth.Bit8,
Argb32 => PngBitDepth.Bit8,
Bgr24 => PngBitDepth.Bit8,
Bgra32 => PngBitDepth.Bit8,
L8 => PngBitDepth.Bit8,
L16 => PngBitDepth.Bit16,
La16 => PngBitDepth.Bit8,
La32 => PngBitDepth.Bit16,
Rgb24 => PngBitDepth.Bit8,
Rgba32 => PngBitDepth.Bit8,
Rgb48 => PngBitDepth.Bit16,
Rgba64 => PngBitDepth.Bit16,
RgbaVector => PngBitDepth.Bit16,
_ => PngBitDepth.Bit8
};
{
int bits = info.GetMaximumComponentPrecision();
if (bits > (int)PixelComponentBitDepth.Bit8)
{
return PngBitDepth.Bit16;
}
return PngBitDepth.Bit8;
}
private unsafe struct ScratchBuffer
{

6
src/ImageSharp/Formats/Png/PngMetadata.cs

@ -29,6 +29,7 @@ public class PngMetadata : IDeepCloneable
this.InterlaceMethod = other.InterlaceMethod;
this.TransparentColor = other.TransparentColor;
this.RepeatCount = other.RepeatCount;
this.AnimateRootFrame = other.AnimateRootFrame;
if (other.ColorTable?.Length > 0)
{
@ -83,6 +84,11 @@ public class PngMetadata : IDeepCloneable
/// </summary>
public uint RepeatCount { get; set; } = 1;
/// <summary>
/// Gets or sets a value indicating whether the root frame is shown as part of the animated sequence
/// </summary>
public bool AnimateRootFrame { get; set; } = true;
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new PngMetadata(this);

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

@ -42,7 +42,6 @@ internal static class PngScanlineProcessor
where TPixel : unmanaged, IPixel<TPixel>
{
uint offset = pixelOffset + frameControl.XOffset;
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(bitDepth) - 1);
@ -55,8 +54,7 @@ internal static class PngScanlineProcessor
for (nuint x = offset; x < frameControl.XMax; x += increment, o += 2)
{
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
pixel.FromL16(Unsafe.As<ushort, L16>(ref luminance));
Unsafe.Add(ref rowSpanRef, x) = pixel;
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromL16(Unsafe.As<ushort, L16>(ref luminance));
}
}
else
@ -64,8 +62,7 @@ internal static class PngScanlineProcessor
for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
{
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor);
pixel.FromL8(Unsafe.As<byte, L8>(ref luminance));
Unsafe.Add(ref rowSpanRef, x) = pixel;
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromL8(Unsafe.As<byte, L8>(ref luminance));
}
}
@ -75,30 +72,22 @@ internal static class PngScanlineProcessor
if (bitDepth == 16)
{
L16 transparent = transparentColor.Value.ToPixel<L16>();
La32 source = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += 2)
{
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
source.L = luminance;
source.A = luminance.Equals(transparent.PackedValue) ? ushort.MinValue : ushort.MaxValue;
pixel.FromLa32(source);
Unsafe.Add(ref rowSpanRef, x) = pixel;
La32 source = new(luminance, luminance.Equals(transparent.PackedValue) ? ushort.MinValue : ushort.MaxValue);
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromLa32(source);
}
}
else
{
byte transparent = (byte)(transparentColor.Value.ToPixel<L8>().PackedValue * scaleFactor);
La16 source = default;
for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
{
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor);
source.L = luminance;
source.A = luminance.Equals(transparent) ? byte.MinValue : byte.MaxValue;
pixel.FromLa16(source);
Unsafe.Add(ref rowSpanRef, x) = pixel;
La16 source = new(luminance, luminance.Equals(transparent) ? byte.MinValue : byte.MaxValue);
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromLa16(source);
}
}
}
@ -133,34 +122,28 @@ internal static class PngScanlineProcessor
where TPixel : unmanaged, IPixel<TPixel>
{
uint offset = pixelOffset + frameControl.XOffset;
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
if (bitDepth == 16)
{
La32 source = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += 4)
{
source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
ushort l = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2));
ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2));
pixel.FromLa32(source);
Unsafe.Add(ref rowSpanRef, (uint)x) = pixel;
Unsafe.Add(ref rowSpanRef, (uint)x) = TPixel.FromLa32(new(l, a));
}
}
else
{
La16 source = default;
nuint offset2 = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment)
{
source.L = Unsafe.Add(ref scanlineSpanRef, offset2);
source.A = Unsafe.Add(ref scanlineSpanRef, offset2 + bytesPerSample);
pixel.FromLa16(source);
Unsafe.Add(ref rowSpanRef, x) = pixel;
byte l = Unsafe.Add(ref scanlineSpanRef, offset2);
byte a = Unsafe.Add(ref scanlineSpanRef, offset2 + bytesPerSample);
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromLa16(new(l, a));
offset2 += bytesPerPixel;
}
}
@ -194,16 +177,15 @@ internal static class PngScanlineProcessor
PngThrowHelper.ThrowMissingPalette();
}
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span);
uint offset = pixelOffset + frameControl.XOffset;
for (nuint x = pixelOffset, o = 0; x < frameControl.XMax; x += increment, o++)
for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
{
uint index = Unsafe.Add(ref scanlineSpanRef, o);
pixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToRgba32());
Unsafe.Add(ref rowSpanRef, x) = pixel;
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToPixel<Rgba32>());
}
}
@ -243,8 +225,6 @@ internal static class PngScanlineProcessor
where TPixel : unmanaged, IPixel<TPixel>
{
uint offset = pixelOffset + frameControl.XOffset;
TPixel pixel = default;
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
@ -252,16 +232,13 @@ internal static class PngScanlineProcessor
{
if (bitDepth == 16)
{
Rgb48 rgb48 = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
pixel.FromRgb48(rgb48);
Unsafe.Add(ref rowSpanRef, x) = pixel;
ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgb48(new(r, g, b));
}
}
else if (pixelOffset == 0 && increment == 1)
@ -274,16 +251,13 @@ internal static class PngScanlineProcessor
}
else
{
Rgb24 rgb = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgb.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
rgb.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
rgb.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
pixel.FromRgb24(rgb);
Unsafe.Add(ref rowSpanRef, x) = pixel;
byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o);
byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgb24(new(r, g, b));
}
}
@ -293,27 +267,20 @@ internal static class PngScanlineProcessor
if (bitDepth == 16)
{
Rgb48 transparent = transparentColor.Value.ToPixel<Rgb48>();
Rgb48 rgb48 = default;
Rgba64 rgba64 = default;
Rgba64 rgba = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
rgba64.Rgb = rgb48;
rgba64.A = rgb48.Equals(transparent) ? ushort.MinValue : ushort.MaxValue;
pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel;
rgba.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgba.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
rgba.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
rgba.A = rgba.Rgb.Equals(transparent) ? ushort.MinValue : ushort.MaxValue;
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(rgba);
}
}
else
{
Rgb24 transparent = transparentColor.Value.ToPixel<Rgb24>();
Rgba32 rgba = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
@ -322,9 +289,7 @@ internal static class PngScanlineProcessor
rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
rgba.A = transparent.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue;
pixel.FromRgba32(rgba);
Unsafe.Add(ref rowSpanRef, x) = pixel;
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(rgba);
}
}
}
@ -362,22 +327,18 @@ internal static class PngScanlineProcessor
where TPixel : unmanaged, IPixel<TPixel>
{
uint offset = pixelOffset + frameControl.XOffset;
TPixel pixel = default;
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
if (bitDepth == 16)
{
Rgba64 rgba64 = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
pixel.FromRgba64(rgba64);
Unsafe.Add(ref rowSpanRef, x) = pixel;
ushort r = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample));
ushort g = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample));
ushort b = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample));
ushort a = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba64(new(r, g, b, a));
}
}
else if (pixelOffset == 0 && increment == 1)
@ -391,17 +352,14 @@ internal static class PngScanlineProcessor
else
{
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan);
Rgba32 rgba = default;
int o = 0;
for (nuint x = offset; x < frameControl.XMax; x += increment, o += bytesPerPixel)
{
rgba.R = Unsafe.Add(ref scanlineSpanRef, (uint)o);
rgba.G = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
rgba.B = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
rgba.A = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
pixel.FromRgba32(rgba);
Unsafe.Add(ref rowSpanRef, x) = pixel;
byte r = Unsafe.Add(ref scanlineSpanRef, (uint)o);
byte g = Unsafe.Add(ref scanlineSpanRef, (uint)(o + bytesPerSample));
byte b = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (2 * bytesPerSample)));
byte a = Unsafe.Add(ref scanlineSpanRef, (uint)(o + (3 * bytesPerSample)));
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(new(r, g, b, a));
}
}
}

14
src/ImageSharp/Formats/Qoi/QoiDecoderCore.cs

@ -149,7 +149,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
Span<Rgba32> previouslySeenPixels = previouslySeenPixelsBuffer.GetSpan();
Rgba32 previousPixel = new(0, 0, 0, 255);
// We save the pixel to avoid loosing the fully opaque black pixel
// We save the pixel to avoid losing the fully opaque black pixel
// See https://github.com/phoboslab/qoi/issues/258
int pixelArrayPosition = GetArrayPosition(previousPixel);
previouslySeenPixels[pixelArrayPosition] = previousPixel;
@ -174,7 +174,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
}
readPixel.A = previousPixel.A;
pixel.FromRgba32(readPixel);
pixel = TPixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
previouslySeenPixels[pixelArrayPosition] = readPixel;
break;
@ -186,7 +186,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
ThrowInvalidImageContentException();
}
pixel.FromRgba32(readPixel);
pixel = TPixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
previouslySeenPixels[pixelArrayPosition] = readPixel;
break;
@ -197,7 +197,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
// Getting one pixel from previously seen pixels
case QoiChunk.QoiOpIndex:
readPixel = previouslySeenPixels[operationByte];
pixel.FromRgba32(readPixel);
pixel = TPixel.FromRgba32(readPixel);
break;
// Get one pixel from the difference (-2..1) of the previous pixel
@ -211,7 +211,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
G = (byte)Numerics.Modulo256(previousPixel.G + (greenDifference - 2)),
B = (byte)Numerics.Modulo256(previousPixel.B + (blueDifference - 2))
};
pixel.FromRgba32(readPixel);
pixel = TPixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
previouslySeenPixels[pixelArrayPosition] = readPixel;
break;
@ -227,7 +227,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
int currentRed = Numerics.Modulo256(diffRedDG - 8 + (diffGreen - 32) + previousPixel.R);
int currentBlue = Numerics.Modulo256(diffBlueDG - 8 + (diffGreen - 32) + previousPixel.B);
readPixel = previousPixel with { R = (byte)currentRed, B = (byte)currentBlue, G = (byte)currentGreen };
pixel.FromRgba32(readPixel);
pixel = TPixel.FromRgba32(readPixel);
pixelArrayPosition = GetArrayPosition(readPixel);
previouslySeenPixels[pixelArrayPosition] = readPixel;
break;
@ -241,7 +241,7 @@ internal class QoiDecoderCore : IImageDecoderInternals
}
readPixel = previousPixel;
pixel.FromRgba32(readPixel);
pixel = TPixel.FromRgba32(readPixel);
for (int k = -1; k < repetitions; k++, j++)
{
if (j == row.Length)

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

@ -222,7 +222,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
private void ReadPaletted<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
bool invertX = InvertX(origin);
for (int y = 0; y < height; y++)
@ -237,14 +236,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow);
}
}
@ -255,14 +254,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow);
}
}
@ -273,14 +272,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, pixelRow);
}
}
@ -319,16 +318,16 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
switch (colorMapPixelSizeInBytes)
{
case 1:
color.FromL8(Unsafe.As<byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
color = TPixel.FromL8(Unsafe.As<byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 2:
this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color);
color = this.ReadPalettedBgra16Pixel<TPixel>(palette, bufferSpan[idx], colorMapPixelSizeInBytes);
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
color = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 4:
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
color = TPixel.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
}
@ -350,17 +349,15 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
private void ReadMonoChrome<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
bool invertX = InvertX(origin);
if (invertX)
if (InvertX(origin))
{
TPixel color = default;
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
ReadL8Pixel(stream, color, x, pixelSpan);
ReadL8Pixel(stream, x, pixelSpan);
}
}
@ -369,8 +366,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0);
Span<byte> rowSpan = row.GetSpan();
bool invertY = InvertY(origin);
if (invertY)
if (InvertY(origin))
{
for (int y = height - 1; y >= 0; y--)
{
@ -398,7 +394,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
private void ReadBgra16<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
bool invertX = InvertX(origin);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0);
Span<byte> rowSpan = row.GetSpan();
@ -426,14 +421,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref MemoryMarshal.GetReference(scratchBuffer)));
pixelSpan[x] = TPixel.FromLa16(Unsafe.As<byte, La16>(ref MemoryMarshal.GetReference(scratchBuffer)));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref MemoryMarshal.GetReference(scratchBuffer)));
pixelSpan[x] = TPixel.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref MemoryMarshal.GetReference(scratchBuffer)));
}
pixelSpan[x] = color;
}
}
else
@ -477,18 +470,16 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
private void ReadBgr24<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
bool invertX = InvertX(origin);
if (invertX)
if (InvertX(origin))
{
Span<byte> scratchBuffer = stackalloc byte[4];
TPixel color = default;
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
ReadBgr24Pixel(stream, color, x, pixelSpan, scratchBuffer);
ReadBgr24Pixel(stream, x, pixelSpan, scratchBuffer);
}
}
@ -497,9 +488,8 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0);
Span<byte> rowSpan = row.GetSpan();
bool invertY = InvertY(origin);
if (invertY)
if (InvertY(origin))
{
for (int y = height - 1; y >= 0; y--)
{
@ -527,7 +517,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
private void ReadBgra32<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
bool invertX = InvertX(origin);
Guard.NotNull(this.tgaMetadata);
@ -565,14 +554,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadBgra32Pixel(stream, x, color, pixelRow, scratchBuffer);
this.ReadBgra32Pixel(stream, x, pixelRow, scratchBuffer);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadBgra32Pixel(stream, x, color, pixelRow, scratchBuffer);
this.ReadBgra32Pixel(stream, x, pixelRow, scratchBuffer);
}
}
}
@ -610,7 +599,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
switch (bytesPerPixel)
{
case 1:
color.FromL8(Unsafe.As<byte, L8>(ref bufferSpan[idx]));
color = TPixel.FromL8(Unsafe.As<byte, L8>(ref bufferSpan[idx]));
break;
case 2:
if (!this.hasAlpha)
@ -621,26 +610,26 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref bufferSpan[idx]));
color = TPixel.FromLa16(Unsafe.As<byte, La16>(ref bufferSpan[idx]));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx]));
color = TPixel.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx]));
}
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
color = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break;
case 4:
if (this.hasAlpha)
{
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref bufferSpan[idx]));
color = TPixel.FromBgra32(Unsafe.As<byte, Bgra32>(ref bufferSpan[idx]));
}
else
{
byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3];
color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha));
color = TPixel.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha));
}
break;
@ -677,16 +666,15 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReadL8Pixel<TPixel>(BufferedReadStream stream, TPixel color, int x, Span<TPixel> pixelSpan)
private static void ReadL8Pixel<TPixel>(BufferedReadStream stream, int x, Span<TPixel> pixelSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
byte pixelValue = (byte)stream.ReadByte();
color.FromL8(Unsafe.As<byte, L8>(ref pixelValue));
pixelSpan[x] = color;
pixelSpan[x] = TPixel.FromL8(Unsafe.As<byte, L8>(ref pixelValue));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReadBgr24Pixel<TPixel>(BufferedReadStream stream, TPixel color, int x, Span<TPixel> pixelSpan, Span<byte> scratchBuffer)
private static void ReadBgr24Pixel<TPixel>(BufferedReadStream stream, int x, Span<TPixel> pixelSpan, Span<byte> scratchBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = stream.Read(scratchBuffer, 0, 3);
@ -695,8 +683,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel");
}
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref MemoryMarshal.GetReference(scratchBuffer)));
pixelSpan[x] = color;
pixelSpan[x] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref MemoryMarshal.GetReference(scratchBuffer)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -714,7 +701,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgra32Pixel<TPixel>(BufferedReadStream stream, int x, TPixel color, Span<TPixel> pixelRow, Span<byte> scratchBuffer)
private void ReadBgra32Pixel<TPixel>(BufferedReadStream stream, int x, Span<TPixel> pixelRow, Span<byte> scratchBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = stream.Read(scratchBuffer, 0, 4);
@ -726,8 +713,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
Guard.NotNull(this.tgaMetadata);
byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : scratchBuffer[3];
color.FromBgra32(new Bgra32(scratchBuffer[2], scratchBuffer[1], scratchBuffer[0], alpha));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromBgra32(new Bgra32(scratchBuffer[2], scratchBuffer[1], scratchBuffer[0], alpha));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -745,7 +731,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra16Pixel<TPixel>(BufferedReadStream stream, Span<byte> palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
private void ReadPalettedBgra16Pixel<TPixel>(BufferedReadStream stream, Span<byte> palette, int colorMapPixelSizeInBytes, int x, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = stream.ReadByte();
@ -754,16 +740,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
}
this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color);
pixelRow[x] = color;
pixelRow[x] = this.ReadPalettedBgra16Pixel<TPixel>(palette, colorIndex, colorMapPixelSizeInBytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra16Pixel<TPixel>(Span<byte> palette, int index, int colorMapPixelSizeInBytes, ref TPixel color)
private TPixel ReadPalettedBgra16Pixel<TPixel>(Span<byte> palette, int index, int colorMapPixelSizeInBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
Bgra5551 bgra = default;
bgra.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref palette[index * colorMapPixelSizeInBytes]));
Bgra5551 bgra = Unsafe.As<byte, Bgra5551>(ref palette[index * colorMapPixelSizeInBytes]);
if (!this.hasAlpha)
{
@ -771,11 +755,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
}
color.FromBgra5551(bgra);
return TPixel.FromBgra5551(bgra);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReadPalettedBgr24Pixel<TPixel>(BufferedReadStream stream, Span<byte> palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
private static void ReadPalettedBgr24Pixel<TPixel>(BufferedReadStream stream, Span<byte> palette, int colorMapPixelSizeInBytes, int x, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = stream.ReadByte();
@ -784,12 +768,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
}
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReadPalettedBgra32Pixel<TPixel>(BufferedReadStream stream, Span<byte> palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
private static void ReadPalettedBgra32Pixel<TPixel>(BufferedReadStream stream, Span<byte> palette, int colorMapPixelSizeInBytes, int x, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = stream.ReadByte();
@ -798,8 +781,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
}
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes]));
}
/// <summary>
@ -937,7 +919,9 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
this.tgaMetadata = this.metadata.GetTgaMetadata();
this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
int alphaBits = this.fileHeader.ImageDescriptor & 0xf;
// TrueColor images with 32 bits per pixel are assumed to always have 8 bit alpha channel,
// because some encoders do not set correctly the alpha bits in the image descriptor.
int alphaBits = this.IsTrueColor32BitPerPixel(this.tgaMetadata.BitsPerPixel) ? 8 : this.fileHeader.ImageDescriptor & 0xf;
if (alphaBits is not 0 and not 1 and not 8)
{
TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits");
@ -949,4 +933,8 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
// Bits 4 and 5 describe the image origin.
return (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
}
private bool IsTrueColor32BitPerPixel(TgaBitsPerPixel bitsPerPixel) => bitsPerPixel == TgaBitsPerPixel.Pixel32 &&
(this.fileHeader.ImageType == TgaImageType.TrueColor ||
this.fileHeader.ImageType == TgaImageType.RleTrueColor);
}

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

@ -5,7 +5,6 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -160,7 +159,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
private void WriteRunLengthEncodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Rgba32 color = default;
Buffer2D<TPixel> pixels = image.PixelBuffer;
for (int y = 0; y < image.Height; y++)
{
@ -168,14 +166,13 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
for (int x = 0; x < image.Width;)
{
TPixel currentPixel = pixelRow[x];
currentPixel.ToRgba32(ref color);
byte equalPixelCount = FindEqualPixels(pixelRow, x);
if (equalPixelCount > 0)
{
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
stream.WriteByte((byte)(equalPixelCount | 128));
this.WritePixel(stream, currentPixel, color);
this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
x += equalPixelCount + 1;
}
else
@ -183,13 +180,12 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
// Write Raw Packet (i.e., Non-Run-Length Encoded):
byte unEqualPixelCount = FindUnEqualPixels(pixelRow, x);
stream.WriteByte(unEqualPixelCount);
this.WritePixel(stream, currentPixel, color);
this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
x++;
for (int i = 0; i < unEqualPixelCount; i++)
{
currentPixel = pixelRow[x];
currentPixel.ToRgba32(ref color);
this.WritePixel(stream, currentPixel, color);
this.WritePixel(stream, currentPixel, currentPixel.ToRgba32());
x++;
}
}

5
src/ImageSharp/Formats/Tga/TgaFileHeader.cs

@ -131,10 +131,7 @@ internal readonly struct TgaFileHeader
/// </summary>
public byte ImageDescriptor { get; }
public static TgaFileHeader Parse(Span<byte> data)
{
return MemoryMarshal.Cast<byte, TgaFileHeader>(data)[0];
}
public static TgaFileHeader Parse(Span<byte> data) => MemoryMarshal.Cast<byte, TgaFileHeader>(data)[0];
public void WriteTo(Span<byte> buffer)
{

140
src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

@ -102,8 +102,7 @@ internal static class HorizontalPredictor
byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R);
byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G);
byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B);
var rgb = new Rgb24(r, g, b);
rowRgb[x].FromRgb24(rgb);
rowRgb[x] = new Rgb24(r, g, b);
}
}
}
@ -128,8 +127,7 @@ internal static class HorizontalPredictor
for (int x = rowL16.Length - 1; x >= 1; x--)
{
ushort val = (ushort)(rowL16[x].PackedValue - rowL16[x - 1].PackedValue);
rowL16[x].PackedValue = val;
rowL16[x].PackedValue = (ushort)(rowL16[x].PackedValue - rowL16[x - 1].PackedValue);
}
}
}
@ -181,13 +179,13 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort pixelValue = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort diff = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue);
offset += 2;
@ -200,13 +198,13 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort pixelValue = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort diff = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue);
offset += 2;
@ -225,13 +223,13 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint pixelValue = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint pixelValue = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint diff = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint diff = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue);
offset += 4;
@ -244,13 +242,13 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint pixelValue = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint pixelValue = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint diff = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint diff = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue);
offset += 4;
@ -278,8 +276,7 @@ internal static class HorizontalPredictor
r += pixel.R;
g += pixel.G;
b += pixel.B;
var rgb = new Rgb24(r, g, b);
pixel.FromRgb24(rgb);
pixel = new Rgb24(r, g, b);
}
}
}
@ -305,8 +302,7 @@ internal static class HorizontalPredictor
g += pixel.G;
b += pixel.B;
a += pixel.A;
var rgb = new Rgba32(r, g, b, a);
pixel.FromRgba32(rgb);
pixel = new Rgba32(r, g, b, a);
}
}
}
@ -321,29 +317,29 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaR = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaG = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaB = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b);
offset += 2;
@ -356,29 +352,29 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaR = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaG = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaB = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b);
offset += 2;
@ -397,37 +393,37 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort a = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
ushort a = TiffUtilities.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaR = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaG = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaB = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaA = TiffUtils.ConvertToUShortBigEndian(rowSpan);
ushort deltaA = TiffUtilities.ConvertToUShortBigEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a);
offset += 2;
@ -440,37 +436,37 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort a = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
ushort a = TiffUtilities.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaR = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaG = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaB = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaA = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
ushort deltaA = TiffUtilities.ConvertToUShortLittleEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a);
offset += 2;
@ -489,29 +485,29 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaR = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaG = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaB = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b);
offset += 4;
@ -524,29 +520,29 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaR = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaG = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaB = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b);
offset += 4;
@ -565,37 +561,37 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint a = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
uint a = TiffUtilities.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaR = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaG = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaB = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaA = TiffUtils.ConvertToUIntBigEndian(rowSpan);
uint deltaA = TiffUtilities.ConvertToUIntBigEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a);
offset += 4;
@ -608,37 +604,37 @@ internal static class HorizontalPredictor
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint a = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
uint a = TiffUtilities.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaR = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaG = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaB = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaA = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
uint deltaA = TiffUtilities.ConvertToUIntLittleEndian(rowSpan);
a += deltaA;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a);
offset += 4;

10
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class BlackIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -32,9 +33,8 @@ internal class BlackIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
L16 l16 = TiffUtilities.L16Default;
TPixel color = TPixel.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)
@ -44,10 +44,10 @@ internal class BlackIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort intensity = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
pixelRow[x] = TPixel.FromL16(new(intensity));
}
}
else

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs

@ -19,11 +19,9 @@ internal class BlackIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
nuint offset = 0;
TPixel colorBlack = default;
TPixel colorWhite = default;
TPixel colorBlack = TPixel.FromRgba32(Color.Black.ToPixel<Rgba32>());
TPixel colorWhite = TPixel.FromRgba32(Color.White.ToPixel<Rgba32>());
colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
ref byte dataRef = ref MemoryMarshal.GetReference(data);
for (nuint y = (uint)top; y < (uint)(top + height); y++)
{

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 24-bit grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class BlackIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,8 +25,6 @@ internal class BlackIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -40,10 +38,10 @@ internal class BlackIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer);
uint intensity = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(intensity);
}
}
else
@ -51,10 +49,10 @@ internal class BlackIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint intensity = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(intensity);
}
}
}

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 32-bit float grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -24,8 +25,6 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default;
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int offset = 0;
@ -41,9 +40,7 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
float intensity = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f));
}
}
else
@ -53,9 +50,7 @@ internal class BlackIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
float intensity = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f));
}
}
}

13
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation for 32-bit grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class BlackIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,9 +25,6 @@ internal class BlackIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -36,20 +33,20 @@ internal class BlackIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint intensity = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(intensity);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint intensity = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(intensity);
}
}
}

25
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs

@ -9,47 +9,30 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class BlackIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
bool isOddWidth = (width & 1) == 1;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixelRowSpan[x++] = color;
byte intensity2 = (byte)((byteData & 0x0F) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixelRowSpan[x++] = color;
pixelRowSpan[x++] = TPixel.FromL8(new((byte)(((byteData & 0xF0) >> 4) * 17)));
pixelRowSpan[x++] = TPixel.FromL8(new((byte)((byteData & 0x0F) * 17)));
}
if (isOddWidth)
{
byte byteData = data[offset++];
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixelRowSpan[left + width - 1] = color;
pixelRowSpan[left + width - 1] = TPixel.FromL8(new((byte)(((byteData & 0xF0) >> 4) * 17)));
}
}
}

13
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,25 +10,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public BlackIsZeroTiffColor(TiffBitsPerSample bitsPerSample)
{
this.bitsPerSample0 = bitsPerSample.Channel0;
this.factor = (1 << this.bitsPerSample0) - 1.0f;
this.factor = (1 << this.bitsPerSample0) - 1f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
BitReader bitReader = new(data);
for (int y = top; y < top + height; y++)
{
@ -38,9 +35,7 @@ internal class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = value / this.factor;
color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(intensity, intensity, intensity, 1f));
}
bitReader.NextRow();

15
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Memory;
@ -13,6 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class CieLabPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -23,22 +23,19 @@ internal class CieLabPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
Span<byte> l = data[0].GetSpan();
Span<byte> a = data[1].GetSpan();
Span<byte> b = data[2].GetSpan();
Span<byte> a = data[1].GetSpan();
Span<byte> l = data[0].GetSpan();
var color = default(TPixel);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
var lab = new CieLab((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]);
var rgb = ColorSpaceConverter.ToRgb(lab);
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
pixelRow[x] = color;
CieLab lab = new((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]);
Rgb rgb = ColorSpaceConverter.ToRgb(lab);
pixelRow[x] = TPixel.FromScaledVector4(new(rgb.R, rgb.G, rgb.B, 1.0f));
offset++;
}

10
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Memory;
@ -12,17 +11,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements decoding pixel data with photometric interpretation of type 'CieLab'.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class CieLabTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private static readonly ColorSpaceConverter ColorSpaceConverter = new();
private const float Inv255 = 1.0f / 255.0f;
private const float Inv255 = 1f / 255f;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -33,9 +31,7 @@ internal class CieLabTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float l = (data[offset] & 0xFF) * 100f * Inv255;
CieLab lab = new(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]);
Rgb rgb = ColorSpaceConverter.ToRgb(lab);
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(rgb.R, rgb.G, rgb.B, 1f));
offset += 3;
}

8
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CmykTiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.ColorSpaces.Conversion;
using SixLabors.ImageSharp.Memory;
@ -12,12 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
internal class CmykTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private const float Inv255 = 1 / 255.0f;
private const float Inv255 = 1f / 255f;
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -26,9 +24,7 @@ internal class CmykTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
Cmyk cmyk = new(data[offset] * Inv255, data[offset + 1] * Inv255, data[offset + 2] * Inv255, data[offset + 3] * Inv255);
Rgb rgb = ColorSpaceConverter.ToRgb(in cmyk);
color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(rgb.R, rgb.G, rgb.B, 1.0f));
offset += 4;
}

13
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -18,8 +18,11 @@ internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
private readonly TPixel[] palette;
private const float InvMax = 1.0f / 65535F;
private const float InvMax = 1f / 65535f;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteTiffColor{TPixel}"/> class.
/// </summary>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap)
@ -32,7 +35,7 @@ internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var bitReader = new BitReader(data);
BitReader bitReader = new(data);
for (int y = top; y < top + height; y++)
{
@ -49,7 +52,7 @@ internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount)
{
var palette = new TPixel[colorCount];
TPixel[] palette = new TPixel[colorCount];
const int rOffset = 0;
int gOffset = colorCount;
@ -60,7 +63,7 @@ internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float r = colorMap[rOffset + i] * InvMax;
float g = colorMap[gOffset + i] * InvMax;
float b = colorMap[bOffset + i] * InvMax;
palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f));
palette[i] = TPixel.FromScaledVector4(new(r, g, b, 1f));
}
return palette;

15
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,11 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 16 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
private readonly Configuration configuration;
/// <summary>
@ -32,10 +31,6 @@ internal class Rgb161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)
@ -46,14 +41,14 @@ internal class Rgb161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
pixelRow[x] = TPixel.FromRgb48(new(r, g, b));
}
}
else

22
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -26,10 +26,6 @@ internal class Rgb16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
@ -42,26 +38,26 @@ internal class Rgb16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
pixelRow[x] = TPixel.FromRgb48(new(r, g, b));
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
pixelRow[x] = TPixel.FromRgb48(new(r, g, b));
}
}
}

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 24 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,8 +25,6 @@ internal class Rgb242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -41,18 +39,18 @@ internal class Rgb242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
uint r = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
uint g = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
uint b = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b);
}
}
else
@ -60,18 +58,18 @@ internal class Rgb242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b);
}
}
}

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 24 bit.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -26,8 +26,6 @@ internal class Rgb24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -45,15 +43,15 @@ internal class Rgb24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
uint r = TiffUtilities.ConvertToUIntBigEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
uint g = TiffUtilities.ConvertToUIntBigEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
uint b = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b);
}
}
else
@ -61,15 +59,15 @@ internal class Rgb24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b);
}
}
}

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 32 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,8 +25,6 @@ internal class Rgb323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)
@ -37,32 +35,32 @@ internal class Rgb323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b);
}
}
}

21
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 32 bit.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -26,9 +26,6 @@ internal class Rgb32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
@ -41,26 +38,26 @@ internal class Rgb32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntBigEndian(redData.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntBigEndian(greenData.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntBigEndian(blueData.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntLittleEndian(redData.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntLittleEndian(greenData.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntLittleEndian(blueData.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b);
}
}
}

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs

@ -9,17 +9,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation for 4 bits per color channel images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgb444TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var bgra = default(Bgra4444);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y);
@ -31,9 +29,8 @@ internal class Rgb444TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
offset++;
byte b = (byte)((data[offset] & 0xF0) >> 4);
bgra.PackedValue = ToBgraPackedValue(b, g, r);
color.FromScaledVector4(bgra.ToScaledVector4());
pixelRow[x] = color;
Bgra4444 bgra = new() { PackedValue = ToBgraPackedValue(b, g, r) };
pixelRow[x] = TPixel.FromScaledVector4(bgra.ToScaledVector4());
if (x + 1 >= pixelRow.Length)
{
offset++;
@ -47,8 +44,7 @@ internal class Rgb444TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
offset++;
bgra.PackedValue = ToBgraPackedValue(b, g, r);
color.FromScaledVector4(bgra.ToScaledVector4());
pixelRow[x + 1] = color;
pixelRow[x + 1] = TPixel.FromScaledVector4(bgra.ToScaledVector4());
}
}
}

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,6 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 32 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -24,8 +24,6 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
@ -52,9 +50,7 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float b = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(r, g, b, 1f));
}
}
else
@ -70,9 +66,7 @@ internal class RgbFloat323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float b = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(r, g, b, 1f));
}
}
}

13
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class RgbPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -49,11 +49,9 @@ internal class RgbPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
/// <param name="height">The height of the image block.</param>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan());
var bBitReader = new BitReader(data[2].GetSpan());
BitReader rBitReader = new(data[0].GetSpan());
BitReader gBitReader = new(data[1].GetSpan());
BitReader bBitReader = new(data[2].GetSpan());
for (int y = top; y < top + height; y++)
{
@ -64,8 +62,7 @@ internal class RgbPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromScaledVector4(new Vector4(r, g, b, 1.0f));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(r, g, b, 1f));
}
rBitReader.NextRow();

8
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -40,9 +41,7 @@ internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
BitReader bitReader = new(data);
for (int y = top; y < top + height; y++)
{
@ -53,8 +52,7 @@ internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromScaledVector4(new Vector4(r, g, b, 1.0f));
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new Vector4(r, g, b, 1f));
}
bitReader.NextRow();

23
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs

@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba16161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -29,8 +30,8 @@ internal class Rgba16161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
/// <param name="extraSamplesType">The type of the extra samples.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.configuration = configuration;
@ -42,15 +43,11 @@ internal class Rgba16161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : [];
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -59,18 +56,18 @@ internal class Rgba16161616TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
ushort a = TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorFromRgba64Premultiplied<TPixel>(r, g, b, a)
: TPixel.FromRgba64(new(r, g, b, a));
}
}
else

34
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -33,10 +33,6 @@ internal class Rgba16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
@ -51,32 +47,32 @@ internal class Rgba16PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortBigEndian(redData.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortBigEndian(greenData.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortBigEndian(blueData.Slice(offset, 2));
ushort a = TiffUtilities.ConvertToUShortBigEndian(alphaData.Slice(offset, 2));
offset += 2;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorFromRgba64Premultiplied<TPixel>(r, g, b, a)
: TPixel.FromRgba64(new(r, g, b, a));
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
ulong a = TiffUtils.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2));
ushort r = TiffUtilities.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ushort g = TiffUtilities.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ushort b = TiffUtilities.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
ushort a = TiffUtilities.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2));
offset += 2;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorFromRgba64Premultiplied<TPixel>(r, g, b, a)
: TPixel.FromRgba64(new(r, g, b, a));
}
}
}

33
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba24242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -32,9 +32,6 @@ internal class Rgba24242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
@ -51,24 +48,24 @@ internal class Rgba24242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
uint r = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
uint g = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
uint b = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
uint a = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo24BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b, a);
}
}
else
@ -76,24 +73,24 @@ internal class Rgba24242424TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint a = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo24BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b, a);
}
}
}

32
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -33,8 +33,6 @@ internal class Rgba24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -54,19 +52,19 @@ internal class Rgba24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntBigEndian(buffer);
uint r = TiffUtilities.ConvertToUIntBigEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntBigEndian(buffer);
uint g = TiffUtilities.ConvertToUIntBigEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntBigEndian(buffer);
uint b = TiffUtilities.ConvertToUIntBigEndian(buffer);
alphaData.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
uint a = TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo24BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b, a);
}
}
else
@ -74,19 +72,19 @@ internal class Rgba24PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
for (int x = 0; x < pixelRow.Length; x++)
{
redData.Slice(offset, 3).CopyTo(bufferSpan);
ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint r = TiffUtilities.ConvertToUIntLittleEndian(buffer);
greenData.Slice(offset, 3).CopyTo(bufferSpan);
ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint g = TiffUtilities.ConvertToUIntLittleEndian(buffer);
blueData.Slice(offset, 3).CopyTo(bufferSpan);
ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint b = TiffUtilities.ConvertToUIntLittleEndian(buffer);
alphaData.Slice(offset, 3).CopyTo(bufferSpan);
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
uint a = TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo24BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo24Bit<TPixel>(r, g, b, a);
}
}
}

33
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -32,9 +32,6 @@ internal class Rgba32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
@ -46,42 +43,42 @@ internal class Rgba32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint a = TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo32BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b, a);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint a = TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo32BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b, a);
}
}
}

34
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,11 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary>
@ -33,9 +32,6 @@ internal class Rgba32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
@ -50,32 +46,32 @@ internal class Rgba32PlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4));
ulong a = TiffUtils.ConvertToUIntBigEndian(alphaData.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntBigEndian(redData.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntBigEndian(greenData.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntBigEndian(blueData.Slice(offset, 4));
uint a = TiffUtilities.ConvertToUIntBigEndian(alphaData.Slice(offset, 4));
offset += 4;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo32BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b, a);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4));
ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4));
ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4));
ulong a = TiffUtils.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4));
uint r = TiffUtilities.ConvertToUIntLittleEndian(redData.Slice(offset, 4));
uint g = TiffUtilities.ConvertToUIntLittleEndian(greenData.Slice(offset, 4));
uint b = TiffUtilities.ConvertToUIntLittleEndian(blueData.Slice(offset, 4));
uint a = TiffUtilities.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4));
offset += 4;
pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
pixelRow[x] = hasAssociatedAlpha
? TiffUtilities.ColorScaleTo32BitPremultiplied<TPixel>(r, g, b, a)
: TiffUtilities.ColorScaleTo32Bit<TPixel>(r, g, b, a);
}
}
}

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs

@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class Rgba8888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -34,10 +35,8 @@ internal class Rgba8888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
int offset = 0;
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : [];
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,6 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -24,8 +24,6 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
@ -57,9 +55,7 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float a = BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(r, g, b, a);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(r, g, b, a));
}
}
else
@ -78,9 +74,7 @@ internal class RgbaFloat32323232TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float a = BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(r, g, b, a);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(r, g, b, a));
}
}
}

18
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs

@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class RgbaPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -59,13 +60,12 @@ internal class RgbaPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
/// <param name="height">The height of the image block.</param>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData;
var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan());
var bBitReader = new BitReader(data[2].GetSpan());
var aBitReader = new BitReader(data[3].GetSpan());
BitReader rBitReader = new(data[0].GetSpan());
BitReader gBitReader = new(data[1].GetSpan());
BitReader bBitReader = new(data[2].GetSpan());
BitReader aBitReader = new(data[3].GetSpan());
for (int y = top; y < top + height; y++)
{
@ -77,17 +77,15 @@ internal class RgbaPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor;
var vec = new Vector4(r, g, b, a);
Vector4 vector = new(r, g, b, a);
if (hasAssociatedAlpha)
{
color = TiffUtils.UnPremultiply(ref vec, color);
pixelRow[x] = TiffUtilities.UnPremultiply<TPixel>(ref vector);
}
else
{
color.FromScaledVector4(vec);
pixelRow[x] = TPixel.FromScaledVector4(vector);
}
pixelRow[x] = color;
}
rBitReader.NextRow();

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class RgbaTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -50,9 +51,7 @@ internal class RgbaTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
BitReader bitReader = new(data);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
@ -66,15 +65,14 @@ internal class RgbaTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor;
var vec = new Vector4(r, g, b, a);
Vector4 vector = new(r, g, b, a);
if (hasAssociatedAlpha)
{
pixelRow[x] = TiffUtils.UnPremultiply(ref vec, color);
pixelRow[x] = TiffUtilities.UnPremultiply<TPixel>(ref vector);
}
else
{
color.FromScaledVector4(vec);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(vector);
}
}

14
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,10 +25,6 @@ internal class WhiteIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -37,20 +33,20 @@ internal class WhiteIsZero16TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)));
ushort intensity = (ushort)(ushort.MaxValue - TiffUtilities.ConvertToUShortBigEndian(data.Slice(offset, 2)));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
pixelRow[x] = TPixel.FromL16(new(intensity));
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
ushort intensity = (ushort)(ushort.MaxValue - TiffUtilities.ConvertToUShortLittleEndian(data.Slice(offset, 2)));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color);
pixelRow[x] = TPixel.FromL16(new(intensity));
}
}
}

7
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -18,11 +19,9 @@ internal class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
nuint offset = 0;
var colorBlack = default(TPixel);
var colorWhite = default(TPixel);
TPixel colorBlack = TPixel.FromRgba32(Color.Black.ToPixel<Rgba32>());
TPixel colorWhite = TPixel.FromRgba32(Color.White.ToPixel<Rgba32>());
colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
ref byte dataRef = ref MemoryMarshal.GetReference(data);
for (nuint y = (uint)top; y < (uint)(top + height); y++)
{

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 24-bit grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,8 +25,6 @@ internal class WhiteIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
const uint maxValue = 0xFFFFFF;
@ -41,10 +39,10 @@ internal class WhiteIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(buffer);
uint intensity = maxValue - TiffUtilities.ConvertToUIntBigEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(intensity);
}
}
else
@ -52,10 +50,10 @@ internal class WhiteIsZero24TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
data.Slice(offset, 3).CopyTo(bufferSpan);
ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(buffer);
uint intensity = maxValue - TiffUtilities.ConvertToUIntLittleEndian(buffer);
offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo24Bit<TPixel>(intensity);
}
}
}

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit float grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -24,8 +25,6 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int offset = 0;
@ -41,9 +40,7 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
float intensity = 1.0f - BitConverter.ToSingle(buffer);
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new(intensity, intensity, intensity, 1f));
}
}
else
@ -53,9 +50,7 @@ internal class WhiteIsZero32FloatTiffColor<TPixel> : TiffBaseColorDecoder<TPixel
float intensity = 1.0f - BitConverter.ToSingle(data.Slice(offset, 4));
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
}
}
}

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,6 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit grayscale images.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -25,8 +25,6 @@ internal class WhiteIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
color.FromScaledVector4(Vector4.Zero);
const uint maxValue = 0xFFFFFFFF;
int offset = 0;
@ -37,20 +35,20 @@ internal class WhiteIsZero32TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
uint intensity = maxValue - TiffUtilities.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(intensity);
}
}
else
{
for (int x = 0; x < pixelRow.Length; x++)
{
ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
uint intensity = maxValue - TiffUtilities.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color);
pixelRow[x] = TiffUtilities.ColorScaleTo32Bit<TPixel>(intensity);
}
}
}

25
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs

@ -9,47 +9,30 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
bool isOddWidth = (width & 1) == 1;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixelRowSpan[x++] = color;
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixelRowSpan[x++] = color;
pixelRowSpan[x++] = TPixel.FromL8(new((byte)((15 - ((byteData & 0xF0) >> 4)) * 17)));
pixelRowSpan[x++] = TPixel.FromL8(new((byte)((15 - (byteData & 0x0F)) * 17)));
}
if (isOddWidth)
{
byte byteData = data[offset++];
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixelRowSpan[left + width - 1] = color;
pixelRowSpan[left + width - 1] = TPixel.FromL8(new((byte)((15 - ((byteData & 0xF0) >> 4)) * 17)));
}
}
}

8
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,24 +9,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
for (int x = 0; x < pixelRow.Length; x++)
{
byte intensity = (byte)(byte.MaxValue - data[offset++]);
pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color);
pixelRow[x] = TPixel.FromL8(new(intensity));
}
}
}

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs

@ -11,11 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public WhiteIsZeroTiffColor(TiffBitsPerSample bitsPerSample)
@ -27,9 +27,7 @@ internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
BitReader bitReader = new(data);
for (int y = top; y < top + height; y++)
{
@ -37,10 +35,8 @@ internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (value / this.factor);
color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixelRow[x] = color;
float intensity = 1f - (value / this.factor);
pixelRow[x] = TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f));
}
bitReader.NextRow();

12
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs

@ -11,11 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements decoding pixel data with photometric interpretation of type 'YCbCr' with the planar configuration.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly YCbCrConverter converter;
private readonly ushort[] ycbcrSubSampling;
public YCbCrPlanarTiffColor(Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling)
@ -36,13 +36,12 @@ internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], cbData, crData);
}
var color = default(TPixel);
int offset = 0;
int widthPadding = 0;
if (this.ycbcrSubSampling != null)
{
// Round to the next integer multiple of horizontalSubSampling.
widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[0]);
widthPadding = TiffUtilities.PaddingToNextInteger(width, this.ycbcrSubSampling[0]);
}
for (int y = top; y < top + height; y++)
@ -51,8 +50,7 @@ internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]);
color.FromRgba32(rgba);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromRgba32(rgba);
offset++;
}
@ -64,8 +62,8 @@ internal class YCbCrPlanarTiffColor<TPixel> : TiffBasePlanarColorDecoder<TPixel>
{
// If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively,
// then the source data will be padded.
width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling);
height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling);
width += TiffUtilities.PaddingToNextInteger(width, horizontalSubSampling);
height += TiffUtilities.PaddingToNextInteger(height, verticalSubSampling);
for (int row = height - 1; row >= 0; row--)
{

11
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
/// <summary>
/// Implements decoding pixel data with photometric interpretation of type 'YCbCr'.
/// </summary>
/// <typeparam name="TPixel">The type of pixel format.</typeparam>
internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
@ -50,13 +51,12 @@ internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
private void DecodeYCbCrData(Buffer2D<TPixel> pixels, int left, int top, int width, int height, ReadOnlySpan<byte> ycbcrData)
{
var color = default(TPixel);
int offset = 0;
int widthPadding = 0;
if (this.ycbcrSubSampling != null)
{
// Round to the next integer multiple of horizontalSubSampling.
widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[0]);
widthPadding = TiffUtilities.PaddingToNextInteger(width, this.ycbcrSubSampling[0]);
}
for (int y = top; y < top + height; y++)
@ -65,8 +65,7 @@ internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
for (int x = 0; x < pixelRow.Length; x++)
{
Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]);
color.FromRgba32(rgba);
pixelRow[x] = color;
pixelRow[x] = TPixel.FromRgba32(rgba);
offset += 3;
}
@ -78,8 +77,8 @@ internal class YCbCrTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
{
// If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively,
// then the source data will be padded.
width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling);
height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling);
width += TiffUtilities.PaddingToNextInteger(width, horizontalSubSampling);
height += TiffUtilities.PaddingToNextInteger(height, verticalSubSampling);
int blockWidth = width / horizontalSubSampling;
int blockHeight = height / verticalSubSampling;
int cbCrOffsetInBlock = horizontalSubSampling * verticalSubSampling;

6
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -163,8 +163,8 @@ internal class TiffDecoderCore : IImageDecoderInternals
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
List<ImageFrame<TPixel>> frames = new();
List<ImageFrameMetadata> framesMetadata = new();
List<ImageFrame<TPixel>> frames = [];
List<ImageFrameMetadata> framesMetadata = [];
try
{
this.inputStream = stream;
@ -221,7 +221,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
DirectoryReader reader = new(stream, this.configuration.MemoryAllocator);
IList<ExifProfile> directories = reader.Read();
List<ImageFrameMetadata> framesMetadata = new();
List<ImageFrameMetadata> framesMetadata = [];
foreach (ExifProfile dir in directories)
{
framesMetadata.Add(this.CreateFrameMetadata(dir));

120
src/ImageSharp/Formats/Tiff/Utils/TiffUtilities.cs

@ -0,0 +1,120 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Utils;
/// <summary>
/// Helper methods for TIFF decoding.
/// </summary>
internal static class TiffUtilities
{
private const float Scale24Bit = 1f / 0xFFFFFF;
private static readonly Vector4 Scale24BitVector = Vector128.Create(Scale24Bit, Scale24Bit, Scale24Bit, 1f).AsVector4();
private const float Scale32Bit = 1f / 0xFFFFFFFF;
private static readonly Vector4 Scale32BitVector = Vector128.Create(Scale32Bit, Scale32Bit, Scale32Bit, 1f).AsVector4();
public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0);
public static L16 L16Default { get; } = new(0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64Premultiplied<TPixel>(ushort r, ushort g, ushort b, ushort a)
where TPixel : unmanaged, IPixel<TPixel>
{
if (a == 0)
{
return TPixel.FromRgba64(default);
}
return TPixel.FromRgba64(new((ushort)(r / a), (ushort)(g / a), (ushort)(b / a), a));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(uint r, uint g, uint b)
where TPixel : unmanaged, IPixel<TPixel>
=> TPixel.FromScaledVector4(new Vector4(r, g, b, 1f) * Scale24BitVector);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(uint r, uint g, uint b, uint a)
where TPixel : unmanaged, IPixel<TPixel>
=> TPixel.FromScaledVector4(new Vector4(r, g, b, a) * Scale24Bit);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24BitPremultiplied<TPixel>(uint r, uint g, uint b, uint a)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
return UnPremultiply<TPixel>(ref colorVector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(uint r, uint g, uint b)
where TPixel : unmanaged, IPixel<TPixel>
=> TPixel.FromScaledVector4(new Vector4(r, g, b, 1f) * Scale32BitVector);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(uint r, uint g, uint b, uint a)
where TPixel : unmanaged, IPixel<TPixel>
=> TPixel.FromScaledVector4(new Vector4(r, g, b, a) * Scale32Bit);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32BitPremultiplied<TPixel>(uint r, uint g, uint b, uint a)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 vector = new Vector4(r, g, b, a) * Scale32Bit;
return UnPremultiply<TPixel>(ref vector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(uint intensity)
where TPixel : unmanaged, IPixel<TPixel>
=> TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale24BitVector);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(uint intensity)
where TPixel : unmanaged, IPixel<TPixel>
=> TPixel.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1f) * Scale32BitVector);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel UnPremultiply<TPixel>(ref Vector4 vector)
where TPixel : unmanaged, IPixel<TPixel>
{
Numerics.UnPremultiply(ref vector);
return TPixel.FromScaledVector4(vector);
}
/// <summary>
/// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value.
/// </summary>
/// <param name="valueToRoundUp">The width or height to round up.</param>
/// <param name="subSampling">The sub sampling.</param>
/// <returns>The padding.</returns>
public static int PaddingToNextInteger(int valueToRoundUp, int subSampling)
{
if (valueToRoundUp % subSampling == 0)
{
return 0;
}
return subSampling - (valueToRoundUp % subSampling);
}
}

176
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -1,176 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Utils;
/// <summary>
/// Helper methods for TIFF decoding.
/// </summary>
internal static class TiffUtils
{
private const float Scale24Bit = 1.0f / 0xFFFFFF;
private const float Scale32Bit = 1.0f / 0xFFFFFFFF;
public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0);
public static L16 L16Default { get; } = new(0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ConvertToUShortLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntBigEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertToUIntLittleEndian(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL8<TPixel>(L8 l8, byte intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
l8.PackedValue = intensity;
color.FromL8(l8);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgb64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
color.FromRgba64(rgba);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48);
color.FromRgba64(rgba);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64Premultiplied<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48);
var vec = rgba.ToVector4();
return UnPremultiply(ref vec, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f);
color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24BitPremultiplied<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
return UnPremultiply(ref colorVector, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f);
color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32BitPremultiplied<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
return UnPremultiply(ref colorVector, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
l16.PackedValue = intensity;
color.FromL16(l16);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f);
color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f);
color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel UnPremultiply<TPixel>(ref Vector4 vector, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Numerics.UnPremultiply(ref vector);
color.FromScaledVector4(vector);
return color;
}
/// <summary>
/// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value.
/// </summary>
/// <param name="valueToRoundUp">The width or height to round up.</param>
/// <param name="subSampling">The sub sampling.</param>
/// <returns>The padding.</returns>
public static int PaddingToNextInteger(int valueToRoundUp, int subSampling)
{
if (valueToRoundUp % subSampling == 0)
{
return 0;
}
return subSampling - (valueToRoundUp % subSampling);
}
}

26
src/ImageSharp/Formats/Webp/AlphaDecoder.cs

@ -6,7 +6,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Memory;
@ -311,32 +313,28 @@ internal class AlphaDecoder : IDisposable
private static void HorizontalUnfilter(Span<byte> prev, Span<byte> input, Span<byte> dst, int width)
{
if (Sse2.IsSupported)
if ((Sse2.IsSupported || AdvSimd.IsSupported) && width >= 9)
{
dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0]));
if (width <= 1)
{
return;
}
nuint i;
Vector128<int> last = Vector128<int>.Zero.WithElement(0, dst[0]);
ref byte srcRef = ref MemoryMarshal.GetReference(input);
ref byte dstRef = ref MemoryMarshal.GetReference(dst);
for (i = 1; i <= (uint)width - 8; i += 8)
{
Vector128<long> a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<byte> a1 = Sse2.Add(a0.AsByte(), last.AsByte());
Vector128<byte> a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1);
Vector128<byte> a3 = Sse2.Add(a1, a2);
Vector128<byte> a4 = Sse2.ShiftLeftLogical128BitLane(a3, 2);
Vector128<byte> a5 = Sse2.Add(a3, a4);
Vector128<byte> a6 = Sse2.ShiftLeftLogical128BitLane(a5, 4);
Vector128<byte> a7 = Sse2.Add(a5, a6);
Vector128<byte> a1 = a0.AsByte() + last.AsByte();
Vector128<byte> a2 = Vector128Utilities.ShiftLeftBytesInVector(a1, 1);
Vector128<byte> a3 = a1 + a2;
Vector128<byte> a4 = Vector128Utilities.ShiftLeftBytesInVector(a3, 2);
Vector128<byte> a5 = a3 + a4;
Vector128<byte> a6 = Vector128Utilities.ShiftLeftBytesInVector(a5, 4);
Vector128<byte> a7 = a5 + a6;
ref byte outputRef = ref Unsafe.Add(ref dstRef, i);
Unsafe.As<byte, Vector64<byte>>(ref outputRef) = a7.GetLower();
last = Sse2.ShiftRightLogical(a7.AsInt64(), 56).AsInt32();
last = Vector128.ShiftRightLogical(a7.AsInt64(), 56).AsInt32();
}
for (; i < (uint)width; ++i)

30
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -1,11 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
@ -88,7 +88,8 @@ internal abstract class BitWriterBase
/// <param name="iccProfile">The color profile.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
public static void WriteTrunksBeforeData(
/// <returns>A <see cref="WebpVp8X"/> or a default instance.</returns>
public static WebpVp8X WriteTrunksBeforeData(
Stream stream,
uint width,
uint height,
@ -102,16 +103,19 @@ internal abstract class BitWriterBase
RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
// Write VP8X, header if necessary.
WebpVp8X vp8x = default;
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;
if (isVp8X)
{
WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha, hasAnimation);
vp8x = WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha, hasAnimation);
if (iccProfile != null)
{
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Iccp, iccProfile.ToByteArray());
}
}
return vp8x;
}
/// <summary>
@ -124,10 +128,16 @@ internal abstract class BitWriterBase
/// Write the trunks after data trunk.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="vp8x">The VP8X chunk.</param>
/// <param name="updateVp8x">Whether to update the chunk.</param>
/// <param name="initialPosition">The initial position of the stream before encoding.</param>
/// <param name="exifProfile">The EXIF profile.</param>
/// <param name="xmpProfile">The XMP profile.</param>
public static void WriteTrunksAfterData(
Stream stream,
in WebpVp8X vp8x,
bool updateVp8x,
long initialPosition,
ExifProfile? exifProfile,
XmpProfile? xmpProfile)
{
@ -141,7 +151,7 @@ internal abstract class BitWriterBase
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Xmp, xmpProfile.Data);
}
RiffHelper.EndWriteRiffFile(stream, 4);
RiffHelper.EndWriteRiffFile(stream, in vp8x, updateVp8x, initialPosition);
}
/// <summary>
@ -157,7 +167,7 @@ internal abstract class BitWriterBase
/// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount)
{
WebpAnimationParameter chunk = new(background.ToBgra32().PackedValue, loopCount);
WebpAnimationParameter chunk = new(background.ToPixel<Bgra32>().PackedValue, loopCount);
chunk.WriteTo(stream);
}
@ -186,19 +196,21 @@ internal abstract class BitWriterBase
/// Writes a VP8X header to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="exifProfile">A exif profile or null, if it does not exist.</param>
/// <param name="xmpProfile">A XMP profile or null, if it does not exist.</param>
/// <param name="exifProfile">An EXIF profile or null, if it does not exist.</param>
/// <param name="xmpProfile">An XMP profile or null, if it does not exist.</param>
/// <param name="iccProfile">The color profile.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
protected static void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
protected static WebpVp8X WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
{
WebpVp8X chunk = new(hasAnimation, xmpProfile != null, exifProfile != null, hasAlpha, iccProfile != null, width, height);
chunk.Validate(MaxDimension, MaxCanvasPixels);
chunk.WriteTo(stream);
return chunk;
}
}

1
src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;

2
src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
internal readonly struct WebpFrameData

25
src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs

@ -1,11 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
internal readonly struct WebpVp8X
internal readonly struct WebpVp8X : IEquatable<WebpVp8X>
{
public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, bool hasIcc, uint width, uint height)
{
@ -53,6 +51,24 @@ internal readonly struct WebpVp8X
/// </summary>
public uint Height { get; }
public static bool operator ==(WebpVp8X left, WebpVp8X right) => left.Equals(right);
public static bool operator !=(WebpVp8X left, WebpVp8X right) => !(left == right);
public override bool Equals(object? obj) => obj is WebpVp8X x && this.Equals(x);
public bool Equals(WebpVp8X other)
=> this.HasAnimation == other.HasAnimation
&& this.HasXmp == other.HasXmp
&& this.HasExif == other.HasExif
&& this.HasAlpha == other.HasAlpha
&& this.HasIcc == other.HasIcc
&& this.Width == other.Width
&& this.Height == other.Height;
public override int GetHashCode()
=> HashCode.Combine(this.HasAnimation, this.HasXmp, this.HasExif, this.HasAlpha, this.HasIcc, this.Width, this.Height);
public void Validate(uint maxDimension, ulong maxCanvasPixels)
{
if (this.Width > maxDimension || this.Height > maxDimension)
@ -67,6 +83,9 @@ internal readonly struct WebpVp8X
}
}
public WebpVp8X WithAlpha(bool hasAlpha)
=> new(this.HasAnimation, this.HasXmp, this.HasExif, hasAlpha, this.HasIcc, this.Width, this.Height);
public void WriteTo(Stream stream)
{
byte flags = 0;

21
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Memory;
@ -237,7 +236,7 @@ internal class Vp8LEncoder : IDisposable
/// </summary>
public Vp8LHashChain HashChain { get; }
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bit-writer buffer to the stream.
@ -247,7 +246,8 @@ internal class Vp8LEncoder : IDisposable
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
BitWriterBase.WriteTrunksBeforeData(
// The alpha flag is updated following encoding.
WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData(
stream,
(uint)image.Width,
(uint)image.Height,
@ -262,9 +262,11 @@ internal class Vp8LEncoder : IDisposable
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
}
return vp8x;
}
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
public void EncodeFooter<TPixel>(Image<TPixel> image, in WebpVp8X vp8x, bool hasAlpha, Stream stream, long initialPosition)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bit-writer buffer to the stream.
@ -273,7 +275,9 @@ internal class Vp8LEncoder : IDisposable
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
bool updateVp8x = hasAlpha && vp8x != default;
WebpVp8X updated = updateVp8x ? vp8x.WithAlpha(true) : vp8x;
BitWriterBase.WriteTrunksAfterData(stream, in updated, updateVp8x, initialPosition, exifProfile, xmpProfile);
}
/// <summary>
@ -285,7 +289,8 @@ internal class Vp8LEncoder : IDisposable
/// <param name="frameMetadata">The frame metadata.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
public void Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
public bool Encode<TPixel>(ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, Stream stream, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
// Convert image pixels to bgra array.
@ -324,6 +329,8 @@ internal class Vp8LEncoder : IDisposable
{
RiffHelper.EndWriteChunk(stream, prevPosition);
}
return hasAlpha;
}
/// <summary>
@ -502,7 +509,7 @@ internal class Vp8LEncoder : IDisposable
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
/// <param name="pixels">The frame pixel buffer to convert.</param>
/// <returns>true, if the image is non opaque.</returns>
private bool ConvertPixelsToBgra<TPixel>(Buffer2DRegion<TPixel> pixels)
public bool ConvertPixelsToBgra<TPixel>(Buffer2DRegion<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
bool nonOpaque = false;

25
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -4,7 +4,6 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Memory;
@ -311,7 +310,7 @@ internal class Vp8Encoder : IDisposable
/// </summary>
private int MbHeaderLimit { get; }
public void EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlpha, bool hasAnimation)
public WebpVp8X EncodeHeader<TPixel>(Image<TPixel> image, Stream stream, bool hasAlpha, bool hasAnimation)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bitwriter buffer to the stream.
@ -321,7 +320,7 @@ internal class Vp8Encoder : IDisposable
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
BitWriterBase.WriteTrunksBeforeData(
WebpVp8X vp8x = BitWriterBase.WriteTrunksBeforeData(
stream,
(uint)image.Width,
(uint)image.Height,
@ -336,9 +335,11 @@ internal class Vp8Encoder : IDisposable
WebpMetadata webpMetadata = WebpCommonUtils.GetWebpMetadata(image);
BitWriterBase.WriteAnimationParameter(stream, webpMetadata.BackgroundColor, webpMetadata.RepeatCount);
}
return vp8x;
}
public void EncodeFooter<TPixel>(Image<TPixel> image, Stream stream)
public void EncodeFooter<TPixel>(Image<TPixel> image, in WebpVp8X vp8x, bool hasAlpha, Stream stream, long initialPosition)
where TPixel : unmanaged, IPixel<TPixel>
{
// Write bytes from the bitwriter buffer to the stream.
@ -347,7 +348,9 @@ internal class Vp8Encoder : IDisposable
ExifProfile exifProfile = this.skipMetadata ? null : metadata.ExifProfile;
XmpProfile xmpProfile = this.skipMetadata ? null : metadata.XmpProfile;
BitWriterBase.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
bool updateVp8x = hasAlpha && vp8x != default;
WebpVp8X updated = updateVp8x ? vp8x.WithAlpha(true) : vp8x;
BitWriterBase.WriteTrunksAfterData(stream, in updated, updateVp8x, initialPosition, exifProfile, xmpProfile);
}
/// <summary>
@ -358,9 +361,10 @@ internal class Vp8Encoder : IDisposable
/// <param name="stream">The stream to encode the image data to.</param>
/// <param name="bounds">The region of interest within the frame to encode.</param>
/// <param name="frameMetadata">The frame metadata.</param>
public void EncodeAnimation<TPixel>(ImageFrame<TPixel> frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata)
where TPixel : unmanaged, IPixel<TPixel> =>
this.Encode(stream, frame, bounds, frameMetadata, true, null);
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
public bool EncodeAnimation<TPixel>(ImageFrame<TPixel> frame, Stream stream, Rectangle bounds, WebpFrameMetadata frameMetadata)
where TPixel : unmanaged, IPixel<TPixel>
=> this.Encode(stream, frame, bounds, frameMetadata, true, null);
/// <summary>
/// Encodes the static image frame to the specified stream.
@ -385,7 +389,8 @@ internal class Vp8Encoder : IDisposable
/// <param name="frameMetadata">The frame metadata.</param>
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
/// <param name="image">The image to encode from.</param>
private void Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
/// <returns>A <see cref="bool"/> indicating whether the frame contains an alpha channel.</returns>
private bool Encode<TPixel>(Stream stream, ImageFrame<TPixel> frame, Rectangle bounds, WebpFrameMetadata frameMetadata, bool hasAnimation, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = bounds.Width;
@ -515,6 +520,8 @@ internal class Vp8Encoder : IDisposable
{
encodedAlphaData?.Dispose();
}
return hasAlpha;
}
/// <inheritdoc/>

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

Loading…
Cancel
Save