Browse Source

Merge branch 'main' into icc-color-conversion

pull/1567/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
6225db3071
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      .github/workflows/build-and-test.yml
  2. 2
      ci-test.ps1
  3. 2
      shared-infrastructure
  4. 5
      src/ImageSharp/Color/Color.cs
  5. 113
      src/ImageSharp/Compression/Zlib/Adler32.cs
  6. 105
      src/ImageSharp/Compression/Zlib/Crc32.cs
  7. 179
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  8. 90
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  9. 143
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  10. 22
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  11. 3
      src/ImageSharp/Formats/Gif/MetadataExtensions.cs
  12. 3
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  13. 34
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  14. 79
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  15. 29
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  16. 46
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  17. 308
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  18. 42
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  19. 41
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  20. 2
      src/ImageSharp/Image.Decode.cs
  21. 6
      src/ImageSharp/Image.cs
  22. 4
      src/ImageSharp/ImageInfo.cs
  23. 4
      src/ImageSharp/Image{TPixel}.cs
  24. 15
      src/ImageSharp/Metadata/ImageFrameMetadata.cs
  25. 39
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  26. 10
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  27. 13
      src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs
  28. 2
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  29. 5
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  30. 2
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  31. 9
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  32. 6
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  33. 1
      tests/Directory.Build.props
  34. 4
      tests/Directory.Build.targets
  35. 6
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  36. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  37. 36
      tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs
  38. 6
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  39. 6
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  40. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  41. 2
      tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs
  42. 6
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  43. 63
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  44. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  45. 7
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
  46. 12
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
  47. 4
      tests/ImageSharp.Tests/PixelFormats/A8Tests.cs
  48. 16
      tests/ImageSharp.Tests/TestImages.cs
  49. 31
      tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
  50. 28
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
  51. 4
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
  52. 56
      tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
  53. 5
      tests/ImageSharp.Tests/runtimeconfig.template.json
  54. 0
      tests/Images/Input/Bmp/9S.bmp
  55. 0
      tests/Images/Input/Bmp/DIAMOND.bmp
  56. 0
      tests/Images/Input/Bmp/GMARBLE.bmp
  57. 0
      tests/Images/Input/Bmp/PINES.bmp
  58. 0
      tests/Images/Input/Bmp/SKATER.bmp
  59. 0
      tests/Images/Input/Bmp/SPADE.bmp
  60. 0
      tests/Images/Input/Bmp/SUNFLOW.bmp
  61. 0
      tests/Images/Input/Bmp/WARPD.bmp

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

@ -9,10 +9,13 @@ on:
pull_request:
branches:
- main
types: [ labeled, opened, synchronize, reopened ]
jobs:
Build:
strategy:
matrix:
isARM:
- ${{ contains(github.event.pull_request.labels.*.name, 'arch:arm32') || contains(github.event.pull_request.labels.*.name, 'arch:arm64') }}
options:
- os: ubuntu-latest
framework: net7.0
@ -32,6 +35,12 @@ jobs:
sdk-preview: true
runtime: -x64
codecov: false
- os: buildjet-4vcpu-ubuntu-2204-arm
framework: net7.0
sdk: 7.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: ubuntu-latest
framework: net6.0
sdk: 6.0.x
@ -47,10 +56,23 @@ jobs:
sdk: 6.0.x
runtime: -x64
codecov: false
- os: buildjet-4vcpu-ubuntu-2204-arm
framework: net6.0
sdk: 6.0.x
runtime: -x64
codecov: false
exclude:
- isARM: false
options:
os: buildjet-4vcpu-ubuntu-2204-arm
runs-on: ${{matrix.options.os}}
steps:
- name: Install libgdi+, which is required for tests running on ubuntu
if: ${{ matrix.options.os == 'buildjet-4vcpu-ubuntu-2204-arm' }}
run: sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
- name: Git Config
shell: bash
run: |

2
ci-test.ps1

@ -33,5 +33,5 @@ elseif ($platform -eq '-x86' -and $targetFramework -match $netFxRegex) {
}
else {
dotnet test --no-build -c Release -f $targetFramework
dotnet test --no-build -c Release -f $targetFramework --blame --diag .tests\Images\ActualOutput\diaglog.txt
}

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 9a82679e92df9476725fd2a2038604fd412af56c
Subproject commit 6c52486c512357475cbb92bbb7c4c0af4d85b1db

5
src/ImageSharp/Color/Color.cs

