Browse Source

Merge remote-tracking branch 'origin/main' into bp/modeScoreArm

# Conflicts:
#	tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs
pull/2356/head
Brian Popow 3 years ago
parent
commit
af0b7bf002
  1. 5
      src/ImageSharp/Color/Color.cs
  2. 179
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 90
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  4. 143
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  5. 22
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  6. 3
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  7. 308
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  8. 42
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  9. 20
      src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs
  10. 41
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  11. 2
      src/ImageSharp/Image.Decode.cs
  12. 6
      src/ImageSharp/Image.cs
  13. 4
      src/ImageSharp/ImageInfo.cs
  14. 4
      src/ImageSharp/Image{TPixel}.cs
  15. 2
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  16. 5
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  17. 2
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  18. 9
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  19. 6
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  20. 195
      tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

20
src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs

@ -88,8 +88,8 @@ internal static class LossyUtils
Vector256<byte> b01s = Avx2.UnpackLow(b01.AsByte(), Vector256<byte>.Zero); Vector256<byte> b01s = Avx2.UnpackLow(b01.AsByte(), Vector256<byte>.Zero);
// subtract, square and accumulate. // subtract, square and accumulate.
Vector256<byte> d0 = Avx2.SubtractSaturate(a01s, b01s); Vector256<short> d0 = Avx2.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16());
Vector256<int> e0 = Avx2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); Vector256<int> e0 = Avx2.MultiplyAddAdjacent(d0, d0);
return Numerics.ReduceSum(e0); return Numerics.ReduceSum(e0);
} }
@ -121,10 +121,10 @@ internal static class LossyUtils
Vector128<byte> b23s = Sse2.UnpackLow(b23.AsByte(), Vector128<byte>.Zero); Vector128<byte> b23s = Sse2.UnpackLow(b23.AsByte(), Vector128<byte>.Zero);
// subtract, square and accumulate. // subtract, square and accumulate.
Vector128<byte> d0 = Sse2.SubtractSaturate(a01s, b01s); Vector128<short> d0 = Sse2.SubtractSaturate(a01s.AsInt16(), b01s.AsInt16());
Vector128<byte> d1 = Sse2.SubtractSaturate(a23s, b23s); Vector128<short> d1 = Sse2.SubtractSaturate(a23s.AsInt16(), b23s.AsInt16());
Vector128<int> e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); Vector128<int> e0 = Sse2.MultiplyAddAdjacent(d0, d0);
Vector128<int> e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); Vector128<int> e1 = Sse2.MultiplyAddAdjacent(d1, d1);
Vector128<int> sum = Sse2.Add(e0, e1); Vector128<int> sum = Sse2.Add(e0, e1);
return Numerics.ReduceSum(sum); return Numerics.ReduceSum(sum);
@ -142,18 +142,16 @@ internal static class LossyUtils
public static int Vp8_SseNxN(Span<byte> a, Span<byte> b, int w, int h) public static int Vp8_SseNxN(Span<byte> a, Span<byte> b, int w, int h)
{ {
int count = 0; int count = 0;
int aOffset = 0; int offset = 0;
int bOffset = 0;
for (int y = 0; y < h; y++) for (int y = 0; y < h; y++)
{ {
for (int x = 0; x < w; x++) for (int x = 0; x < w; x++)
{ {
int diff = a[aOffset + x] - b[bOffset + x]; int diff = a[offset + x] - b[offset + x];
count += diff * diff; count += diff * diff;
} }
aOffset += WebpConstants.Bps; offset += WebpConstants.Bps;
bOffset += WebpConstants.Bps;
} }
return count; return count;

41
src/ImageSharp/IO/ChunkedMemoryStream.cs

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

2
src/ImageSharp/Image.Decode.cs

@ -91,7 +91,7 @@ public abstract partial class Image
ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager);
} }
return format!; return format;
} }
/// <summary> /// <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="pixelType">The pixel type information.</param>
/// <param name="metadata">The image metadata.</param> /// <param name="metadata">The image metadata.</param>
/// <param name="size">The size in px units.</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) : base(pixelType, size, metadata)
=> this.configuration = configuration ?? Configuration.Default; => this.configuration = configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image"/> class. /// Initializes a new instance of the <see cref="Image"/> class.
@ -43,7 +43,7 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv
internal Image( internal Image(
Configuration configuration, Configuration configuration,
PixelTypeInfo pixelType, PixelTypeInfo pixelType,
ImageMetadata metadata, ImageMetadata? metadata,
int width, int width,
int height) int height)
: this(configuration, pixelType, metadata, new Size(width, 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="width">The width of the image in px units.</param>
/// <param name="height">The height 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> /// <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) : this(pixelType, new(width, height), metadata)
{ {
} }
@ -29,7 +29,7 @@ public class ImageInfo
/// <param name="pixelType">The pixel type information.</param> /// <param name="pixelType">The pixel type information.</param>
/// <param name="size">The size of the image in px units.</param> /// <param name="size">The size of the image in px units.</param>
/// <param name="metadata">The image metadata.</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.PixelType = pixelType;
this.Metadata = metadata ?? new ImageMetadata(); 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="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param> /// <param name="height">The height of the image in pixels.</param>
/// <param name="metadata">The images metadata.</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) : base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
=> this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel)); => this.frames = new ImageFrameCollection<TPixel>(this, width, height, default(TPixel));
@ -128,7 +128,7 @@ public sealed class Image<TPixel> : Image
int width, int width,
int height, int height,
TPixel backgroundColor, TPixel backgroundColor,
ImageMetadata metadata) ImageMetadata? metadata)
: base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height) : base(configuration, PixelTypeInfo.Create<TPixel>(), metadata, width, height)
=> this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor); => this.frames = new ImageFrameCollection<TPixel>(this, width, height, backgroundColor);

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; ReadOnlySpan<Color> sourcePalette = definition.Palette.Span;
this.paletteOwner = this.Configuration.MemoryAllocator.Allocate<TPixel>(sourcePalette.Length); 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.ditherProcessor = new DitherProcessor(
this.Configuration, 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 maxColors;
private readonly int bitDepth; private readonly int bitDepth;
private readonly Octree octree; private readonly Octree octree;
private IMemoryOwner<TPixel> paletteOwner; private readonly IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette; private ReadOnlyMemory<TPixel> palette;
private EuclideanPixelMap<TPixel>? pixelMap; private EuclideanPixelMap<TPixel>? pixelMap;
private readonly bool isDithering; private readonly bool isDithering;
@ -40,9 +40,6 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public OctreeQuantizer(Configuration configuration, QuantizerOptions options) public OctreeQuantizer(Configuration configuration, QuantizerOptions options)
{ {
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; 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. // Always use the palette length over options since the palette cannot be reduced.
TPixel[] palette = new TPixel[this.colorPalette.Length]; 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); 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", "Design",
"CA1001:Types that own disposable fields should be disposable", "CA1001:Types that own disposable fields should be disposable",
Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] 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> where TPixel : unmanaged, IPixel<TPixel>
{ {
private EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct. /// 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); => (byte)this.pixelMap.GetClosestColor(color, out match);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose() => this.pixelMap.Dispose();
{
this.pixelMap.Dispose();
}
} }

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

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

195
tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs

@ -77,151 +77,73 @@ public class LossyUtilsTests
private static void RunVp8Sse16X16Test() private static void RunVp8Sse16X16Test()
{ {
// arrange // arrange
byte[] a = Random rand = new(1234);
byte[] a = new byte[512 * 10];
byte[] b = new byte[512 * 10];
for (int i = 0; i < a.Length; i++)
{ {
154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, a[i] = (byte)rand.Next(byte.MaxValue);
101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, b[i] = (byte)rand.Next(byte.MaxValue);
164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, }
170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, int[] expected = { 2533110, 2818581, 2984663, 2891188, 2855134, 2634604, 2466504, 3061747, 2626010, 2640965 };
104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151,
148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, // act + assert
170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, int offset = 0;
92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, for (int i = 0; i < expected.Length; i++)
152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100,
101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153,
150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172,
171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105,
102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163,
150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133,
90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161,
157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78,
131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78,
95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130,
82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204
};
byte[] b =
{ {
150, 150, 150, 150, 146, 149, 152, 154, 164, 166, 154, 132, 99, 92, 106, 112, 204, 204, 204, 204, int actual = LossyUtils.Vp8_Sse16x16(a.AsSpan(offset), b.AsSpan(offset));
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, Assert.Equal(expected[i], actual);
161, 164, 151, 130, 93, 86, 100, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, 93, 86, 100, 106,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150,
146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 148, 148, 148, 148, 149, 158, 162, 159, 155, 155, 153, 129,
94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 154, 154, 154, 156, 161, 159, 152,
155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, 94, 87, 101, 106,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 153, 157, 162,
150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 152, 156, 158, 157, 140, 137, 145, 159, 155, 160, 150, 131,
89, 88, 102, 101, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
153, 161, 160, 149, 118, 128, 147, 162, 155, 160, 150, 131, 86, 85, 99, 98, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 165, 161, 144, 96, 128, 154, 159, 155,
160, 150, 131, 83, 82, 97, 96, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 161, 160, 149, 105, 78, 127, 156, 170, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 160, 160, 133, 85, 81, 129, 155,
167, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 156, 147, 109, 76, 85, 130, 153, 163, 156, 156, 154, 130, 81, 77, 95, 102,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 128, 87, 83,
88, 132, 152, 159, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204
};
int expected = 2063; offset += 512;
}
// act
int actual = LossyUtils.Vp8_Sse16x16(a, b);
// assert
Assert.Equal(expected, actual);
} }
private static void RunVp8Sse16X8Test() private static void RunVp8Sse16X8Test()
{ {
// arrange // arrange
byte[] a = Random rand = new(1234);
byte[] a = new byte[256 * 10];
byte[] b = new byte[256 * 10];
for (int i = 0; i < a.Length; i++)
{ {
107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, a[i] = (byte)rand.Next(byte.MaxValue);
147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, b[i] = (byte)rand.Next(byte.MaxValue);
172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, }
93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, int[] expected = { 1298274, 1234836, 1325264, 1493317, 1551995, 1432668, 1407891, 1483297, 1537930, 1317204 };
150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100,
102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, // act + assert
154, 154, 151, 132, 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, int offset = 0;
171, 178, 172, 176, 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, for (int i = 0; i < expected.Length; i++)
102, 100, 107, 100, 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155,
160, 162, 161, 153, 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120,
171, 179, 178, 172, 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128,
86, 86, 102, 105, 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173,
154, 152, 158, 163, 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105
};
byte[] b =
{ {
103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, int actual = LossyUtils.Vp8_Sse16x8(a.AsSpan(offset), b.AsSpan(offset));
146, 149, 152, 154, 161, 164, 151, 130, 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, Assert.Equal(expected[i], actual);
171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127,
93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175,
150, 150, 150, 150, 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 103, 103, 103, 103,
101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 148, 148, 148, 148, 149, 158, 162, 159,
155, 155, 153, 129, 94, 87, 101, 106, 102, 100, 100, 102, 100, 101, 120, 122, 170, 176, 176, 170,
174, 180, 171, 177, 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106,
102, 105, 105, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, 154, 154, 154, 154,
156, 161, 159, 152, 155, 155, 153, 129, 94, 87, 101, 106, 102, 112, 112, 102, 100, 101, 120, 122,
170, 176, 176, 170, 174, 180, 171, 177, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129,
94, 87, 101, 106, 102, 117, 117, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177,
152, 153, 157, 162, 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104
};
int expected = 749; offset += 256;
}
// act
int actual = LossyUtils.Vp8_Sse16x8(a, b);
// assert
Assert.Equal(expected, actual);
} }
private static void RunVp8Sse4X4Test() private static void RunVp8Sse4X4Test()
{ {
// arrange // arrange
byte[] a = Random rand = new(1234);
byte[] a = new byte[128 * 10];
byte[] b = new byte[128 * 10];
for (int i = 0; i < a.Length; i++)
{ {
27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, a[i] = (byte)rand.Next(byte.MaxValue);
129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28, b[i] = (byte)rand.Next(byte.MaxValue);
28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26, }
26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, int[] expected = { 194133, 125861, 165966, 195688, 106491, 173015, 266960, 200272, 311224, 122545 };
128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27,
129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128 // act + assert
}; int offset = 0;
for (int i = 0; i < expected.Length; i++)
byte[] b =
{ {
26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, int actual = LossyUtils.Vp8_Sse4x4(a.AsSpan(offset), b.AsSpan(offset));
204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, Assert.Equal(expected[i], actual);
28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26,
26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204,
204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204
};
int expected = 27;
// act offset += 128;
int actual = LossyUtils.Vp8_Sse4x4(a, b); }
// assert
Assert.Equal(expected, actual);
} }
private static void RunMean16x4Test() private static void RunMean16x4Test()
@ -311,32 +233,41 @@ public class LossyUtilsTests
[Fact] [Fact]
public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic);
// This will test the AVX2 version.
[Fact] [Fact]
public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll); public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll);
// This will test the SSE2 version.
[Fact] [Fact]
public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2); public void Vp8Sse16X16_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2);
// This will test the fallback scalar version.
[Fact] [Fact]
public void Vp8Sse16X16_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2); public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableAVX);
// This will test the AVX2 version.
[Fact] [Fact]
public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll);
// This will test the SSE2 version.
[Fact] [Fact]
public void Vp8Sse16X8_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2); public void Vp8Sse16X8_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2);
// This will test the fallback scalar version.
[Fact] [Fact]
public void Vp8Sse16X8_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2); public void Vp8Sse16X8_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableAVX);
// This will test the AVX2 version.
[Fact] [Fact]
public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll);
// This will test the SSE2 version.
[Fact] [Fact]
public void Vp8Sse4X4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableSSE2); public void Vp8Sse4X4_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2);
// This will test the fallback scalar version.
[Fact] [Fact]
public void Vp8Sse4X4_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2); public void Vp8Sse4X4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableAVX);
[Fact] [Fact]
public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll);

Loading…
Cancel
Save