@ -286,13 +286,10 @@ public readonly partial struct Color : IEquatable<Color>
/// Bulk converts a span of <see cref="Color"/> to a span of a specified <typeparamref name="TPixel"/> type.
/// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source color span.</param>
/// <param name="destination">The destination pixel span.</param>
[MethodImpl(InliningOptions.ShortMethod)]
#pragma warning disable RCS1163 // Unused parameter.
public static void ToPixel<TPixel>(Configuration configuration, ReadOnlySpan<Color> source, Span<TPixel> destination)
#pragma warning restore RCS1163 // Unused parameter.
public static void ToPixel<TPixel>(ReadOnlySpan<Color> source, Span<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Investigate bulk operations utilizing configuration parameter here.

113
src/ImageSharp/Compression/Zlib/Adler32.cs

@ -4,6 +4,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
#pragma warning disable IDE0007 // Use implicit type
@ -70,6 +71,11 @@ internal static class Adler32
return CalculateSse(adler, buffer);
}
if (AdvSimd.IsSupported)
{
return CalculateArm(adler, buffer);
}
return CalculateScalar(adler, buffer);
}
@ -95,7 +101,7 @@ internal static class Adler32
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
Vector128<short> ones = Vector128.Create((short)1);
while (blocks > 0)
{
@ -179,13 +185,13 @@ internal static class Adler32
byte* localBufferPtr = bufferPtr;
Vector256<byte> zero = Vector256<byte>.Zero;
var dot3v = Vector256.Create((short)1);
var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
Vector256<short> dot3v = Vector256.Create((short)1);
Vector256<sbyte> dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
var vs1 = Vector256.CreateScalar(s1);
var vs2 = Vector256.CreateScalar(s2);
Vector256<uint> vs1 = Vector256.CreateScalar(s1);
Vector256<uint> vs2 = Vector256.CreateScalar(s2);
while (length >= 32)
{
@ -243,6 +249,100 @@ internal static class Adler32
}
}
// Based on: https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateArm(uint adler, ReadOnlySpan<byte> buffer)
{
// Split Adler-32 into component sums.
uint s1 = adler & 0xFFFF;
uint s2 = (adler >> 16) & 0xFFFF;
uint length = (uint)buffer.Length;
// Process the data in blocks.
long blocks = length / BlockSize;
length -= (uint)(blocks * BlockSize);
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
byte* localBufferPtr = bufferPtr;
while (blocks != 0)
{
uint n = NMAX / BlockSize;
if (n > blocks)
{
n = (uint)blocks;
}
blocks -= n;
// Process n blocks of data. At most nMax data bytes can be
// processed before s2 must be reduced modulo Base.
Vector128<uint> vs1 = Vector128<uint>.Zero;
Vector128<uint> vs2 = vs1.WithElement(3, s1 * n);
Vector128<ushort> vColumnSum1 = Vector128<ushort>.Zero;
Vector128<ushort> vColumnSum2 = Vector128<ushort>.Zero;
Vector128<ushort> vColumnSum3 = Vector128<ushort>.Zero;
Vector128<ushort> vColumnSum4 = Vector128<ushort>.Zero;
do
{
// Load 32 input bytes.
Vector128<ushort> bytes1 = AdvSimd.LoadVector128(localBufferPtr).AsUInt16();
Vector128<ushort> bytes2 = AdvSimd.LoadVector128(localBufferPtr + 0x10).AsUInt16();
// Add previous block byte sum to v_s2.
vs2 = AdvSimd.Add(vs2, vs1);
// Horizontally add the bytes for s1.
vs1 = AdvSimd.AddPairwiseWideningAndAdd(
vs1.AsUInt32(),
AdvSimd.AddPairwiseWideningAndAdd(AdvSimd.AddPairwiseWidening(bytes1.AsByte()).AsUInt16(), bytes2.AsByte()));
// Vertically add the bytes for s2.
vColumnSum1 = AdvSimd.AddWideningLower(vColumnSum1, bytes1.GetLower().AsByte());
vColumnSum2 = AdvSimd.AddWideningLower(vColumnSum2, bytes1.GetUpper().AsByte());
vColumnSum3 = AdvSimd.AddWideningLower(vColumnSum3, bytes2.GetLower().AsByte());
vColumnSum4 = AdvSimd.AddWideningLower(vColumnSum4, bytes2.GetUpper().AsByte());
localBufferPtr += BlockSize;
}
while (--n > 0);
vs2 = AdvSimd.ShiftLeftLogical(vs2, 5);
// Multiply-add bytes by [ 32, 31, 30, ... ] for s2.
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetLower(), Vector64.Create((ushort)32, 31, 30, 29));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetUpper(), Vector64.Create((ushort)28, 27, 26, 25));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetLower(), Vector64.Create((ushort)24, 23, 22, 21));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetUpper(), Vector64.Create((ushort)20, 19, 18, 17));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetLower(), Vector64.Create((ushort)16, 15, 14, 13));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetUpper(), Vector64.Create((ushort)12, 11, 10, 9));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetLower(), Vector64.Create((ushort)8, 7, 6, 5));
vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetUpper(), Vector64.Create((ushort)4, 3, 2, 1));
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
Vector64<uint> sum1 = AdvSimd.AddPairwise(vs1.GetLower(), vs1.GetUpper());
Vector64<uint> sum2 = AdvSimd.AddPairwise(vs2.GetLower(), vs2.GetUpper());
Vector64<uint> s1s2 = AdvSimd.AddPairwise(sum1, sum2);
// Store the results.
s1 += AdvSimd.Extract(s1s2, 0);
s2 += AdvSimd.Extract(s1s2, 1);
// Reduce.
s1 %= BASE;
s2 %= BASE;
}
if (length > 0)
{
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
return s1 | (s2 << 16);
}
}
private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2)
{
if (length >= 16)
@ -286,7 +386,6 @@ internal static class Adler32
{
uint s1 = adler & 0xFFFF;
uint s2 = (adler >> 16) & 0xFFFF;
uint k;
fixed (byte* bufferPtr = buffer)
{
@ -295,7 +394,7 @@ internal static class Adler32
while (length > 0)
{
k = length < NMAX ? length : NMAX;
uint k = length < NMAX ? length : NMAX;
length -= k;
while (k >= 16)

105
src/ImageSharp/Compression/Zlib/Crc32.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using ArmCrc32 = System.Runtime.Intrinsics.Arm.Crc32;
namespace SixLabors.ImageSharp.Compression.Zlib;
@ -60,6 +61,16 @@ internal static partial class Crc32
return ~CalculateSse(~crc, buffer);
}
if (ArmCrc32.Arm64.IsSupported)
{
return ~CalculateArm64(~crc, buffer);
}
if (ArmCrc32.IsSupported)
{
return ~CalculateArm(~crc, buffer);
}
return ~CalculateScalar(~crc, buffer);
}
@ -187,6 +198,100 @@ internal static partial class Crc32
}
}
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateArm(uint crc, ReadOnlySpan<byte> buffer)
{
fixed (byte* bufferPtr = buffer)
{
byte* localBufferPtr = bufferPtr;
int len = buffer.Length;
while (len > 0 && ((ulong)localBufferPtr & 3) != 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}
uint* intBufferPtr = (uint*)localBufferPtr;
while (len >= 8 * sizeof(uint))
{
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
len -= 8 * sizeof(uint);
}
while (len >= sizeof(uint))
{
crc = ArmCrc32.ComputeCrc32(crc, *intBufferPtr++);
len -= sizeof(uint);
}
localBufferPtr = (byte*)intBufferPtr;
while (len > 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}
return crc;
}
}
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateArm64(uint crc, ReadOnlySpan<byte> buffer)
{
fixed (byte* bufferPtr = buffer)
{
byte* localBufferPtr = bufferPtr;
int len = buffer.Length;
while (len > 0 && ((ulong)localBufferPtr & 7) != 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}
ulong* longBufferPtr = (ulong*)localBufferPtr;
while (len >= 8 * sizeof(ulong))
{
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
len -= 8 * sizeof(ulong);
}
while (len >= sizeof(ulong))
{
crc = ArmCrc32.Arm64.ComputeCrc32(crc, *longBufferPtr++);
len -= sizeof(ulong);
}
localBufferPtr = (byte*)longBufferPtr;
while (len > 0)
{
crc = ArmCrc32.ComputeCrc32(crc, *localBufferPtr++);
len--;
}
return crc;
}
}
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static uint CalculateScalar(uint crc, ReadOnlySpan<byte> buffer)
{

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

@ -1,9 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
@ -58,20 +58,15 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// </summary>
private const int RleDelta = 0x02;
/// <summary>
/// The stream to decode from.
/// </summary>
private BufferedReadStream stream;
/// <summary>
/// The metadata.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// The bitmap specific metadata.
/// </summary>
private BmpMetadata bmpMetadata;
private BmpMetadata? bmpMetadata;
/// <summary>
/// The file header containing general information.
@ -126,7 +121,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
Image<TPixel>? image = null;
try
{
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
@ -142,24 +137,25 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3)
{
this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else
{
this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
}
else if (this.infoHeader.BitsPerPixel == 24)
{
this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else if (this.infoHeader.BitsPerPixel <= 8)
{
this.ReadRgbPalette(
stream,
pixels,
palette,
this.infoHeader.Width,
@ -172,19 +168,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
break;
case BmpCompression.RLE24:
this.ReadRle24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
break;
case BmpCompression.RLE8:
case BmpCompression.RLE4:
this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
this.ReadRle(stream, this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
break;
case BmpCompression.BitFields:
case BmpCompression.BI_ALPHABITFIELDS:
this.ReadBitFields(pixels, inverted);
this.ReadBitFields(stream, pixels, inverted);
break;
@ -250,14 +246,16 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// which will be used to determine which bits belong to that channel.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadBitFields<TPixel>(Buffer2D<TPixel> pixels, bool inverted)
private void ReadBitFields<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(
stream,
pixels,
this.infoHeader.Width,
this.infoHeader.Height,
@ -269,6 +267,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
else
{
this.ReadRgb32BitFields(
stream,
pixels,
this.infoHeader.Width,
this.infoHeader.Height,
@ -282,17 +281,17 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <summary>
/// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data.
/// Compressed RLE8 stream is uncompressed by <see cref="UncompressRle8(int, Span{byte}, Span{bool}, Span{bool})"/>
/// Compressed RLE4 stream is uncompressed by <see cref="UncompressRle4(int, Span{byte}, Span{bool}, Span{bool})"/>
/// Compressed RLE4 stream is uncompressed by <see cref="UncompressRle4(BufferedReadStream, int, Span{byte}, Span{bool}, Span{bool})"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="compression">The compression type. Either RLE4 or RLE8.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle<TPixel>(BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
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;
@ -305,11 +304,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
Span<byte> bufferSpan = buffer.Memory.Span;
if (compression is BmpCompression.RLE8)
{
this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
this.UncompressRle8(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
else
{
this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
this.UncompressRle4(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
}
for (int y = 0; y < height; y++)
@ -368,11 +367,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Looks up color values and builds the image from de-compressed RLE24.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
private void ReadRle24<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
@ -384,7 +384,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
Span<bool> undefinedPixelsSpan = undefinedPixels.Memory.Span;
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
this.UncompressRle24(stream, width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan);
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
@ -446,18 +446,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, the first byte is the length of the run and second byte contains two color indexes.
/// </remarks>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
/// <param name="undefinedPixels">Keeps track over skipped and therefore undefined pixels.</param>
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
private void UncompressRle4(int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
private void UncompressRle4(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
{
Span<byte> cmd = stackalloc byte[2];
int count = 0;
while (count < buffer.Length)
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
if (stream.Read(cmd, 0, cmd.Length) != 2)
{
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap.");
}
@ -478,8 +479,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
break;
case RleDelta:
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
int dx = stream.ReadByte();
int dy = stream.ReadByte();
count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels);
break;
@ -492,7 +493,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
byte[] run = new byte[bytesToRead];
this.stream.Read(run, 0, run.Length);
stream.Read(run, 0, run.Length);
int idx = 0;
for (int i = 0; i < max; i++)
@ -512,7 +513,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Absolute mode data is aligned to two-byte word-boundary.
int padding = bytesToRead & 1;
this.stream.Skip(padding);
stream.Skip(padding);
break;
}
@ -551,18 +552,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, the first byte is the length of the run and second byte is the color for the run.
/// </remarks>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
/// <param name="undefinedPixels">Keeps track of skipped and therefore undefined pixels.</param>
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
private void UncompressRle8(int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
private void UncompressRle8(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
{
Span<byte> cmd = stackalloc byte[2];
int count = 0;
while (count < buffer.Length)
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
if (stream.Read(cmd, 0, cmd.Length) != 2)
{
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap.");
}
@ -583,8 +585,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
break;
case RleDelta:
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
int dx = stream.ReadByte();
int dy = stream.ReadByte();
count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels);
break;
@ -596,7 +598,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
byte[] run = new byte[length];
this.stream.Read(run, 0, run.Length);
stream.Read(run, 0, run.Length);
run.AsSpan().CopyTo(buffer[count..]);
@ -605,7 +607,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Absolute mode data is aligned to two-byte word-boundary.
int padding = length & 1;
this.stream.Skip(padding);
stream.Skip(padding);
break;
}
@ -630,18 +632,19 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, the first byte is the length of the run and following three bytes are the color for the run.
/// </remarks>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
/// <param name="undefinedPixels">Keeps track of skipped and therefore undefined pixels.</param>
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
private void UncompressRle24(int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
private void UncompressRle24(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
{
Span<byte> cmd = stackalloc byte[2];
int uncompressedPixels = 0;
while (uncompressedPixels < buffer.Length)
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
if (stream.Read(cmd, 0, cmd.Length) != 2)
{
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap.");
}
@ -662,8 +665,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
break;
case RleDelta:
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
int dx = stream.ReadByte();
int dy = stream.ReadByte();
uncompressedPixels += RleSkipDelta(uncompressedPixels, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels);
break;
@ -675,7 +678,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
byte[] run = new byte[length * 3];
this.stream.Read(run, 0, run.Length);
stream.Read(run, 0, run.Length);
run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]);
@ -684,7 +687,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Absolute mode data is aligned to two-byte word-boundary.
int padding = run.Length & 1;
this.stream.Skip(padding);
stream.Skip(padding);
break;
}
@ -693,8 +696,8 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
int max = uncompressedPixels + cmd[0];
byte blueIdx = cmd[1];
byte greenIdx = (byte)this.stream.ReadByte();
byte redIdx = (byte)this.stream.ReadByte();
byte greenIdx = (byte)stream.ReadByte();
byte redIdx = (byte)stream.ReadByte();
int bufferIdx = uncompressedPixels * 3;
for (; uncompressedPixels < max; uncompressedPixels++)
@ -800,6 +803,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Reads the color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
@ -808,7 +812,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="bytesPerColorMapEntry">Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
/// the bytes per color palette entry's can be 3 bytes instead of 4.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
private void ReadRgbPalette<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
// Pixels per byte (bits per pixel).
@ -833,7 +837,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
if (this.stream.Read(rowSpan) == 0)
if (stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -861,6 +865,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Reads the 16 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
@ -868,7 +873,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="redMask">The bitmask for the red channel.</param>
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
private void ReadRgb16<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 2);
@ -889,7 +894,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
for (int y = 0; y < height; y++)
{
if (this.stream.Read(bufferSpan) == 0)
if (stream.Read(bufferSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -935,11 +940,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Reads the 24 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb24<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
private void ReadRgb24<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 3);
@ -948,7 +954,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
for (int y = 0; y < height; y++)
{
if (this.stream.Read(rowSpan) == 0)
if (stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -967,11 +973,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Reads the 32 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32Fast<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
private void ReadRgb32Fast<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
@ -980,7 +987,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
for (int y = 0; y < height; y++)
{
if (this.stream.Read(rowSpan) == 0)
if (stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -1000,11 +1007,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
private void ReadRgb32Slow<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
@ -1012,7 +1020,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
using IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width);
Span<byte> rowSpan = row.GetSpan();
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
long currentPosition = stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
@ -1020,7 +1028,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
if (this.stream.Read(rowSpan) == 0)
if (stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -1049,14 +1057,14 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
if (this.stream.Read(rowSpan) == 0)
if (stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -1077,7 +1085,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
if (this.stream.Read(rowSpan) == 0)
if (stream.Read(rowSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -1105,6 +1113,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Decode an 32 Bit Bitmap containing a bitmask for each color channel.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
@ -1113,7 +1122,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
/// <param name="alphaMask">The bitmask for the alpha channel.</param>
private void ReadRgb32BitFields<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
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;
@ -1142,7 +1151,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
for (int y = 0; y < height; y++)
{
if (this.stream.Read(bufferSpan) == 0)
if (stream.Read(bufferSpan) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
}
@ -1228,10 +1237,13 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the <see cref="BmpInfoHeader"/> from the stream.
/// </summary>
private void ReadInfoHeader()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
[MemberNotNull(nameof(metadata))]
[MemberNotNull(nameof(bmpMetadata))]
private void ReadInfoHeader(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize];
long infoHeaderStart = this.stream.Position;
long infoHeaderStart = stream.Position;
// Resolution is stored in PPM.
this.metadata = new ImageMetadata
@ -1240,7 +1252,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
};
// Read the header size.
this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize);
stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize);
int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer);
if (headerSize is < BmpInfoHeader.CoreSize or > BmpInfoHeader.MaxHeaderSize)
@ -1249,7 +1261,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
}
// Read the rest of the header.
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2;
if (headerSize == BmpInfoHeader.CoreSize)
@ -1275,7 +1287,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
if (this.infoHeader.Compression == BmpCompression.BitFields)
{
byte[] bitfieldsBuffer = new byte[12];
this.stream.Read(bitfieldsBuffer, 0, 12);
stream.Read(bitfieldsBuffer, 0, 12);
Span<byte> data = bitfieldsBuffer.AsSpan();
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
@ -1284,7 +1296,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS)
{
byte[] bitfieldsBuffer = new byte[16];
this.stream.Read(bitfieldsBuffer, 0, 16);
stream.Read(bitfieldsBuffer, 0, 16);
Span<byte> data = bitfieldsBuffer.AsSpan();
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
@ -1324,12 +1336,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0)
{
// Read color profile.
long streamPosition = this.stream.Position;
long streamPosition = stream.Position;
byte[] iccProfileData = new byte[this.infoHeader.ProfileSize];
this.stream.Position = infoHeaderStart + this.infoHeader.ProfileData;
this.stream.Read(iccProfileData);
stream.Position = infoHeaderStart + this.infoHeader.ProfileData;
stream.Read(iccProfileData);
this.metadata.IccProfile = new IccProfile(iccProfileData);
this.stream.Position = streamPosition;
stream.Position = streamPosition;
}
}
else
@ -1358,10 +1370,11 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the <see cref="BmpFileHeader"/> from the stream.
/// </summary>
private void ReadFileHeader()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadFileHeader(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[BmpFileHeader.Size];
this.stream.Read(buffer, 0, BmpFileHeader.Size);
stream.Read(buffer, 0, BmpFileHeader.Size);
short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer);
switch (fileTypeMarker)
@ -1375,7 +1388,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Because we only decode the first bitmap in the array, the array header will be ignored.
// The bitmap file header of the first image follows the array header.
this.stream.Read(buffer, 0, BmpFileHeader.Size);
stream.Read(buffer, 0, BmpFileHeader.Size);
this.fileHeader = BmpFileHeader.Parse(buffer);
if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap)
{
@ -1398,12 +1411,12 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// <param name="palette">The color palette.</param>
/// <returns>Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
/// the bytes per color palette entry's can be 3 bytes instead of 4.</returns>
[MemberNotNull(nameof(metadata))]
[MemberNotNull(nameof(bmpMetadata))]
private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette)
{
this.stream = stream;
this.ReadFileHeader();
this.ReadInfoHeader();
this.ReadFileHeader(stream);
this.ReadInfoHeader(stream);
// see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517
// If the height is negative, then this is a Windows bitmap whose origin
@ -1451,13 +1464,13 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry;
}
palette = null;
palette = Array.Empty<byte>();
if (colorMapSizeBytes > 0)
{
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
if ((stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
{
BmpThrowHelper.ThrowInvalidImageContentException(
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");
@ -1465,21 +1478,21 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
palette = new byte[colorMapSizeBytes];
if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0)
if (stream.Read(palette, 0, colorMapSizeBytes) == 0)
{
BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!");
}
}
int skipAmount = this.fileHeader.Offset - (int)this.stream.Position;
if ((skipAmount + (int)this.stream.Position) > this.stream.Length)
int skipAmount = this.fileHeader.Offset - (int)stream.Position;
if ((skipAmount + (int)stream.Position) > stream.Length)
{
BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length.");
}
if (skipAmount > 0)
{
this.stream.Skip(skipAmount);
stream.Skip(skipAmount);
}
return bytesPerColorMapEntry;

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
@ -69,11 +68,6 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
/// <summary>
/// The color depth, in number of bits per pixel.
/// </summary>
@ -124,7 +118,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration();
Configuration configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
BmpMetadata bmpMetadata = metadata.GetBmpMetadata();
this.bitsPerPixel ??= bmpMetadata.BitsPerPixel;
@ -142,7 +136,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
_ => 0
};
byte[] iccProfileData = null;
byte[]? iccProfileData = null;
int iccProfileSize = 0;
if (metadata.IccProfile != null)
{
@ -165,7 +159,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer);
this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize);
this.WriteImage(stream, image);
this.WriteImage(configuration, stream, image);
WriteColorProfile(stream, iccProfileData, buffer);
stream.Flush();
@ -182,7 +176,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <param name="metadata">The metadata.</param>
/// <param name="iccProfileData">The icc profile data.</param>
/// <returns>The bitmap information header.</returns>
private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, short bpp, int bytesPerLine, ImageMetadata metadata, byte[] iccProfileData)
private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, short bpp, int bytesPerLine, ImageMetadata metadata, byte[]? iccProfileData)
{
int hResolution = 0;
int vResolution = 0;
@ -234,7 +228,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
infoHeader.Compression = BmpCompression.BitFields;
}
if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && metadata.IccProfile != null)
if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && iccProfileData != null)
{
infoHeader.ProfileSize = iccProfileData.Length;
infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED;
@ -250,7 +244,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// <param name="stream">The stream to write to.</param>
/// <param name="iccProfileData">The color profile data.</param>
/// <param name="buffer">The buffer.</param>
private static void WriteColorProfile(Stream stream, byte[] iccProfileData, Span<byte> buffer)
private static void WriteColorProfile(Stream stream, byte[]? iccProfileData, Span<byte> buffer)
{
if (iccProfileData != null)
{
@ -313,42 +307,43 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes the pixel data to the binary stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Stream stream, Image<TPixel> image)
private void WriteImage<TPixel>(Configuration configuration, Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer;
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32BitPixelData(stream, pixels);
this.Write32BitPixelData(configuration, stream, pixels);
break;
case BmpBitsPerPixel.Pixel24:
this.Write24BitPixelData(stream, pixels);
this.Write24BitPixelData(configuration, stream, pixels);
break;
case BmpBitsPerPixel.Pixel16:
this.Write16BitPixelData(stream, pixels);
this.Write16BitPixelData(configuration, stream, pixels);
break;
case BmpBitsPerPixel.Pixel8:
this.Write8BitPixelData(stream, image);
this.Write8BitPixelData(configuration, stream, image);
break;
case BmpBitsPerPixel.Pixel4:
this.Write4BitPixelData(stream, image);
this.Write4BitPixelData(configuration, stream, image);
break;
case BmpBitsPerPixel.Pixel2:
this.Write2BitPixelData(stream, image);
this.Write2BitPixelData(configuration, stream, image);
break;
case BmpBitsPerPixel.Pixel1:
this.Write1BitPixelData(stream, image);
this.Write1BitPixelData(configuration, stream, image);
break;
}
}
@ -360,9 +355,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 32-bit data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write32BitPixelData<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@ -372,7 +368,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
{
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
@ -384,9 +380,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 24-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write24BitPixelData<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
@ -398,7 +395,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
{
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
width);
@ -410,10 +407,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 16-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
private void Write16BitPixelData<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
int rowBytesWithoutPadding = width * 2;
@ -425,7 +423,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
pixelSpan.Length);
@ -438,9 +436,10 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
private void Write8BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
private void Write8BitPixelData<TPixel>(Configuration configuration, Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
@ -453,7 +452,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
}
else
{
this.Write8BitColor(stream, image, colorPalette);
this.Write8BitColor(configuration, stream, image, colorPalette);
}
}
@ -461,19 +460,20 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="Image{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitColor<TPixel>(Stream stream, Image<TPixel> image, Span<byte> colorPalette)
private void Write8BitColor<TPixel>(Configuration configuration, Stream stream, Image<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration);
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds);
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette);
for (int y = image.Height - 1; y >= 0; y--)
{
@ -529,12 +529,13 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
private void Write4BitPixelData<TPixel>(Configuration configuration, Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration, new QuantizerOptions()
{
MaxColors = 16
});
@ -546,7 +547,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.DangerousGetRowSpan(0);
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
@ -576,12 +577,13 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write2BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
private void Write2BitPixelData<TPixel>(Configuration configuration, Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration, new QuantizerOptions()
{
MaxColors = 4
});
@ -593,7 +595,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.DangerousGetRowSpan(0);
int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding;
@ -632,12 +634,13 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitPixelData<TPixel>(Stream stream, Image<TPixel> image)
private void Write1BitPixelData<TPixel>(Configuration configuration, Stream stream, Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration, new QuantizerOptions()
{
MaxColors = 2
});
@ -649,7 +652,7 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
WriteColorPalette(configuration, stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> quantizedPixelRow = quantized.DangerousGetRowSpan(0);
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
@ -681,14 +684,15 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
/// Writes the color palette to the stream. The color palette has 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="quantizedColorPalette">The color palette from the quantized image.</param>
/// <param name="colorPalette">A temporary byte span to write the color palette to.</param>
private void WriteColorPalette<TPixel>(Stream stream, ReadOnlySpan<TPixel> quantizedColorPalette, Span<byte> colorPalette)
private static void WriteColorPalette<TPixel>(Configuration configuration, Stream stream, ReadOnlySpan<TPixel> quantizedColorPalette, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
int quantizedColorBytes = quantizedColorPalette.Length * 4;
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast<byte, Bgra32>(colorPalette[..quantizedColorBytes]));
PixelOperations<TPixel>.Instance.ToBgra32(configuration, quantizedColorPalette, MemoryMarshal.Cast<byte, Bgra32>(colorPalette[..quantizedColorBytes]));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{

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

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@ -24,15 +24,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The currently loaded stream.
/// </summary>
private BufferedReadStream stream;
/// <summary>
/// The global color table.
/// </summary>
private IMemoryOwner<byte> globalColorTable;
private IMemoryOwner<byte>? globalColorTable;
/// <summary>
/// The area to restore.
@ -77,12 +72,12 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary>
/// The abstract metadata.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// The gif specific metadata.
/// </summary>
private GifMetadata gifMetadata;
private GifMetadata? gifMetadata;
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderCore"/> class.
@ -108,8 +103,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
where TPixel : unmanaged, IPixel<TPixel>
{
uint frameCount = 0;
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
Image<TPixel>? image = null;
ImageFrame<TPixel>? previousFrame = null;
try
{
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream);
@ -125,7 +120,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
break;
}
this.ReadFrame(ref image, ref previousFrame);
this.ReadFrame(stream, ref image, ref previousFrame);
// Reset per-frame state.
this.imageDescriptor = default;
@ -136,16 +131,16 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
this.ReadGraphicalControlExtension();
this.ReadGraphicalControlExtension(stream);
break;
case GifConstants.CommentLabel:
this.ReadComments();
this.ReadComments(stream);
break;
case GifConstants.ApplicationExtensionLabel:
this.ReadApplicationExtension();
this.ReadApplicationExtension(stream);
break;
case GifConstants.PlainTextLabel:
this.SkipBlock(); // Not supported by any known decoder.
SkipBlock(stream); // Not supported by any known decoder.
break;
}
}
@ -187,23 +182,23 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
{
if (nextFlag == GifConstants.ImageLabel)
{
this.ReadImageDescriptor();
this.ReadImageDescriptor(stream);
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
this.SkipBlock(); // Skip graphic control extension block
SkipBlock(stream); // Skip graphic control extension block
break;
case GifConstants.CommentLabel:
this.ReadComments();
this.ReadComments(stream);
break;
case GifConstants.ApplicationExtensionLabel:
this.ReadApplicationExtension();
this.ReadApplicationExtension(stream);
break;
case GifConstants.PlainTextLabel:
this.SkipBlock(); // Not supported by any known decoder.
SkipBlock(stream); // Not supported by any known decoder.
break;
}
}
@ -239,9 +234,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the graphic control extension.
/// </summary>
private void ReadGraphicalControlExtension()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadGraphicalControlExtension(BufferedReadStream stream)
{
int bytesRead = this.stream.Read(this.buffer, 0, 6);
int bytesRead = stream.Read(this.buffer, 0, 6);
if (bytesRead != 6)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
@ -253,9 +249,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the image descriptor.
/// </summary>
private void ReadImageDescriptor()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadImageDescriptor(BufferedReadStream stream)
{
int bytesRead = this.stream.Read(this.buffer, 0, 9);
int bytesRead = stream.Read(this.buffer, 0, 9);
if (bytesRead != 9)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
@ -271,9 +268,10 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the logical screen descriptor.
/// </summary>
private void ReadLogicalScreenDescriptor()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
{
int bytesRead = this.stream.Read(this.buffer, 0, 7);
int bytesRead = stream.Read(this.buffer, 0, 7);
if (bytesRead != 7)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
@ -286,84 +284,87 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// Reads the application extension block parsing any animation or XMP information
/// if present.
/// </summary>
private void ReadApplicationExtension()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadApplicationExtension(BufferedReadStream stream)
{
int appLength = this.stream.ReadByte();
int appLength = stream.ReadByte();
// If the length is 11 then it's a valid extension and most likely
// a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this.
long position = this.stream.Position;
long position = stream.Position;
if (appLength == GifConstants.ApplicationBlockSize)
{
this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
if (isXmp && !this.skipMetadata)
{
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator);
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
if (extension.Data.Length > 0)
{
this.metadata.XmpProfile = new XmpProfile(extension.Data);
this.metadata!.XmpProfile = new XmpProfile(extension.Data);
}
else
{
// Reset the stream position and continue.
this.stream.Position = position;
this.SkipBlock(appLength);
stream.Position = position;
SkipBlock(stream, appLength);
}
return;
}
int subBlockSize = this.stream.ReadByte();
int subBlockSize = stream.ReadByte();
// TODO: There's also a NETSCAPE buffer extension.
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
{
this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
this.stream.Skip(1); // Skip the terminator.
stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
stream.Skip(1); // Skip the terminator.
return;
}
// Could be something else not supported yet.
// Skip the subblock and terminator.
this.SkipBlock(subBlockSize);
SkipBlock(stream, subBlockSize);
return;
}
this.SkipBlock(appLength); // Not supported by any known decoder.
SkipBlock(stream, appLength); // Not supported by any known decoder.
}
/// <summary>
/// Skips over a block or reads its terminator.
/// <param name="blockSize">The length of the block to skip.</param>
/// </summary>
private void SkipBlock(int blockSize = 0)
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="blockSize">The length of the block to skip.</param>
private static void SkipBlock(BufferedReadStream stream, int blockSize = 0)
{
if (blockSize > 0)
{
this.stream.Skip(blockSize);
stream.Skip(blockSize);
}
int flag;
while ((flag = this.stream.ReadByte()) > 0)
while ((flag = stream.ReadByte()) > 0)
{
this.stream.Skip(flag);
stream.Skip(flag);
}
}
/// <summary>
/// Reads the gif comments.
/// </summary>
private void ReadComments()
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
private void ReadComments(BufferedReadStream stream)
{
int length;
var stringBuilder = new StringBuilder();
while ((length = this.stream.ReadByte()) != 0)
StringBuilder stringBuilder = new();
while ((length = stream.ReadByte()) != 0)
{
if (length > GifConstants.MaxCommentSubBlockLength)
{
@ -372,21 +373,21 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
if (this.skipMetadata)
{
this.stream.Seek(length, SeekOrigin.Current);
stream.Seek(length, SeekOrigin.Current);
continue;
}
using IMemoryOwner<byte> commentsBuffer = this.memoryAllocator.Allocate<byte>(length);
Span<byte> commentsSpan = commentsBuffer.GetSpan();
this.stream.Read(commentsSpan);
stream.Read(commentsSpan);
string commentPart = GifConstants.Encoding.GetString(commentsSpan);
stringBuilder.Append(commentPart);
}
if (stringBuilder.Length > 0)
{
this.gifMetadata.Comments.Add(stringBuilder.ToString());
this.gifMetadata!.Comments.Add(stringBuilder.ToString());
}
}
@ -394,15 +395,16 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// Reads an individual gif frame.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="image">The image to decode the information to.</param>
/// <param name="previousFrame">The previous frame.</param>
private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame)
private void ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame)
where TPixel : unmanaged, IPixel<TPixel>
{
this.ReadImageDescriptor();
this.ReadImageDescriptor(stream);
IMemoryOwner<byte> localColorTable = null;
Buffer2D<byte> indices = null;
IMemoryOwner<byte>? localColorTable = null;
Buffer2D<byte>? indices = null;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
@ -410,11 +412,11 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
{
int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.GetSpan());
stream.Read(localColorTable.GetSpan());
}
indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(indices);
this.ReadFrameIndices(stream, indices);
Span<byte> rawColorTable = default;
if (localColorTable != null)
@ -430,7 +432,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
// Skip any remaining blocks
this.SkipBlock();
SkipBlock(stream);
}
finally
{
@ -442,12 +444,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <summary>
/// Reads the frame indices marking the color to use for each pixel.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="indices">The 2D pixel buffer to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(Buffer2D<byte> indices)
private void ReadFrameIndices(BufferedReadStream stream, Buffer2D<byte> indices)
{
int minCodeSize = this.stream.ReadByte();
using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream);
int minCodeSize = stream.ReadByte();
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
lzwDecoder.DecodePixels(minCodeSize, indices);
}
@ -460,15 +463,15 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
where TPixel : unmanaged, IPixel<TPixel>
{
int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height;
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
ImageFrame<TPixel> prevFrame = null;
ImageFrame<TPixel> currentFrame = null;
ImageFrame<TPixel>? prevFrame = null;
ImageFrame<TPixel>? currentFrame = null;
ImageFrame<TPixel> imageFrame;
if (previousFrame is null)
@ -494,7 +497,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
prevFrame = previousFrame;
}
currentFrame = image.Frames.CreateFrame();
currentFrame = image!.Frames.CreateFrame();
this.SetFrameMetadata(currentFrame.Metadata, false);
@ -661,13 +664,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// Reads the logical screen descriptor and global color table blocks
/// </summary>
/// <param name="stream">The stream containing image data. </param>
[MemberNotNull(nameof(metadata))]
[MemberNotNull(nameof(gifMetadata))]
private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream)
{
this.stream = stream;
// Skip the identifier
this.stream.Skip(6);
this.ReadLogicalScreenDescriptor();
stream.Skip(6);
this.ReadLogicalScreenDescriptor(stream);
ImageMetadata meta = new();

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Runtime.CompilerServices;
@ -93,7 +92,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;
// Quantize the image returning a palette.
IndexedImageFrame<TPixel> quantized;
IndexedImageFrame<TPixel>? quantized;
using (IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration))
{
if (useGlobalTable)
@ -129,7 +128,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
this.WriteComments(gifMetadata, stream);
// Write application extensions.
XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
XmpProfile? xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
}
@ -152,8 +151,8 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
// Gather the metadata for this frame.
ImageFrame<TPixel> frame = image.Frames[i];
ImageFrameMetadata metadata = frame.Metadata;
bool hasMetadata = metadata.TryGetGifMetadata(out GifFrameMetadata frameMetadata);
bool useLocal = this.colorTableMode == GifColorTableMode.Local || (hasMetadata && frameMetadata.ColorTableMode == GifColorTableMode.Local);
bool hasMetadata = metadata.TryGetGifMetadata(out GifFrameMetadata? frameMetadata);
bool useLocal = this.colorTableMode == GifColorTableMode.Local || (hasMetadata && frameMetadata!.ColorTableMode == GifColorTableMode.Local);
if (!useLocal && !hasPaletteQuantizer && i > 0)
{
@ -164,11 +163,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
paletteQuantizer = new(this.configuration, this.quantizer.Options, palette);
}
this.EncodeFrame(stream, frame, i, useLocal, frameMetadata, ref quantized, ref paletteQuantizer);
this.EncodeFrame(stream, frame, i, useLocal, frameMetadata, ref quantized!, ref paletteQuantizer);
// Clean up for the next run.
quantized.Dispose();
quantized = null;
}
if (hasPaletteQuantizer)
@ -182,7 +180,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
ImageFrame<TPixel> frame,
int frameIndex,
bool useLocal,
GifFrameMetadata metadata,
GifFrameMetadata? metadata,
ref IndexedImageFrame<TPixel> quantized,
ref PaletteQuantizer<TPixel> paletteQuantizer)
where TPixel : unmanaged, IPixel<TPixel>
@ -193,7 +191,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
if (useLocal)
{
// Reassign using the current frame and details.
QuantizerOptions options = null;
QuantizerOptions? options = null;
int colorTableLength = metadata?.ColorTableLength ?? 0;
if (colorTableLength > 0)
{
@ -338,7 +336,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
/// <param name="frameCount">The frame count fo this image.</param>
/// <param name="repeatCount">The animated image repeat count.</param>
/// <param name="xmpProfile">The XMP metadata profile. Null if profile is not to be written.</param>
private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile xmpProfile)
private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile? xmpProfile)
{
// Application Extension: Loop repeat count.
if (frameCount > 1 && repeatCount != 1)
@ -350,7 +348,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
// Application Extension: XMP Profile.
if (xmpProfile != null)
{
GifXmpApplicationExtension xmpExtension = new(xmpProfile.Data);
GifXmpApplicationExtension xmpExtension = new(xmpProfile.Data!);
this.WriteExtension(xmpExtension, stream);
}
}
@ -439,7 +437,7 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension
{
IMemoryOwner<byte> owner = null;
IMemoryOwner<byte>? owner = null;
Span<byte> extensionBuffer;
int extensionSize = extension.ContentLength;

3
src/ImageSharp/Formats/Gif/MetadataExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata;
@ -37,5 +38,5 @@ public static partial class MetadataExtensions
/// <returns>
/// <see langword="true"/> if the gif frame metadata exists; otherwise, <see langword="false"/>.
/// </returns>
public static bool TryGetGifMetadata(this ImageFrameMetadata source, out GifFrameMetadata metadata) => source.TryGetFormatMetadata(GifFormat.Instance, out metadata);
public static bool TryGetGifMetadata(this ImageFrameMetadata source, [NotNullWhen(true)] out GifFrameMetadata? metadata) => source.TryGetFormatMetadata(GifFormat.Instance, out metadata);
}

3
src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -45,7 +44,7 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
/// <summary>
/// The <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// Initializes a new instance of the <see cref="PbmDecoderCore" /> class.

34
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -4,6 +4,7 @@
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.Formats.Png.Filters;
@ -35,6 +36,10 @@ internal static class AverageFilter
{
DecodeSse2(scanline, previousScanline);
}
else if (AdvSimd.IsSupported && bytesPerPixel is 4)
{
DecodeArm(scanline, previousScanline);
}
else
{
DecodeScalar(scanline, previousScanline, bytesPerPixel);
@ -48,7 +53,7 @@ internal static class AverageFilter
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
Vector128<byte> d = Vector128<byte>.Zero;
var ones = Vector128.Create((byte)1);
Vector128<byte> ones = Vector128.Create((byte)1);
int rb = scanline.Length;
nint offset = 1;
@ -75,6 +80,33 @@ internal static class AverageFilter
}
}
public static void DecodeArm(Span<byte> scanline, Span<byte> previousScanline)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
Vector64<byte> d = Vector64<byte>.Zero;
int rb = scanline.Length;
int offset = 1;
const int bytesPerBatch = 4;
while (rb >= bytesPerBatch)
{
ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
Vector64<byte> a = d;
Vector64<byte> b = Vector64.CreateScalar(Unsafe.As<byte, int>(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte();
d = Vector64.CreateScalar(Unsafe.As<byte, int>(ref scanRef)).AsByte();
Vector64<byte> avg = AdvSimd.FusedAddHalving(a, b);
d = AdvSimd.Add(d, avg);
Unsafe.As<byte, int>(ref scanRef) = d.AsInt32().ToScalar();
rb -= bytesPerBatch;
offset += bytesPerBatch;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DecodeScalar(Span<byte> scanline, Span<byte> previousScanline, int bytesPerPixel)
{

79
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -5,6 +5,7 @@ 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.Formats.Png.Filters;
@ -38,6 +39,10 @@ internal static class PaethFilter
{
DecodeSse41(scanline, previousScanline);
}
else if (AdvSimd.Arm64.IsSupported && bytesPerPixel is 4)
{
DecodeArm(scanline, previousScanline);
}
else
{
DecodeScalar(scanline, previousScanline, bytesPerPixel);
@ -99,6 +104,80 @@ internal static class PaethFilter
}
}
public static void DecodeArm(Span<byte> scanline, Span<byte> previousScanline)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
Vector128<byte> b = Vector128<byte>.Zero;
Vector128<byte> d = Vector128<byte>.Zero;
int rb = scanline.Length;
nint offset = 1;
const int bytesPerBatch = 4;
while (rb >= bytesPerBatch)
{
ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
Vector128<byte> c = b;
Vector128<byte> a = d;
b = AdvSimd.Arm64.ZipLow(
Vector128.CreateScalar(Unsafe.As<byte, int>(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(),
Vector128<byte>.Zero).AsByte();
d = AdvSimd.Arm64.ZipLow(
Vector128.CreateScalar(Unsafe.As<byte, int>(ref scanRef)).AsByte(),
Vector128<byte>.Zero).AsByte();
// (p-a) == (a+b-c - a) == (b-c)
Vector128<short> pa = AdvSimd.Subtract(b.AsInt16(), c.AsInt16());
// (p-b) == (a+b-c - b) == (a-c)
Vector128<short> pb = AdvSimd.Subtract(a.AsInt16(), c.AsInt16());
// (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c)
Vector128<short> pc = AdvSimd.Add(pa.AsInt16(), pb.AsInt16());
pa = AdvSimd.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */
pb = AdvSimd.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */
pc = AdvSimd.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */
Vector128<short> smallest = AdvSimd.Min(pc, AdvSimd.Min(pa, pb));
// Paeth breaks ties favoring a over b over c.
Vector128<byte> mask = BlendVariable(c, b, AdvSimd.CompareEqual(smallest, pb).AsByte());
Vector128<byte> nearest = BlendVariable(mask, a, AdvSimd.CompareEqual(smallest, pa).AsByte());
d = AdvSimd.Add(d, nearest);
Vector64<byte> e = AdvSimd.ExtractNarrowingSaturateUnsignedLower(d.AsInt16());
Unsafe.As<byte, int>(ref scanRef) = Vector128.Create(e, e).AsInt32().ToScalar();
rb -= bytesPerBatch;
offset += bytesPerBatch;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> BlendVariable(Vector128<byte> a, Vector128<byte> b, Vector128<byte> c)
{
// Equivalent of Sse41.BlendVariable:
// Blend packed 8-bit integers from a and b using mask, and store the results in
// dst.
//
// FOR j := 0 to 15
// i := j*8
// IF mask[i+7]
// dst[i+7:i] := b[i+7:i]
// ELSE
// dst[i+7:i] := a[i+7:i]
// FI
// ENDFOR
//
// Use a signed shift right to create a mask with the sign bit.
Vector128<short> mask = AdvSimd.ShiftRightArithmetic(c.AsInt16(), 7);
return AdvSimd.BitwiseSelect(mask, b.AsInt16(), a.AsInt16()).AsByte();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DecodeScalar(Span<byte> scanline, Span<byte> previousScanline, int bytesPerPixel)
{

29
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -5,6 +5,7 @@ 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.Formats.Png.Filters;
@ -29,6 +30,10 @@ internal static class SubFilter
{
DecodeSse2(scanline);
}
else if (AdvSimd.IsSupported && bytesPerPixel is 4)
{
DecodeArm(scanline);
}
else
{
DecodeScalar(scanline, bytesPerPixel);
@ -58,6 +63,30 @@ internal static class SubFilter
}
}
public static void DecodeArm(Span<byte> scanline)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
Vector64<byte> d = Vector64<byte>.Zero;
int rb = scanline.Length;
int offset = 1;
const int bytesPerBatch = 4;
while (rb >= bytesPerBatch)
{
ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
Vector64<byte> a = d;
d = Vector64.CreateScalar(Unsafe.As<byte, int>(ref scanRef)).AsByte();
d = AdvSimd.Add(d, a);
Unsafe.As<byte, int>(ref scanRef) = d.AsInt32().ToScalar();
rb -= bytesPerBatch;
offset += bytesPerBatch;
}
}
private static void DecodeScalar(Span<byte> scanline, int bytesPerPixel)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);

46
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -5,6 +5,7 @@ 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.Formats.Png.Filters;
@ -34,6 +35,10 @@ internal static class UpFilter
{
DecodeSse2(scanline, previousScanline);
}
else if (AdvSimd.IsSupported)
{
DecodeArm(scanline, previousScanline);
}
else
{
DecodeScalar(scanline, previousScanline);
@ -51,11 +56,10 @@ internal static class UpFilter
while (rb >= Vector256<byte>.Count)
{
ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
Vector256<byte> current = Unsafe.As<byte, Vector256<byte>>(ref scanRef);
Vector256<byte> prior = Unsafe.As<byte, Vector256<byte>>(ref scanRef);
Vector256<byte> up = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, offset));
Vector256<byte> sum = Avx2.Add(up, current);
Unsafe.As<byte, Vector256<byte>>(ref scanRef) = sum;
Unsafe.As<byte, Vector256<byte>>(ref scanRef) = Avx2.Add(up, prior);
offset += Vector256<byte>.Count;
rb -= Vector256<byte>.Count;
@ -82,11 +86,10 @@ internal static class UpFilter
while (rb >= Vector128<byte>.Count)
{
ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
Vector128<byte> current = Unsafe.As<byte, Vector128<byte>>(ref scanRef);
Vector128<byte> prior = Unsafe.As<byte, Vector128<byte>>(ref scanRef);
Vector128<byte> up = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref prevBaseRef, offset));
Vector128<byte> sum = Sse2.Add(up, current);
Unsafe.As<byte, Vector128<byte>>(ref scanRef) = sum;
Unsafe.As<byte, Vector128<byte>>(ref scanRef) = Sse2.Add(up, prior);
offset += Vector128<byte>.Count;
rb -= Vector128<byte>.Count;
@ -102,6 +105,37 @@ internal static class UpFilter
}
}
private static void DecodeArm(Span<byte> scanline, Span<byte> previousScanline)
{
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
// Up(x) + Prior(x)
int rb = scanline.Length;
nint offset = 1;
const int bytesPerBatch = 16;
while (rb >= bytesPerBatch)
{
ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset);
Vector128<byte> prior = Unsafe.As<byte, Vector128<byte>>(ref scanRef);
Vector128<byte> up = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref prevBaseRef, offset));
Unsafe.As<byte, Vector128<byte>>(ref scanRef) = AdvSimd.Add(prior, up);
offset += bytesPerBatch;
rb -= bytesPerBatch;
}
// Handle left over.
for (nint i = offset; i < scanline.Length; i++)
{
ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset);
byte above = Unsafe.Add(ref prevBaseRef, offset);
scan = (byte)(scan + above);
offset++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DecodeScalar(Span<byte> scanline, Span<byte> previousScanline)
{

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

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -29,12 +29,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// <summary>
/// The metadata.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// The tga specific metadata.
/// </summary>
private TgaMetadata tgaMetadata;
private TgaMetadata? tgaMetadata;
/// <summary>
/// The file header containing general information about the image.
@ -46,11 +46,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
private BufferedReadStream currentStream;
/// <summary>
/// Indicates whether there is a alpha channel present.
/// </summary>
@ -80,7 +75,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
try
{
TgaImageOrigin origin = this.ReadFileHeader(stream);
this.currentStream.Skip(this.fileHeader.IdLength);
stream.Skip(this.fileHeader.IdLength);
// Parse the color map, if present.
if (this.fileHeader.ColorMapType is not 0 and not 1)
@ -93,7 +88,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
throw new UnknownImageFormatException("Width or height cannot be 0");
}
var image = Image.CreateUninitialized<TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
Image<TPixel> image = Image.CreateUninitialized<TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.fileHeader.ColorMapType == 1)
@ -113,7 +108,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
using (IMemoryOwner<byte> palette = this.memoryAllocator.Allocate<byte>(colorMapSizeInBytes, AllocationOptions.Clean))
{
Span<byte> paletteSpan = palette.GetSpan();
int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
int bytesRead = stream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
if (bytesRead != colorMapSizeInBytes)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map");
@ -122,6 +117,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
if (this.fileHeader.ImageType == TgaImageType.RleColorMapped)
{
this.ReadPalettedRle(
stream,
this.fileHeader.Width,
this.fileHeader.Height,
pixels,
@ -132,6 +128,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
else
{
this.ReadPaletted(
stream,
this.fileHeader.Width,
this.fileHeader.Height,
pixels,
@ -148,7 +145,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
if (this.fileHeader.CMapLength > 0)
{
int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes);
stream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes);
}
switch (this.fileHeader.PixelDepth)
@ -156,11 +153,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
case 8:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin);
this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin);
}
else
{
this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
this.ReadMonoChrome(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@ -169,11 +166,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
case 16:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin);
this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin);
}
else
{
this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
this.ReadBgra16(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@ -181,11 +178,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
case 24:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin);
this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin);
}
else
{
this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
this.ReadBgr24(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@ -193,11 +190,11 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
case 32:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin);
this.ReadRle(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin);
}
else
{
this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
this.ReadBgra32(stream, this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@ -219,13 +216,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a uncompressed TGA image with a palette.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="palette">The color palette.</param>
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="origin">The image origin.</param>
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
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;
@ -243,14 +241,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
this.ReadPalettedBgra16Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
}
@ -261,14 +259,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgr24Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
}
@ -279,14 +277,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
ReadPalettedBgra32Pixel(stream, palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
}
@ -299,48 +297,47 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a run length encoded TGA image with a palette.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="palette">The color palette.</param>
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="origin">The image origin.</param>
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
private void ReadPalettedRle<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height, AllocationOptions.Clean))
{
TPixel color = default;
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1);
using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height, AllocationOptions.Clean);
TPixel color = default;
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle(stream, width, height, bufferSpan, bytesPerPixel: 1);
for (int y = 0; y < height; y++)
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int rowStartIdx = y * width;
for (int x = 0; x < width; x++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int rowStartIdx = y * width;
for (int x = 0; x < width; x++)
int idx = rowStartIdx + x;
switch (colorMapPixelSizeInBytes)
{
int idx = rowStartIdx + x;
switch (colorMapPixelSizeInBytes)
{
case 1:
color.FromL8(Unsafe.As<byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 2:
this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color);
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 4:
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
}
int newX = InvertX(x, width, origin);
pixelRow[newX] = color;
case 1:
color.FromL8(Unsafe.As<byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 2:
this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color);
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
case 4:
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
break;
}
int newX = InvertX(x, width, origin);
pixelRow[newX] = color;
}
}
}
@ -349,11 +346,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a uncompressed monochrome TGA image.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="origin">the image origin.</param>
private void ReadMonoChrome<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
private void ReadMonoChrome<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
bool invertX = InvertX(origin);
@ -366,7 +364,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
this.ReadL8Pixel(color, x, pixelSpan);
ReadL8Pixel(stream, color, x, pixelSpan);
}
}
@ -380,14 +378,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadL8Row(width, pixels, rowSpan, y);
this.ReadL8Row(stream, width, pixels, rowSpan, y);
}
}
else
{
for (int y = 0; y < height; y++)
{
this.ReadL8Row(width, pixels, rowSpan, y);
this.ReadL8Row(stream, width, pixels, rowSpan, y);
}
}
}
@ -396,11 +394,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a uncompressed TGA image where each pixels has 16 bit.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="origin">The image origin.</param>
private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
private void ReadBgra16<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
@ -417,7 +416,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2);
int bytesRead = stream.Read(this.scratchBuffer, 0, 2);
if (bytesRead != 2)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
@ -442,7 +441,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
else
{
int bytesRead = this.currentStream.Read(rowSpan);
int bytesRead = stream.Read(rowSpan);
if (bytesRead != rowSpan.Length)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
@ -473,11 +472,12 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a uncompressed TGA image where each pixels has 24 bit.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="origin">The image origin.</param>
private void ReadBgr24<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
private void ReadBgr24<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
bool invertX = InvertX(origin);
@ -490,7 +490,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
for (int x = width - 1; x >= 0; x--)
{
this.ReadBgr24Pixel(color, x, pixelSpan);
this.ReadBgr24Pixel(stream, color, x, pixelSpan);
}
}
@ -505,14 +505,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadBgr24Row(width, pixels, rowSpan, y);
this.ReadBgr24Row(stream, width, pixels, rowSpan, y);
}
}
else
{
for (int y = 0; y < height; y++)
{
this.ReadBgr24Row(width, pixels, rowSpan, y);
this.ReadBgr24Row(stream, width, pixels, rowSpan, y);
}
}
}
@ -521,15 +521,19 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a uncompressed TGA image where each pixels has 32 bit.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="origin">The image origin.</param>
private void ReadBgra32<TPixel>(int width, int height, Buffer2D<TPixel> pixels, TgaImageOrigin origin)
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);
if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX)
{
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0);
@ -539,14 +543,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadBgra32Row(width, pixels, rowSpan, y);
this.ReadBgra32Row(stream, width, pixels, rowSpan, y);
}
}
else
{
for (int y = 0; y < height; y++)
{
this.ReadBgra32Row(width, pixels, rowSpan, y);
this.ReadBgra32Row(stream, width, pixels, rowSpan, y);
}
}
@ -561,14 +565,14 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
{
for (int x = width - 1; x >= 0; x--)
{
this.ReadBgra32Pixel(x, color, pixelRow);
this.ReadBgra32Pixel(stream, x, color, pixelRow);
}
}
else
{
for (int x = 0; x < width; x++)
{
this.ReadBgra32Pixel(x, color, pixelRow);
this.ReadBgra32Pixel(stream, x, color, pixelRow);
}
}
}
@ -578,70 +582,72 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// Reads a run length encoded TGA image.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="origin">The image origin.</param>
private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, TgaImageOrigin origin)
private void ReadRle<TPixel>(BufferedReadStream stream, int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
Guard.NotNull(this.tgaMetadata);
byte alphaBits = this.tgaMetadata.AlphaChannelBits;
using (IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean))
using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(width * height * bytesPerPixel, AllocationOptions.Clean);
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle(stream, width, height, bufferSpan, bytesPerPixel);
for (int y = 0; y < height; y++)
{
Span<byte> bufferSpan = buffer.GetSpan();
this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
for (int y = 0; y < height; y++)
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
int idx = rowStartIdx + (x * bytesPerPixel);
switch (bytesPerPixel)
{
int idx = rowStartIdx + (x * bytesPerPixel);
switch (bytesPerPixel)
{
case 1:
color.FromL8(Unsafe.As<byte, L8>(ref bufferSpan[idx]));
break;
case 2:
if (!this.hasAlpha)
{
// Set alpha value to 1, to treat it as opaque for Bgra5551.
bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
}
if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref bufferSpan[idx]));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx]));
}
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break;
case 4:
if (this.hasAlpha)
{
color.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));
}
break;
}
case 1:
color.FromL8(Unsafe.As<byte, L8>(ref bufferSpan[idx]));
break;
case 2:
if (!this.hasAlpha)
{
// Set alpha value to 1, to treat it as opaque for Bgra5551.
bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
}
if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref bufferSpan[idx]));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx]));
}
int newX = InvertX(x, width, origin);
pixelRow[newX] = color;
break;
case 3:
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break;
case 4:
if (this.hasAlpha)
{
color.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));
}
break;
}
int newX = InvertX(x, width, origin);
pixelRow[newX] = color;
}
}
}
@ -658,10 +664,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
private void ReadL8Row<TPixel>(BufferedReadStream stream, int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = this.currentStream.Read(row);
int bytesRead = stream.Read(row);
if (bytesRead != row.Length)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
@ -672,19 +678,19 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadL8Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
private static void ReadL8Pixel<TPixel>(BufferedReadStream stream, TPixel color, int x, Span<TPixel> pixelSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
byte pixelValue = (byte)this.currentStream.ReadByte();
byte pixelValue = (byte)stream.ReadByte();
color.FromL8(Unsafe.As<byte, L8>(ref pixelValue));
pixelSpan[x] = color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgr24Pixel<TPixel>(TPixel color, int x, Span<TPixel> pixelSpan)
private void ReadBgr24Pixel<TPixel>(BufferedReadStream stream, TPixel color, int x, Span<TPixel> pixelSpan)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3);
int bytesRead = stream.Read(this.scratchBuffer, 0, 3);
if (bytesRead != 3)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel");
@ -695,10 +701,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
private void ReadBgr24Row<TPixel>(BufferedReadStream stream, int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = this.currentStream.Read(row);
int bytesRead = stream.Read(row);
if (bytesRead != row.Length)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
@ -709,25 +715,27 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgra32Pixel<TPixel>(int x, TPixel color, Span<TPixel> pixelRow)
private void ReadBgra32Pixel<TPixel>(BufferedReadStream stream, int x, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4);
int bytesRead = stream.Read(this.scratchBuffer, 0, 4);
if (bytesRead != 4)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel");
}
Guard.NotNull(this.tgaMetadata);
byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha));
pixelRow[x] = color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
private void ReadBgra32Row<TPixel>(BufferedReadStream stream, int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesRead = this.currentStream.Read(row);
int bytesRead = stream.Read(row);
if (bytesRead != row.Length)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row");
@ -738,10 +746,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra16Pixel<TPixel>(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, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
int colorIndex = stream.ReadByte();
if (colorIndex == -1)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
@ -768,10 +776,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgr24Pixel<TPixel>(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, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
int colorIndex = stream.ReadByte();
if (colorIndex == -1)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
@ -782,10 +790,10 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra32Pixel<TPixel>(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, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
int colorIndex = stream.ReadByte();
if (colorIndex == -1)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index");
@ -798,25 +806,26 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// <summary>
/// Produce uncompressed tga data from a run length encoded stream.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
/// <param name="bytesPerPixel">The bytes used per pixel.</param>
private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPerPixel)
private void UncompressRle(BufferedReadStream stream, int width, int height, Span<byte> buffer, int bytesPerPixel)
{
int uncompressedPixels = 0;
Span<byte> pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel);
int totalPixels = width * height;
while (uncompressedPixels < totalPixels)
{
byte runLengthByte = (byte)this.currentStream.ReadByte();
byte runLengthByte = (byte)stream.ReadByte();
// The high bit of a run length packet is set to 1.
int highBit = runLengthByte >> 7;
if (highBit == 1)
{
int runLength = runLengthByte & 127;
int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel);
int bytesRead = stream.Read(pixel, 0, bytesPerPixel);
if (bytesRead != bytesPerPixel)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
@ -836,7 +845,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
int bufferIdx = uncompressedPixels * bytesPerPixel;
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
{
int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel);
int bytesRead = stream.Read(pixel, 0, bytesPerPixel);
if (bytesRead != bytesPerPixel)
{
TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream");
@ -917,13 +926,13 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <returns>The image origin.</returns>
[MemberNotNull(nameof(metadata))]
[MemberNotNull(nameof(tgaMetadata))]
private TgaImageOrigin ReadFileHeader(BufferedReadStream stream)
{
this.currentStream = stream;
Span<byte> buffer = stackalloc byte[TgaFileHeader.Size];
this.currentStream.Read(buffer, 0, TgaFileHeader.Size);
stream.Read(buffer, 0, TgaFileHeader.Size);
this.fileHeader = TgaFileHeader.Parse(buffer);
this.metadata = new ImageMetadata();
this.tgaMetadata = this.metadata.GetTgaMetadata();
@ -939,7 +948,6 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
this.hasAlpha = alphaBits > 0;
// Bits 4 and 5 describe the image origin.
var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
return origin;
return (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
}
}

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
@ -23,11 +22,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
/// <summary>
/// Reusable buffer for writing data.
/// </summary>
@ -68,7 +62,6 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
TgaMetadata tgaMetadata = metadata.GetTgaMetadata();
this.bitsPerPixel ??= tgaMetadata.BitsPerPixel;
@ -124,7 +117,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
}
else
{
this.WriteImage(stream, image.Frames.RootFrame);
this.WriteImage(image.GetConfiguration(), stream, image.Frames.RootFrame);
}
stream.Flush();
@ -134,30 +127,31 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// Writes the pixel data to the binary stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image">
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
/// </param>
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void WriteImage<TPixel>(Configuration configuration, Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> pixels = image.PixelBuffer;
switch (this.bitsPerPixel)
{
case TgaBitsPerPixel.Pixel8:
this.Write8Bit(stream, pixels);
this.Write8Bit(configuration, stream, pixels);
break;
case TgaBitsPerPixel.Pixel16:
this.Write16Bit(stream, pixels);
this.Write16Bit(configuration, stream, pixels);
break;
case TgaBitsPerPixel.Pixel24:
this.Write24Bit(stream, pixels);
this.Write24Bit(configuration, stream, pixels);
break;
case TgaBitsPerPixel.Pixel32:
this.Write32Bit(stream, pixels);
this.Write32Bit(configuration, stream, pixels);
break;
}
}
@ -227,7 +221,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
case TgaBitsPerPixel.Pixel16:
Bgra5551 bgra5551 = new(color.ToVector4());
BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue);
BinaryPrimitives.WriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue);
stream.WriteByte(this.buffer[0]);
stream.WriteByte(this.buffer[1]);
@ -321,9 +315,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// Writes the 8bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write8Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 1);
@ -333,7 +328,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
{
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
@ -345,9 +340,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// Writes the 16bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write16Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 2);
@ -357,7 +353,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
{
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
@ -369,9 +365,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// Writes the 24bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write24Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 3);
@ -381,7 +378,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
{
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
@ -393,9 +390,10 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
/// Writes the 32bit pixels uncompressed to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The global configuration.</param>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write32Bit<TPixel>(Configuration configuration, Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@ -405,7 +403,7 @@ internal sealed class TgaEncoderCore : IImageEncoderInternals
{
Span<TPixel> pixelSpan = pixels.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);

41
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Runtime.CompilerServices;
@ -19,7 +18,7 @@ internal sealed class ChunkedMemoryStream : Stream
private readonly MemoryAllocator allocator;
// Data
private MemoryChunk memoryChunk;
private MemoryChunk? memoryChunk;
// The total number of allocated chunks
private int chunkCount;
@ -31,13 +30,13 @@ internal sealed class ChunkedMemoryStream : Stream
private bool isDisposed;
// Current chunk to write to
private MemoryChunk writeChunk;
private MemoryChunk? writeChunk;
// Offset into chunk to write to
private int writeOffset;
// Current chunk to read from
private MemoryChunk readChunk;
private MemoryChunk? readChunk;
// Offset into chunk to read from
private int readOffset;
@ -48,8 +47,6 @@ internal sealed class ChunkedMemoryStream : Stream
/// <param name="allocator">The memory allocator.</param>
public ChunkedMemoryStream(MemoryAllocator allocator)
{
Guard.NotNull(allocator, nameof(allocator));
this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
this.allocator = allocator;
}
@ -71,10 +68,10 @@ internal sealed class ChunkedMemoryStream : Stream
this.EnsureNotDisposed();
int length = 0;
MemoryChunk chunk = this.memoryChunk;
MemoryChunk? chunk = this.memoryChunk;
while (chunk != null)
{
MemoryChunk next = chunk.Next;
MemoryChunk? next = chunk.Next;
if (next != null)
{
length += chunk.Length;
@ -104,8 +101,8 @@ internal sealed class ChunkedMemoryStream : Stream
}
int pos = 0;
MemoryChunk chunk = this.memoryChunk;
while (chunk != this.readChunk)
MemoryChunk? chunk = this.memoryChunk;
while (chunk != this.readChunk && chunk is not null)
{
pos += chunk.Length;
chunk = chunk.Next;
@ -126,14 +123,14 @@ internal sealed class ChunkedMemoryStream : Stream
}
// Back up current position in case new position is out of range
MemoryChunk backupReadChunk = this.readChunk;
MemoryChunk? backupReadChunk = this.readChunk;
int backupReadOffset = this.readOffset;
this.readChunk = null;
this.readOffset = 0;
int leftUntilAtPos = (int)value;
MemoryChunk chunk = this.memoryChunk;
MemoryChunk? chunk = this.memoryChunk;
while (chunk != null)
{
if ((leftUntilAtPos < chunk.Length)
@ -365,6 +362,8 @@ internal sealed class ChunkedMemoryStream : Stream
this.writeOffset = 0;
}
Guard.NotNull(this.writeChunk);
Span<byte> chunkBuffer = this.writeChunk.Buffer.GetSpan();
int chunkSize = this.writeChunk.Length;
int count = buffer.Length;
@ -402,6 +401,8 @@ internal sealed class ChunkedMemoryStream : Stream
this.writeOffset = 0;
}
Guard.NotNull(this.writeChunk);
IMemoryOwner<byte> chunkBuffer = this.writeChunk.Buffer;
int chunkSize = this.writeChunk.Length;
@ -426,7 +427,7 @@ internal sealed class ChunkedMemoryStream : Stream
int length = (int)this.Length; // This will throw if stream is closed
byte[] copy = new byte[this.Length];
MemoryChunk backupReadChunk = this.readChunk;
MemoryChunk? backupReadChunk = this.readChunk;
int backupReadOffset = this.readOffset;
this.readChunk = this.memoryChunk;
@ -522,15 +523,14 @@ internal sealed class ChunkedMemoryStream : Stream
// available contiguous buffer size.
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
return new MemoryChunk
return new MemoryChunk(buffer)
{
Buffer = buffer,
Next = null,
Length = buffer.Length()
};
}
private static void ReleaseMemoryChunks(MemoryChunk chunk)
private static void ReleaseMemoryChunks(MemoryChunk? chunk)
{
while (chunk != null)
{
@ -555,11 +555,13 @@ internal sealed class ChunkedMemoryStream : Stream
{
private bool isDisposed;
public IMemoryOwner<byte> Buffer { get; set; }
public MemoryChunk(IMemoryOwner<byte> buffer) => this.Buffer = buffer;
public IMemoryOwner<byte> Buffer { get; }
public MemoryChunk Next { get; set; }
public MemoryChunk? Next { get; set; }
public int Length { get; set; }
public int Length { get; init; }
private void Dispose(bool disposing)
{
@ -570,7 +572,6 @@ internal sealed class ChunkedMemoryStream : Stream
this.Buffer.Dispose();
}
this.Buffer = null;
this.isDisposed = true;
}
}

2
src/ImageSharp/Image.Decode.cs

@ -91,7 +91,7 @@ public abstract partial class Image
ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager);
}
return format!;
return format;
}
/// <summary>

6
src/ImageSharp/Image.cs

@ -28,9 +28,9 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv
/// <param name="pixelType">The pixel type information.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="size">The size in px units.</param>
protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size)
protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata? metadata, Size size)
: base(pixelType, size, metadata)
=> this.configuration = configuration ?? Configuration.Default;
=> this.configuration = configuration;
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
@ -43,7 +43,7 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv
internal Image(
Configuration configuration,
PixelTypeInfo pixelType,
ImageMetadata metadata,
ImageMetadata? metadata,
int width,
int height)
: this(configuration, pixelType, metadata, new Size(width, height))

4
src/ImageSharp/ImageInfo.cs

@ -18,7 +18,7 @@ public class ImageInfo
/// <param name="width">The width of the image in px units.</param>
/// <param name="height">The height of the image in px units.</param>
/// <param name="metadata">The image metadata.</param>
public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata)
public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata? metadata)
: this(pixelType, new(width, height), metadata)
{
}
@ -29,7 +29,7 @@ public class ImageInfo
/// <param name="pixelType">The pixel type information.</param>
/// <param name="size">The size of the image in px units.</param>
/// <param name="metadata">The image metadata.</param>
public ImageInfo(PixelTypeInfo pixelType, Size size, ImageMetadata metadata)
public ImageInfo(PixelTypeInfo pixelType, Size size, ImageMetadata? metadata)
{
this.PixelType = pixelType;
this.Metadata = metadata ?? new ImageMetadata();

4
src/ImageSharp/Image{TPixel}.cs

@ -77,7 +77,7 @@ public sealed class Image<TPixel> : Image
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</param>
internal Image(Configuration configuration, int width, int height, ImageMetadata metadata)
internal Image(Configuration configuration, int width, int height, ImageMetadata? metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
=> this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
@ -128,7 +128,7 @@ public sealed class Image<TPixel> : Image
int width,
int height,
TPixel backgroundColor,
ImageMetadata metadata)
ImageMetadata? metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
=> this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);

15
src/ImageSharp/Metadata/ImageFrameMetadata.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -49,22 +48,22 @@ public sealed class ImageFrameMetadata : IDeepCloneable<ImageFrameMetadata>
/// <summary>
/// Gets or sets the Exif profile.
/// </summary>
public ExifProfile ExifProfile { get; set; }
public ExifProfile? ExifProfile { get; set; }
/// <summary>
/// Gets or sets the XMP profile.
/// </summary>
public XmpProfile XmpProfile { get; set; }
public XmpProfile? XmpProfile { get; set; }
/// <summary>
/// Gets or sets the ICC profile.
/// </summary>
public IccProfile IccProfile { get; set; }
public IccProfile? IccProfile { get; set; }
/// <summary>
/// Gets or sets the iptc profile.
/// </summary>
public IptcProfile IptcProfile { get; set; }
public IptcProfile? IptcProfile { get; set; }
/// <inheritdoc/>
public ImageFrameMetadata DeepClone() => new(this);
@ -83,7 +82,7 @@ public sealed class ImageFrameMetadata : IDeepCloneable<ImageFrameMetadata>
where TFormatMetadata : class
where TFormatFrameMetadata : class, IDeepCloneable
{
if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta))
if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
{
return (TFormatFrameMetadata)meta;
}
@ -107,11 +106,11 @@ public sealed class ImageFrameMetadata : IDeepCloneable<ImageFrameMetadata>
/// <returns>
/// <see langword="true"/> if the frame metadata exists for the specified key; otherwise, <see langword="false"/>.
/// </returns>
public bool TryGetFormatMetadata<TFormatMetadata, TFormatFrameMetadata>(IImageFormat<TFormatMetadata, TFormatFrameMetadata> key, out TFormatFrameMetadata metadata)
public bool TryGetFormatMetadata<TFormatMetadata, TFormatFrameMetadata>(IImageFormat<TFormatMetadata, TFormatFrameMetadata> key, out TFormatFrameMetadata? metadata)
where TFormatMetadata : class
where TFormatFrameMetadata : class, IDeepCloneable
{
if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta))
if (this.formatMetadata.TryGetValue(key, out IDeepCloneable? meta))
{
metadata = (TFormatFrameMetadata)meta;
return true;

39
src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs

@ -1,9 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
using SixLabors.ImageSharp.Metadata.Profiles.IPTC;
@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc;
/// </summary>
public sealed class IptcProfile : IDeepCloneable<IptcProfile>
{
private Collection<IptcValue> values;
private readonly Collection<IptcValue> values = new();
private const byte IptcTagMarkerByte = 0x1c;
@ -30,7 +30,7 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
public IptcProfile()
: this((byte[])null)
: this((byte[]?)null)
{
}
@ -38,7 +38,7 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
/// <param name="data">The byte array to read the iptc profile from.</param>
public IptcProfile(byte[] data)
public IptcProfile(byte[]? data)
{
this.Data = data;
this.Initialize();
@ -53,14 +53,9 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
{
Guard.NotNull(other, nameof(other));
if (other.values != null)
foreach (IptcValue value in other.Values)
{
this.values = new Collection<IptcValue>();
foreach (IptcValue value in other.Values)
{
this.values.Add(value.DeepClone());
}
this.values.Add(value.DeepClone());
}
if (other.Data != null)
@ -78,19 +73,12 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
/// <summary>
/// Gets the byte data of the IPTC profile.
/// </summary>
public byte[] Data { get; private set; }
public byte[]? Data { get; private set; }
/// <summary>
/// Gets the values of this iptc profile.
/// </summary>
public IEnumerable<IptcValue> Values
{
get
{
this.Initialize();
return this.values;
}
}
public IEnumerable<IptcValue> Values => this.values;
/// <inheritdoc/>
public IptcProfile DeepClone() => new(this);
@ -121,8 +109,6 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
/// <returns>True when the value was found and removed.</returns>
public bool RemoveValue(IptcTag tag)
{
this.Initialize();
bool removed = false;
for (int i = this.values.Count - 1; i >= 0; i--)
{
@ -144,8 +130,6 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
/// <returns>True when the value was found and removed.</returns>
public bool RemoveValue(IptcTag tag, string value)
{
this.Initialize();
bool removed = false;
for (int i = this.values.Count - 1; i >= 0; i--)
{
@ -312,13 +296,6 @@ public sealed class IptcProfile : IDeepCloneable<IptcProfile>
private void Initialize()
{
if (this.values != null)
{
return;
}
this.values = new Collection<IptcValue>();
if (this.Data == null || this.Data[0] != IptcTagMarkerByte)
{
return;

10
src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Text;
@ -22,10 +21,7 @@ public sealed class IptcValue : IDeepCloneable<IptcValue>
other.data.AsSpan().CopyTo(this.data);
}
if (other.Encoding != null)
{
this.Encoding = (Encoding)other.Encoding.Clone();
}
this.encoding = (Encoding)other.Encoding.Clone();
this.Tag = other.Tag;
this.Strict = other.Strict;
@ -133,7 +129,7 @@ public sealed class IptcValue : IDeepCloneable<IptcValue>
/// </summary>
/// <param name="obj">The object to compare this <see cref="IptcValue"/> with.</param>
/// <returns>True when the specified object is equal to the current <see cref="IptcValue"/>.</returns>
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
{
@ -148,7 +144,7 @@ public sealed class IptcValue : IDeepCloneable<IptcValue>
/// </summary>
/// <param name="other">The iptc value to compare this <see cref="IptcValue"/> with.</param>
/// <returns>True when the specified iptc value is equal to the current <see cref="IptcValue"/>.</returns>
public bool Equals(IptcValue other)
public bool Equals(IptcValue? other)
{
if (other is null)
{

13
src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics;
using System.Text;
using System.Xml.Linq;
@ -17,7 +17,7 @@ public sealed class XmpProfile : IDeepCloneable<XmpProfile>
/// Initializes a new instance of the <see cref="XmpProfile"/> class.
/// </summary>
public XmpProfile()
: this((byte[])null)
: this((byte[]?)null)
{
}
@ -25,7 +25,7 @@ public sealed class XmpProfile : IDeepCloneable<XmpProfile>
/// Initializes a new instance of the <see cref="XmpProfile"/> class.
/// </summary>
/// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
public XmpProfile(byte[] data) => this.Data = data;
public XmpProfile(byte[]? data) => this.Data = data;
/// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class
@ -42,15 +42,15 @@ public sealed class XmpProfile : IDeepCloneable<XmpProfile>
/// <summary>
/// Gets the XMP raw data byte array.
/// </summary>
internal byte[] Data { get; private set; }
internal byte[]? Data { get; private set; }
/// <summary>
/// Gets the raw XML document containing the XMP profile.
/// </summary>
/// <returns>The <see cref="XDocument"/></returns>
public XDocument GetDocument()
public XDocument? GetDocument()
{
byte[] byteArray = this.Data;
byte[]? byteArray = this.Data;
if (byteArray is null)
{
return null;
@ -77,6 +77,7 @@ public sealed class XmpProfile : IDeepCloneable<XmpProfile>
/// <returns>The <see cref="T:Byte[]"/></returns>
public byte[] ToByteArray()
{
Guard.NotNull(this.Data);
byte[] result = new byte[this.Data.Length];
this.Data.AsSpan().CopyTo(result);
return result;

2
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -35,7 +35,7 @@ internal sealed class PaletteDitherProcessor<TPixel> : ImageProcessor<TPixel>
ReadOnlySpan<Color> sourcePalette = definition.Palette.Span;
this.paletteOwner = this.Configuration.MemoryAllocator.Allocate<TPixel>(sourcePalette.Length);
Color.ToPixel(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span);
Color.ToPixel(sourcePalette, this.paletteOwner.Memory.Span);
this.ditherProcessor = new DitherProcessor(
this.Configuration,

5
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -26,7 +26,7 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
private readonly int maxColors;
private readonly int bitDepth;
private readonly Octree octree;
private IMemoryOwner<TPixel> paletteOwner;
private readonly IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette;
private EuclideanPixelMap<TPixel>? pixelMap;
private readonly bool isDithering;
@ -40,9 +40,6 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
[MethodImpl(InliningOptions.ShortMethod)]
public OctreeQuantizer(Configuration configuration, QuantizerOptions options)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;

2
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -51,7 +51,7 @@ public class PaletteQuantizer : IQuantizer
// Always use the palette length over options since the palette cannot be reduced.
TPixel[] palette = new TPixel[this.colorPalette.Length];
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
Color.ToPixel(this.colorPalette.Span, palette.AsSpan());
return new PaletteQuantizer<TPixel>(configuration, options, palette);
}
}

9
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

@ -17,10 +17,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
"Design",
"CA1001:Types that own disposable fields should be disposable",
Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")]
internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
internal readonly struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private EuclideanPixelMap<TPixel> pixelMap;
private readonly EuclideanPixelMap<TPixel> pixelMap;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct.
@ -65,8 +65,5 @@ internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
=> (byte)this.pixelMap.GetClosestColor(color, out match);
/// <inheritdoc/>
public void Dispose()
{
this.pixelMap.Dispose();
}
public void Dispose() => this.pixelMap.Dispose();
}

6
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -69,9 +69,9 @@ internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
/// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
private IMemoryOwner<Moment> momentsOwner;
private IMemoryOwner<byte> tagsOwner;
private IMemoryOwner<TPixel> paletteOwner;
private readonly IMemoryOwner<Moment> momentsOwner;
private readonly IMemoryOwner<byte> tagsOwner;
private readonly IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette;
private int maxColors;
private readonly Box[] colorCube;

1
tests/Directory.Build.props

@ -18,6 +18,7 @@
<PropertyGroup>
<CodeAnalysisRuleSet>..\ImageSharp.Tests.ruleset</CodeAnalysisRuleSet>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
<ItemGroup>

4
tests/Directory.Build.targets

@ -21,7 +21,7 @@
<PackageReference Update="BenchmarkDotNet" Version="0.13.0" />
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" Condition="'$(IsWindows)'=='true'" />
<PackageReference Update="Colourful" Version="3.0.0" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="11.1.2" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="12.2.2" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Moq" Version="4.14.6" />
@ -32,7 +32,7 @@
<PackageReference Update="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="5.8.64" Condition="'$(IsOSX)'=='true'" />
<PackageReference Update="SharpZipLib" Version="1.3.2" />
<PackageReference Update="SkiaSharp" Version="2.80.2" />
<PackageReference Update="System.Drawing.Common" Version="5.0.2" />
<PackageReference Update="System.Drawing.Common" Version="6.0.0" />
<PackageReference Update="System.IO.Compression" Version="4.3.0" />
</ItemGroup>

6
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
@ -49,8 +50,11 @@ public class GifDecoderTests
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
// Floating point differences result in minor pixel differences.
// Output have been manually verified.
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0001F),
ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0002F : 0.0001F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);

2
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -64,7 +64,7 @@ public partial class JpegEncoderTests
{
{ JpegEncodingColor.Luminance, 100, 0.0175f / 100 },
{ JpegEncodingColor.Luminance, 80, 0.6730f / 100 },
{ JpegEncodingColor.Luminance, 40, 0.9941f / 100 },
{ JpegEncodingColor.Luminance, 40, 0.9943f / 100 },
};
[Theory]

36
tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Tests.TestUtilities;
using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;
namespace SixLabors.ImageSharp.Tests.Formats.Png;
@ -13,10 +14,7 @@ public class Crc32Tests
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
public void ReturnsCorrectWhenEmpty(uint input)
{
Assert.Equal(input, Crc32.Calculate(input, default));
}
public void CalculateCrc_ReturnsCorrectResultWhenEmpty(uint input) => Assert.Equal(input, Crc32.Calculate(input, default));
[Theory]
[InlineData(0)]
@ -26,23 +24,43 @@ public class Crc32Tests
[InlineData(1024 + 15)]
[InlineData(2034)]
[InlineData(4096)]
public void MatchesReference(int length)
public void CalculateCrc_MatchesReference(int length) => CalculateCrcAndCompareToReference(length);
private static void CalculateCrcAndCompareToReference(int length)
{
var data = GetBuffer(length);
var crc = new SharpCrc32();
// arrange
byte[] data = GetBuffer(length);
SharpCrc32 crc = new();
crc.Update(data);
long expected = crc.Value;
// act
long actual = Crc32.Calculate(data);
// assert
Assert.Equal(expected, actual);
}
private static byte[] GetBuffer(int length)
{
var data = new byte[length];
byte[] data = new byte[length];
new Random(1).NextBytes(data);
return data;
}
[Fact]
public void RunCalculateCrcTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateCrcTest, HwIntrinsics.AllowAll);
[Fact]
public void RunCalculateCrcTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateCrcTest, HwIntrinsics.DisableHWIntrinsic);
private static void RunCalculateCrcTest()
{
int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 };
for (int i = 0; i < testData.Length; i++)
{
CalculateCrcAndCompareToReference(testData[i]);
}
}
}

6
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
@ -120,8 +121,11 @@ public partial class PngDecoderTests
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
// Floating point differences result in minor pixel differences.
// Output have been manually verified.
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0003F), // Magick decoder shows difference on Mac
ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0005F : 0.0003F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);

6
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tga;
@ -758,8 +759,11 @@ public class TgaDecoderTests
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
// Floating point differences result in minor pixel differences.
// Output have been manually verified.
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0001F),
ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0016F : 0.0001F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);

6
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
// ReSharper disable InconsistentNaming
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
@ -765,8 +766,11 @@ public class TiffDecoderTests : TiffDecoderBaseTester
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
// Floating point differences result in minor pixel differences.
// Output have been manually verified.
image.CompareToReferenceOutput(
ImageComparer.Exact,
TestEnvironment.OSArchitecture == Architecture.Arm64 ? ImageComparer.TolerantPercentage(0.0006F) : ImageComparer.Exact,
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);

2
tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs

@ -17,7 +17,7 @@ public class LosslessUtilsTests
float actual = LosslessUtils.CombinedShannonEntropy(x, y);
Assert.Equal(expected, actual, 5);
Assert.Equal(expected, actual, precision: 5);
}
private static void RunSubtractGreenTest()

6
tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.PixelFormats;
@ -365,8 +366,11 @@ public class WebpDecoderTests
FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}";
image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false);
// Floating point differences result in minor pixel differences.
// Output have been manually verified.
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.0007F),
ImageComparer.TolerantPercentage(TestEnvironment.OSArchitecture == Architecture.Arm64 ? 0.0156F : 0.0007F),
provider,
testOutputDetails: details,
appendPixelTypeToFileName: false);

63
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
@ -23,13 +24,13 @@ public class WebpEncoderTests
public void Encode_PreserveRatio<TPixel>(TestImageProvider<TPixel> provider, WebpFileFormatType expectedFormat)
where TPixel : unmanaged, IPixel<TPixel>
{
var options = new WebpEncoder();
WebpEncoder options = new();
using Image<TPixel> input = provider.GetImage();
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
input.Save(memoryStream, options);
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
using Image<Rgba32> output = Image.Load<Rgba32>(memoryStream);
ImageMetadata meta = output.Metadata;
WebpMetadata webpMetaData = meta.GetWebpMetadata();
@ -43,7 +44,7 @@ public class WebpEncoderTests
public void Encode_Lossless_WithPalette_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossless,
Quality = 100,
@ -61,7 +62,7 @@ public class WebpEncoderTests
public void Encode_Lossless_WithDifferentQuality_Works<TPixel>(TestImageProvider<TPixel> provider, int quality)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossless,
Quality = quality
@ -90,7 +91,7 @@ public class WebpEncoderTests
public void Encode_Lossless_WithDifferentMethodAndQuality_Works<TPixel>(TestImageProvider<TPixel> provider, WebpEncodingMethod method, int quality)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossless,
Method = method,
@ -107,14 +108,14 @@ public class WebpEncoderTests
public void Encode_Lossless_WithBestQuality_HasExpectedSize<TPixel>(TestImageProvider<TPixel> provider, int expectedBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossless,
Method = WebpEncodingMethod.BestQuality
};
using Image<TPixel> image = provider.GetImage();
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
image.Save(memoryStream, encoder);
Assert.Equal(memoryStream.Length, expectedBytes);
@ -130,7 +131,7 @@ public class WebpEncoderTests
public void Encode_Lossless_WithNearLosslessFlag_Works<TPixel>(TestImageProvider<TPixel> provider, int nearLosslessQuality)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossless,
NearLossless = true,
@ -154,7 +155,7 @@ public class WebpEncoderTests
public void Encode_Lossless_WithPreserveTransparentColor_Works<TPixel>(TestImageProvider<TPixel> provider, WebpEncodingMethod method)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossless,
Method = method,
@ -170,9 +171,9 @@ public class WebpEncoderTests
public void Encode_Lossless_OneByOnePixel_Works()
{
// Just make sure, encoding 1 pixel by 1 pixel does not throw an exception.
using var image = new Image<Rgba32>(1, 1);
var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless };
using (var memStream = new MemoryStream())
using Image<Rgba32> image = new(1, 1);
WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless };
using (MemoryStream memStream = new())
{
image.SaveAsWebp(memStream, encoder);
}
@ -185,7 +186,7 @@ public class WebpEncoderTests
public void Encode_Lossy_WithDifferentQuality_Works<TPixel>(TestImageProvider<TPixel> provider, int quality)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossy,
Quality = quality
@ -205,7 +206,7 @@ public class WebpEncoderTests
public void Encode_Lossy_WithDifferentFilterStrength_Works<TPixel>(TestImageProvider<TPixel> provider, int filterStrength)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossy,
FilterStrength = filterStrength
@ -225,7 +226,7 @@ public class WebpEncoderTests
public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works<TPixel>(TestImageProvider<TPixel> provider, int snsStrength)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossy,
SpatialNoiseShaping = snsStrength
@ -254,7 +255,7 @@ public class WebpEncoderTests
public void Encode_Lossy_WithDifferentMethodsAndQuality_Works<TPixel>(TestImageProvider<TPixel> provider, WebpEncodingMethod method, int quality)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossy,
Method = method,
@ -267,11 +268,15 @@ public class WebpEncoderTests
}
[Theory]
[WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, 64020)]
public void Encode_Lossy_WithAlpha_Works<TPixel>(TestImageProvider<TPixel> provider, int expectedFileSize)
[WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)]
public void Encode_Lossy_WithAlpha_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
// Floating point differences result in minor pixel differences affecting compression.
// Output have been manually verified.
int expectedFileSize = TestEnvironment.OSArchitecture == Architecture.Arm64 ? 64060 : 64020;
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossy,
UseAlphaCompression = false
@ -291,11 +296,15 @@ public class WebpEncoderTests
}
[Theory]
[WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, 16200)]
public void Encode_Lossy_WithAlphaUsingCompression_Works<TPixel>(TestImageProvider<TPixel> provider, int expectedFileSize)
[WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)]
public void Encode_Lossy_WithAlphaUsingCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new WebpEncoder()
// Floating point differences result in minor pixel differences affecting compression.
// Output have been manually verified.
int expectedFileSize = TestEnvironment.OSArchitecture == Architecture.Arm64 ? 16240 : 16200;
WebpEncoder encoder = new()
{
FileFormat = WebpFileFormatType.Lossy,
UseAlphaCompression = true
@ -322,7 +331,7 @@ public class WebpEncoderTests
{
using Image<TPixel> image = provider.GetImage();
var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless };
WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossless };
image.VerifyEncoder(provider, "webp", string.Empty, encoder);
}
@ -334,16 +343,16 @@ public class WebpEncoderTests
{
using Image<TPixel> image = provider.GetImage();
var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy };
WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy };
image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f));
}
public static void RunEncodeLossy_WithPeakImage()
{
var provider = TestImageProvider<Rgba32>.File(TestImageLossyFullPath);
TestImageProvider<Rgba32> provider = TestImageProvider<Rgba32>.File(TestImageLossyFullPath);
using Image<Rgba32> image = provider.GetImage();
var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy };
WebpEncoder encoder = new() { FileFormat = WebpFileFormatType.Lossy };
image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f));
}

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -4,7 +4,7 @@
<PropertyGroup>
<DebugSymbols>True</DebugSymbols>
<AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms>
<Platforms>AnyCPU;x64;x86;ARM64</Platforms>
<RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>

7
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Memory.Internals;
@ -66,6 +67,12 @@ public partial class UniformUnmanagedMemoryPoolTests
return;
}
if (TestEnvironment.OSArchitecture == Architecture.Arm64)
{
// Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342
return;
}
RemoteExecutor.Invoke(RunTest).Dispose();
static void RunTest()

12
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs

@ -261,6 +261,12 @@ public class UniformUnmanagedPoolMemoryAllocatorTests
return;
}
if (TestEnvironment.OSArchitecture == Architecture.Arm64)
{
// Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342
return;
}
if (!TestEnvironment.RunsOnCI)
{
// This may fail in local runs resulting in high memory load.
@ -323,6 +329,12 @@ public class UniformUnmanagedPoolMemoryAllocatorTests
return;
}
if (TestEnvironment.OSArchitecture == Architecture.Arm64)
{
// Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342
return;
}
if (!TestEnvironment.RunsOnCI)
{
// This may fail in local runs resulting in high memory load.

4
tests/ImageSharp.Tests/PixelFormats/A8Tests.cs

@ -65,7 +65,7 @@ public class A8Tests
Assert.Equal(0, actual.X);
Assert.Equal(0, actual.Y);
Assert.Equal(0, actual.Z);
Assert.Equal(.5F, actual.W, 2);
Assert.Equal(.5F, actual.W, precision: 2);
}
[Fact]
@ -81,7 +81,7 @@ public class A8Tests
Assert.Equal(0, actual.X);
Assert.Equal(0, actual.Y);
Assert.Equal(0, actual.Z);
Assert.Equal(.5F, actual.W, 2);
Assert.Equal(.5F, actual.W, precision: 2);
}
[Fact]

16
tests/ImageSharp.Tests/TestImages.cs

@ -388,14 +388,14 @@ public static class TestImages
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";
public const string Os2v2 = "Bmp/pal8os2v2.bmp";
public const string Os2BitmapArray = "Bmp/ba-bm.bmp";
public const string Os2BitmapArray9s = "Bmp/9S.BMP";
public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.BMP";
public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.BMP";
public const string Os2BitmapArraySkater = "Bmp/SKATER.BMP";
public const string Os2BitmapArraySpade = "Bmp/SPADE.BMP";
public const string Os2BitmapArraySunflower = "Bmp/SUNFLOW.BMP";
public const string Os2BitmapArrayWarpd = "Bmp/WARPD.BMP";
public const string Os2BitmapArrayPines = "Bmp/PINES.BMP";
public const string Os2BitmapArray9s = "Bmp/9S.bmp";
public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.bmp";
public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.bmp";
public const string Os2BitmapArraySkater = "Bmp/SKATER.bmp";
public const string Os2BitmapArraySpade = "Bmp/SPADE.bmp";
public const string Os2BitmapArraySunflower = "Bmp/SUNFLOW.bmp";
public const string Os2BitmapArrayWarpd = "Bmp/WARPD.bmp";
public const string Os2BitmapArrayPines = "Bmp/PINES.bmp";
public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp";
public const string Pal8Offset = "Bmp/pal8offs.bmp";
public const string OversizedPalette = "Bmp/pal8oversizepal.bmp";

31
tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities;
/// </summary>
public static class FeatureTestRunner
{
private static readonly char[] SplitChars = new[] { ',', ' ' };
private static readonly char[] SplitChars = { ',', ' ' };
/// <summary>
/// Allows the deserialization of parameters passed to the feature test.
@ -35,6 +35,7 @@ public static class FeatureTestRunner
/// Allows the deserialization of types implementing <see cref="IConvertible"/>
/// passed to the feature test.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="value">The string value to deserialize.</param>
/// <returns>The <typeparamref name="T"/> value.</returns>
public static T Deserialize<T>(string value)
@ -58,7 +59,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -99,7 +100,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -142,7 +143,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -185,7 +186,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -232,7 +233,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -276,7 +277,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -321,7 +322,7 @@ public static class FeatureTestRunner
foreach (KeyValuePair<HwIntrinsics, string> intrinsic in intrinsics.ToFeatureKeyValueCollection())
{
var processStartInfo = new ProcessStartInfo();
ProcessStartInfo processStartInfo = new();
if (intrinsic.Key != HwIntrinsics.AllowAll)
{
processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0";
@ -347,11 +348,11 @@ public static class FeatureTestRunner
internal static Dictionary<HwIntrinsics, string> ToFeatureKeyValueCollection(this HwIntrinsics intrinsics)
{
// Loop through and translate the given values into COMPlus equivaluents
var features = new Dictionary<HwIntrinsics, string>();
// Loop through and translate the given values into COMPlus equivalents
Dictionary<HwIntrinsics, string> features = new();
foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries))
{
var key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic);
HwIntrinsics key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic);
switch (intrinsic)
{
case nameof(HwIntrinsics.AllowAll):
@ -400,5 +401,11 @@ public enum HwIntrinsics
DisableBMI1 = 1 << 13,
DisableBMI2 = 1 << 14,
DisableLZCNT = 1 << 15,
AllowAll = 1 << 16
DisableArm64AdvSimd = 1 << 16,
DisableArm64Crc32 = 1 << 17,
DisableArm64Dp = 1 << 18,
DisableArm64Aes = 1 << 19,
DisableArm64Sha1 = 1 << 20,
DisableArm64Sha256 = 1 << 21,
AllowAll = 1 << 22
}

28
tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Globalization;
using System.Text;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -11,33 +12,36 @@ public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException
public ImageDifferenceIsOverThresholdException(IEnumerable<ImageSimilarityReport> reports)
: base("Image difference is over threshold!" + StringifyReports(reports))
{
this.Reports = reports.ToArray();
}
=> this.Reports = reports.ToArray();
private static string StringifyReports(IEnumerable<ImageSimilarityReport> reports)
{
var sb = new StringBuilder();
StringBuilder sb = new();
sb.Append(Environment.NewLine);
sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment OS : {0}", GetEnvironmentName());
sb.Append(Environment.NewLine);
sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment is CI : {0}", TestEnvironment.RunsOnCI);
sb.Append(Environment.NewLine);
sb.AppendFormat("Test Environment OS : {0}", GetEnvironmentName());
sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework);
sb.Append(Environment.NewLine);
sb.AppendFormat("Test Environment is CI : {0}", TestEnvironment.RunsOnCI);
sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment is Mono : {0}", TestEnvironment.IsMono);
sb.Append(Environment.NewLine);
sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework);
sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment OS Architecture : {0}", TestEnvironment.OSArchitecture);
sb.Append(Environment.NewLine);
sb.AppendFormat("Test Environment is Mono : {0}", TestEnvironment.IsMono);
sb.AppendFormat(CultureInfo.InvariantCulture, "Test Environment Process Architecture : {0}", TestEnvironment.ProcessArchitecture);
sb.Append(Environment.NewLine);
foreach (ImageSimilarityReport r in reports)
{
sb.AppendFormat("Report ImageFrame {0}: ", r.Index);
sb.Append(r);
sb.Append(Environment.NewLine);
sb.AppendFormat(CultureInfo.InvariantCulture, "Report ImageFrame {0}: ", r.Index)
.Append(r)
.Append(Environment.NewLine);
}
return sb.ToString();
@ -50,7 +54,7 @@ public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException
return "MacOS";
}
if (TestEnvironment.IsMacOS)
if (TestEnvironment.IsLinux)
{
return "Linux";
}

4
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs

@ -117,6 +117,10 @@ public static partial class TestEnvironment
internal static bool IsFramework => NetCoreVersion == null;
internal static Architecture OSArchitecture => RuntimeInformation.OSArchitecture;
internal static Architecture ProcessArchitecture => RuntimeInformation.ProcessArchitecture;
/// <summary>
/// A dummy operation to enforce the execution of the static constructor.
/// </summary>

56
tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs

@ -2,8 +2,10 @@
// Licensed under the Six Labors Split License.
using System.Numerics;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using Xunit.Abstractions;
using Aes = System.Runtime.Intrinsics.X86.Aes;
namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests;
@ -12,9 +14,9 @@ public class FeatureTestRunnerTests
public static TheoryData<HwIntrinsics, string[]> Intrinsics =>
new()
{
{ HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } },
{ HwIntrinsics.DisableHWIntrinsic, new string[] { "EnableHWIntrinsic" } },
{ HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } }
{ HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new[] { "EnableAES", "AllowAll" } },
{ HwIntrinsics.DisableHWIntrinsic, new[] { "EnableHWIntrinsic" } },
{ HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new[] { "EnableSSE42", "EnableAVX" } }
};
[Theory]
@ -101,6 +103,12 @@ public class FeatureTestRunnerTests
Assert.False(Bmi1.IsSupported);
Assert.False(Bmi2.IsSupported);
Assert.False(Lzcnt.IsSupported);
Assert.False(AdvSimd.IsSupported);
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
Assert.False(Crc32.IsSupported);
Assert.False(Dp.IsSupported);
Assert.False(Sha1.IsSupported);
Assert.False(Sha256.IsSupported);
break;
case HwIntrinsics.DisableSSE:
Assert.False(Sse.IsSupported);
@ -147,6 +155,24 @@ public class FeatureTestRunnerTests
case HwIntrinsics.DisableLZCNT:
Assert.False(Lzcnt.IsSupported);
break;
case HwIntrinsics.DisableArm64AdvSimd:
Assert.False(AdvSimd.IsSupported);
break;
case HwIntrinsics.DisableArm64Aes:
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
break;
case HwIntrinsics.DisableArm64Crc32:
Assert.False(Crc32.IsSupported);
break;
case HwIntrinsics.DisableArm64Dp:
Assert.False(Dp.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha1:
Assert.False(Sha1.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha256:
Assert.False(Sha256.IsSupported);
break;
}
}
@ -198,6 +224,12 @@ public class FeatureTestRunnerTests
Assert.False(Bmi1.IsSupported);
Assert.False(Bmi2.IsSupported);
Assert.False(Lzcnt.IsSupported);
Assert.False(AdvSimd.IsSupported);
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
Assert.False(Crc32.IsSupported);
Assert.False(Dp.IsSupported);
Assert.False(Sha1.IsSupported);
Assert.False(Sha256.IsSupported);
break;
case HwIntrinsics.DisableSSE:
Assert.False(Sse.IsSupported);
@ -244,6 +276,24 @@ public class FeatureTestRunnerTests
case HwIntrinsics.DisableLZCNT:
Assert.False(Lzcnt.IsSupported);
break;
case HwIntrinsics.DisableArm64AdvSimd:
Assert.False(AdvSimd.IsSupported);
break;
case HwIntrinsics.DisableArm64Aes:
Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported);
break;
case HwIntrinsics.DisableArm64Crc32:
Assert.False(Crc32.IsSupported);
break;
case HwIntrinsics.DisableArm64Dp:
Assert.False(Dp.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha1:
Assert.False(Sha1.IsSupported);
break;
case HwIntrinsics.DisableArm64Sha256:
Assert.False(Sha256.IsSupported);
break;
}
}

5
tests/ImageSharp.Tests/runtimeconfig.template.json

@ -0,0 +1,5 @@
{
"configProperties": {
"System.Drawing.EnableUnixSupport": true
}
}

0
tests/Images/Input/Bmp/9S.BMP → tests/Images/Input/Bmp/9S.bmp

0
tests/Images/Input/Bmp/DIAMOND.BMP → tests/Images/Input/Bmp/DIAMOND.bmp

0
tests/Images/Input/Bmp/GMARBLE.BMP → tests/Images/Input/Bmp/GMARBLE.bmp

0
tests/Images/Input/Bmp/PINES.BMP → tests/Images/Input/Bmp/PINES.bmp

0
tests/Images/Input/Bmp/SKATER.BMP → tests/Images/Input/Bmp/SKATER.bmp

0
tests/Images/Input/Bmp/SPADE.BMP → tests/Images/Input/Bmp/SPADE.bmp

0
tests/Images/Input/Bmp/SUNFLOW.BMP → tests/Images/Input/Bmp/SUNFLOW.bmp

0
tests/Images/Input/Bmp/WARPD.BMP → tests/Images/Input/Bmp/WARPD.bmp

Loading…
Cancel
Save