Browse Source

Merge branch 'main' into js/premultiply

pull/2369/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
aed484a72c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 68
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  2. 26
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  3. 21
      src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs
  4. 4
      src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs
  5. 7
      src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
  6. 4
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  7. 38
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  8. 13
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  9. 47
      src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
  10. 31
      src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
  11. 5
      src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs
  12. 19
      src/ImageSharp/Formats/Webp/Lossless/CostManager.cs
  13. 3
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  14. 10
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  15. 25
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  16. 1
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  17. 220
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  18. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  19. 12
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  20. 9
      src/ImageSharp/Formats/Webp/WebpImageInfo.cs
  21. 6
      src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

68
src/ImageSharp/Formats/Webp/AlphaDecoder.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.Runtime.Intrinsics; using System.Runtime.Intrinsics;
@ -38,7 +38,7 @@ internal class AlphaDecoder : IDisposable
this.LastRow = 0; this.LastRow = 0;
int totalPixels = width * height; int totalPixels = width * height;
var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); WebpAlphaCompressionMethod compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03);
if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression) if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression)
{ {
WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found");
@ -59,7 +59,7 @@ internal class AlphaDecoder : IDisposable
if (this.Compressed) if (this.Compressed)
{ {
var bitReader = new Vp8LBitReader(data); Vp8LBitReader bitReader = new(data);
this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration);
this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true);
@ -110,6 +110,7 @@ internal class AlphaDecoder : IDisposable
/// <summary> /// <summary>
/// Gets a value indicating whether the alpha channel uses compression. /// Gets a value indicating whether the alpha channel uses compression.
/// </summary> /// </summary>
[MemberNotNullWhen(true, nameof(LosslessDecoder))]
private bool Compressed { get; } private bool Compressed { get; }
/// <summary> /// <summary>
@ -120,7 +121,7 @@ internal class AlphaDecoder : IDisposable
/// <summary> /// <summary>
/// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
/// </summary> /// </summary>
private WebpLosslessDecoder LosslessDecoder { get; } private WebpLosslessDecoder? LosslessDecoder { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding.
@ -173,17 +174,14 @@ internal class AlphaDecoder : IDisposable
dst = dst[this.Width..]; dst = dst[this.Width..];
} }
} }
else if (this.Use8BDecode)
{
this.LosslessDecoder.DecodeAlphaData(this);
}
else else
{ {
if (this.Use8BDecode) this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
{ this.ExtractAlphaRows(this.Vp8LDec);
this.LosslessDecoder.DecodeAlphaData(this);
}
else
{
this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
this.ExtractAlphaRows(this.Vp8LDec);
}
} }
} }
@ -261,8 +259,7 @@ internal class AlphaDecoder : IDisposable
{ {
int numRowsToProcess = dec.Height; int numRowsToProcess = dec.Height;
int width = dec.Width; int width = dec.Width;
Span<uint> pixels = dec.Pixels.Memory.Span; Span<uint> input = dec.Pixels.Memory.Span;
Span<uint> input = pixels;
Span<byte> output = this.Alpha.Memory.Span; Span<byte> output = this.Alpha.Memory.Span;
// Extract alpha (which is stored in the green plane). // Extract alpha (which is stored in the green plane).
@ -327,7 +324,7 @@ internal class AlphaDecoder : IDisposable
ref byte srcRef = ref MemoryMarshal.GetReference(input); ref byte srcRef = ref MemoryMarshal.GetReference(input);
for (i = 1; i + 8 <= width; i += 8) for (i = 1; i + 8 <= width; i += 8)
{ {
var a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0); Vector128<long> a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<byte> a1 = Sse2.Add(a0.AsByte(), last.AsByte()); Vector128<byte> a1 = Sse2.Add(a0.AsByte(), last.AsByte());
Vector128<byte> a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1); Vector128<byte> a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1);
Vector128<byte> a3 = Sse2.Add(a1, a2); Vector128<byte> a3 = Sse2.Add(a1, a2);
@ -365,32 +362,29 @@ internal class AlphaDecoder : IDisposable
{ {
HorizontalUnfilter(null, input, dst, width); HorizontalUnfilter(null, input, dst, width);
} }
else else if (Avx2.IsSupported)
{ {
if (Avx2.IsSupported) nint i;
int maxPos = width & ~31;
for (i = 0; i < maxPos; i += 32)
{ {
nint i; Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i));
int maxPos = width & ~31; Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i));
for (i = 0; i < maxPos; i += 32) Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
{ ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i)); Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i)); }
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
}
for (; i < width; i++) for (; i < width; i++)
{ {
dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]); dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]);
}
} }
else }
else
{
for (int i = 0; i < width; i++)
{ {
for (int i = 0; i < width; i++) dst[i] = (byte)(prev[i] + input[i]);
{
dst[i] = (byte)(prev[i] + input[i]);
}
} }
} }
} }

26
src/ImageSharp/Formats/Webp/AlphaEncoder.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 SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -13,10 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary> /// <summary>
/// Methods for encoding the alpha data of a VP8 image. /// Methods for encoding the alpha data of a VP8 image.
/// </summary> /// </summary>
internal class AlphaEncoder : IDisposable internal static class AlphaEncoder
{ {
private IMemoryOwner<byte> alphaData;
/// <summary> /// <summary>
/// Encodes the alpha channel data. /// Encodes the alpha channel data.
/// Data is either compressed as lossless webp image or uncompressed. /// Data is either compressed as lossless webp image or uncompressed.
@ -29,12 +26,18 @@ internal class AlphaEncoder : IDisposable
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param> /// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
/// <param name="size">The size in bytes of the alpha data.</param> /// <param name="size">The size in bytes of the alpha data.</param>
/// <returns>The encoded alpha data.</returns> /// <returns>The encoded alpha data.</returns>
public IMemoryOwner<byte> EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, bool skipMetadata, bool compress, out int size) public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
Image<TPixel> image,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
bool compress,
out int size)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int width = image.Width; int width = image.Width;
int height = image.Height; int height = image.Height;
this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); IMemoryOwner<byte> alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
if (compress) if (compress)
{ {
@ -55,15 +58,15 @@ internal class AlphaEncoder : IDisposable
// The transparency information will be stored in the green channel of the ARGB quadruplet. // The transparency information will be stored in the green channel of the ARGB quadruplet.
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels, // The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
// that can improve compression. // that can improve compression.
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan()); using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan());
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData); size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData);
return this.alphaData; return alphaData;
} }
size = width * height; size = width * height;
return this.alphaData; return alphaData;
} }
/// <summary> /// <summary>
@ -128,7 +131,4 @@ internal class AlphaEncoder : IDisposable
return alphaDataBuffer; return alphaDataBuffer;
} }
/// <inheritdoc/>
public void Dispose() => this.alphaData?.Dispose();
} }

21
src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.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 SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -14,10 +13,16 @@ internal abstract class BitReaderBase : IDisposable
{ {
private bool isDisposed; private bool isDisposed;
protected BitReaderBase(IMemoryOwner<byte> data)
=> this.Data = data;
protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator)
=> this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator);
/// <summary> /// <summary>
/// Gets or sets the raw encoded image data. /// Gets the raw encoded image data.
/// </summary> /// </summary>
public IMemoryOwner<byte> Data { get; set; } public IMemoryOwner<byte> Data { get; }
/// <summary> /// <summary>
/// Copies the raw encoded image data from the stream into a byte array. /// Copies the raw encoded image data from the stream into a byte array.
@ -25,11 +30,13 @@ internal abstract class BitReaderBase : IDisposable
/// <param name="input">The input stream.</param> /// <param name="input">The input stream.</param>
/// <param name="bytesToRead">Number of bytes to read as indicated from the chunk size.</param> /// <param name="bytesToRead">Number of bytes to read as indicated from the chunk size.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param> /// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) protected static IMemoryOwner<byte> ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
{ {
this.Data = memoryAllocator.Allocate<byte>(bytesToRead); IMemoryOwner<byte> data = memoryAllocator.Allocate<byte>(bytesToRead);
Span<byte> dataSpan = this.Data.Memory.Span; Span<byte> dataSpan = data.Memory.Span;
input.Read(dataSpan[..bytesToRead], 0, bytesToRead); input.Read(dataSpan[..bytesToRead], 0, bytesToRead);
return data;
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
@ -41,7 +48,7 @@ internal abstract class BitReaderBase : IDisposable
if (disposing) if (disposing)
{ {
this.Data?.Dispose(); this.Data.Dispose();
} }
this.isDisposed = true; this.isDisposed = true;

4
src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs

@ -57,12 +57,12 @@ internal class Vp8BitReader : BitReaderBase
/// <param name="partitionLength">The partition length.</param> /// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param> /// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0)
: base(inputStream, (int)imageDataSize, memoryAllocator)
{ {
Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize)); Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize));
this.ImageDataSize = imageDataSize; this.ImageDataSize = imageDataSize;
this.PartitionLength = partitionLength; this.PartitionLength = partitionLength;
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.InitBitreader(partitionLength, startPos); this.InitBitreader(partitionLength, startPos);
} }
@ -73,8 +73,8 @@ internal class Vp8BitReader : BitReaderBase
/// <param name="partitionLength">The partition length.</param> /// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param> /// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(IMemoryOwner<byte> imageData, uint partitionLength, int startPos = 0) public Vp8BitReader(IMemoryOwner<byte> imageData, uint partitionLength, int startPos = 0)
: base(imageData)
{ {
this.Data = imageData;
this.ImageDataSize = (uint)imageData.Memory.Length; this.ImageDataSize = (uint)imageData.Memory.Length;
this.PartitionLength = partitionLength; this.PartitionLength = partitionLength;
this.InitBitreader(partitionLength, startPos); this.InitBitreader(partitionLength, startPos);

7
src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs

@ -63,8 +63,8 @@ internal class Vp8LBitReader : BitReaderBase
/// </summary> /// </summary>
/// <param name="data">Lossless compressed image data.</param> /// <param name="data">Lossless compressed image data.</param>
public Vp8LBitReader(IMemoryOwner<byte> data) public Vp8LBitReader(IMemoryOwner<byte> data)
: base(data)
{ {
this.Data = data;
this.len = data.Memory.Length; this.len = data.Memory.Length;
this.value = 0; this.value = 0;
this.bitPos = 0; this.bitPos = 0;
@ -88,11 +88,10 @@ internal class Vp8LBitReader : BitReaderBase
/// <param name="imageDataSize">The raw image data size in bytes.</param> /// <param name="imageDataSize">The raw image data size in bytes.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param> /// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
: base(inputStream, (int)imageDataSize, memoryAllocator)
{ {
long length = imageDataSize; long length = imageDataSize;
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.len = length; this.len = length;
this.value = 0; this.value = 0;
this.bitPos = 0; this.bitPos = 0;
@ -193,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void ShiftBytes() private void ShiftBytes()
{ {
System.Span<byte> dataSpan = this.Data.Memory.Span; System.Span<byte> dataSpan = this.Data!.Memory.Span;
while (this.bitPos >= 8 && this.pos < this.len) while (this.bitPos >= 8 && this.pos < this.len)
{ {
this.value >>= 8; this.value >>= 8;

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

@ -123,7 +123,7 @@ internal abstract class BitWriterBase
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
/// <param name="metadataBytes">The metadata profile's bytes.</param> /// <param name="metadataBytes">The metadata profile's bytes.</param>
/// <param name="chunkType">The chuck type to write.</param> /// <param name="chunkType">The chuck type to write.</param>
protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChunkType chunkType) protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
{ {
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes)); DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
@ -207,7 +207,7 @@ internal abstract class BitWriterBase
/// <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="hasAlpha">Flag indicating, if a alpha channel is present.</param> /// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, byte[] iccProfileBytes, uint width, uint height, bool hasAlpha) protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, byte[]? iccProfileBytes, uint width, uint height, bool hasAlpha)
{ {
if (width > MaxDimension || height > MaxDimension) if (width > MaxDimension || height > MaxDimension)
{ {

38
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.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.Binary; using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -58,7 +57,8 @@ internal class Vp8BitWriter : BitWriterBase
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class. /// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary> /// </summary>
/// <param name="expectedSize">The expected size in bytes.</param> /// <param name="expectedSize">The expected size in bytes.</param>
public Vp8BitWriter(int expectedSize) /// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: base(expectedSize) : base(expectedSize)
{ {
this.range = 255 - 1; this.range = 255 - 1;
@ -67,15 +67,9 @@ internal class Vp8BitWriter : BitWriterBase
this.nbBits = -8; this.nbBits = -8;
this.pos = 0; this.pos = 0;
this.maxPos = 0; this.maxPos = 0;
}
/// <summary> this.enc = enc;
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class. }
/// </summary>
/// <param name="expectedSize">The expected size in bytes.</param>
/// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: this(expectedSize) => this.enc = enc;
/// <inheritdoc/> /// <inheritdoc/>
public override int NumBytes() => (int)this.pos; public override int NumBytes() => (int)this.pos;
@ -414,9 +408,9 @@ internal class Vp8BitWriter : BitWriterBase
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param> /// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
public void WriteEncodedImageToStream( public void WriteEncodedImageToStream(
Stream stream, Stream stream,
ExifProfile exifProfile, ExifProfile? exifProfile,
XmpProfile xmpProfile, XmpProfile? xmpProfile,
IccProfile iccProfile, IccProfile? iccProfile,
uint width, uint width,
uint height, uint height,
bool hasAlpha, bool hasAlpha,
@ -424,22 +418,22 @@ internal class Vp8BitWriter : BitWriterBase
bool alphaDataIsCompressed) bool alphaDataIsCompressed)
{ {
bool isVp8X = false; bool isVp8X = false;
byte[] exifBytes = null; byte[]? exifBytes = null;
byte[] xmpBytes = null; byte[]? xmpBytes = null;
byte[] iccProfileBytes = null; byte[]? iccProfileBytes = null;
uint riffSize = 0; uint riffSize = 0;
if (exifProfile != null) if (exifProfile != null)
{ {
isVp8X = true; isVp8X = true;
exifBytes = exifProfile.ToByteArray(); exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes); riffSize += MetadataChunkSize(exifBytes!);
} }
if (xmpProfile != null) if (xmpProfile != null)
{ {
isVp8X = true; isVp8X = true;
xmpBytes = xmpProfile.Data; xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes); riffSize += MetadataChunkSize(xmpBytes!);
} }
if (iccProfile != null) if (iccProfile != null)
@ -465,7 +459,7 @@ internal class Vp8BitWriter : BitWriterBase
int mbSize = this.enc.Mbw * this.enc.Mbh; int mbSize = this.enc.Mbw * this.enc.Mbh;
int expectedSize = mbSize * 7 / 8; int expectedSize = mbSize * 7 / 8;
var bitWriterPartZero = new Vp8BitWriter(expectedSize); Vp8BitWriter bitWriterPartZero = new(expectedSize, this.enc);
// Partition #0 with header and partition sizes. // Partition #0 with header and partition sizes.
uint size0 = this.GeneratePartition0(bitWriterPartZero); uint size0 = this.GeneratePartition0(bitWriterPartZero);
@ -676,9 +670,9 @@ internal class Vp8BitWriter : BitWriterBase
bool isVp8X, bool isVp8X,
uint width, uint width,
uint height, uint height,
ExifProfile exifProfile, ExifProfile? exifProfile,
XmpProfile xmpProfile, XmpProfile? xmpProfile,
byte[] iccProfileBytes, byte[]? iccProfileBytes,
bool hasAlpha, bool hasAlpha,
Span<byte> alphaData, Span<byte> alphaData,
bool alphaDataIsCompressed) bool alphaDataIsCompressed)

13
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.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.Binary; using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -138,25 +137,25 @@ internal class Vp8LBitWriter : BitWriterBase
/// <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="hasAlpha">Flag indicating, if a alpha channel is present.</param> /// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, IccProfile iccProfile, uint width, uint height, bool hasAlpha) public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha)
{ {
bool isVp8X = false; bool isVp8X = false;
byte[] exifBytes = null; byte[]? exifBytes = null;
byte[] xmpBytes = null; byte[]? xmpBytes = null;
byte[] iccBytes = null; byte[]? iccBytes = null;
uint riffSize = 0; uint riffSize = 0;
if (exifProfile != null) if (exifProfile != null)
{ {
isVp8X = true; isVp8X = true;
exifBytes = exifProfile.ToByteArray(); exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes); riffSize += MetadataChunkSize(exifBytes!);
} }
if (xmpProfile != null) if (xmpProfile != null)
{ {
isVp8X = true; isVp8X = true;
xmpBytes = xmpProfile.Data; xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes); riffSize += MetadataChunkSize(xmpBytes!);
} }
if (iccProfile != null) if (iccProfile != null)

47
src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.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 SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -50,7 +49,7 @@ internal static class BackwardReferenceEncoder
int lz77TypeBest = 0; int lz77TypeBest = 0;
double bitCostBest = -1; double bitCostBest = -1;
int cacheBitsInitial = cacheBits; int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null; Vp8LHashChain? hashChainBox = null;
var stats = new Vp8LStreaks(); var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy(); var bitsEntropy = new Vp8LBitEntropy();
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
@ -101,7 +100,7 @@ internal static class BackwardReferenceEncoder
// Improve on simple LZ77 but only for high quality (TraceBackwards is costly). // Improve on simple LZ77 but only for high quality (TraceBackwards is costly).
if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25)
{ {
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox!;
BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits); var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
@ -140,8 +139,7 @@ internal static class BackwardReferenceEncoder
for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++) for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++)
{ {
histos[i] = new Vp8LHistogram(paletteCodeBits: i); histos[i] = new Vp8LHistogram(paletteCodeBits: i);
colorCache[i] = new ColorCache(); colorCache[i] = new ColorCache(i);
colorCache[i].Init(i);
} }
// Find the cacheBits giving the lowest entropy. // Find the cacheBits giving the lowest entropy.
@ -274,11 +272,11 @@ internal static class BackwardReferenceEncoder
double offsetCost = -1; double offsetCost = -1;
int firstOffsetIsConstant = -1; // initialized with 'impossible' value. int firstOffsetIsConstant = -1; // initialized with 'impossible' value.
int reach = 0; int reach = 0;
var colorCache = new ColorCache(); ColorCache? colorCache = null;
if (useColorCache) if (useColorCache)
{ {
colorCache.Init(cacheBits); colorCache = new ColorCache(cacheBits);
} }
costModel.Build(xSize, cacheBits, refs); costModel.Build(xSize, cacheBits, refs);
@ -375,12 +373,12 @@ internal static class BackwardReferenceEncoder
private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan<uint> bgra, int cacheBits, Span<ushort> chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan<uint> bgra, int cacheBits, Span<ushort> chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs)
{ {
bool useColorCache = cacheBits > 0; bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache(); ColorCache? colorCache = null;
int i = 0; int i = 0;
if (useColorCache) if (useColorCache)
{ {
colorCache.Init(cacheBits); colorCache = new ColorCache(cacheBits);
} }
backwardRefs.Refs.Clear(); backwardRefs.Refs.Clear();
@ -396,7 +394,7 @@ internal static class BackwardReferenceEncoder
{ {
for (int k = 0; k < len; k++) for (int k = 0; k < len; k++)
{ {
colorCache.Insert(bgra[i + k]); colorCache!.Insert(bgra[i + k]);
} }
} }
@ -405,7 +403,7 @@ internal static class BackwardReferenceEncoder
else else
{ {
PixOrCopy v; PixOrCopy v;
int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1; int idx = useColorCache ? colorCache!.Contains(bgra[i]) : -1;
if (idx >= 0) if (idx >= 0)
{ {
// useColorCache is true and color cache contains bgra[i] // useColorCache is true and color cache contains bgra[i]
@ -416,7 +414,7 @@ internal static class BackwardReferenceEncoder
{ {
if (useColorCache) if (useColorCache)
{ {
colorCache.Insert(bgra[i]); colorCache!.Insert(bgra[i]);
} }
v = PixOrCopy.CreateLiteral(bgra[i]); v = PixOrCopy.CreateLiteral(bgra[i]);
@ -430,7 +428,7 @@ internal static class BackwardReferenceEncoder
private static void AddSingleLiteralWithCostModel( private static void AddSingleLiteralWithCostModel(
ReadOnlySpan<uint> bgra, ReadOnlySpan<uint> bgra,
ColorCache colorCache, ColorCache? colorCache,
CostModel costModel, CostModel costModel,
int idx, int idx,
bool useColorCache, bool useColorCache,
@ -440,7 +438,7 @@ internal static class BackwardReferenceEncoder
{ {
double costVal = prevCost; double costVal = prevCost;
uint color = bgra[idx]; uint color = bgra[idx];
int ix = useColorCache ? colorCache.Contains(color) : -1; int ix = useColorCache ? colorCache!.Contains(color) : -1;
if (ix >= 0) if (ix >= 0)
{ {
double mul0 = 0.68; double mul0 = 0.68;
@ -451,7 +449,7 @@ internal static class BackwardReferenceEncoder
double mul1 = 0.82; double mul1 = 0.82;
if (useColorCache) if (useColorCache)
{ {
colorCache.Insert(color); colorCache!.Insert(color);
} }
costVal += costModel.GetLiteralCost(color) * mul1; costVal += costModel.GetLiteralCost(color) * mul1;
@ -469,10 +467,10 @@ internal static class BackwardReferenceEncoder
int iLastCheck = -1; int iLastCheck = -1;
bool useColorCache = cacheBits > 0; bool useColorCache = cacheBits > 0;
int pixCount = xSize * ySize; int pixCount = xSize * ySize;
var colorCache = new ColorCache(); ColorCache? colorCache = null;
if (useColorCache) if (useColorCache)
{ {
colorCache.Init(cacheBits); colorCache = new ColorCache(cacheBits);
} }
refs.Refs.Clear(); refs.Refs.Clear();
@ -529,7 +527,7 @@ internal static class BackwardReferenceEncoder
{ {
for (j = i; j < i + len; j++) for (j = i; j < i + len; j++)
{ {
colorCache.Insert(bgra[j]); colorCache!.Insert(bgra[j]);
} }
} }
} }
@ -725,11 +723,11 @@ internal static class BackwardReferenceEncoder
{ {
int pixelCount = xSize * ySize; int pixelCount = xSize * ySize;
bool useColorCache = cacheBits > 0; bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache(); ColorCache? colorCache = null;
if (useColorCache) if (useColorCache)
{ {
colorCache.Init(cacheBits); colorCache = new ColorCache(cacheBits);
} }
refs.Refs.Clear(); refs.Refs.Clear();
@ -757,7 +755,7 @@ internal static class BackwardReferenceEncoder
{ {
for (int k = 0; k < prevRowLen; ++k) for (int k = 0; k < prevRowLen; ++k)
{ {
colorCache.Insert(bgra[i + k]); colorCache!.Insert(bgra[i + k]);
} }
} }
@ -777,8 +775,7 @@ internal static class BackwardReferenceEncoder
private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cacheBits, Vp8LBackwardRefs refs) private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
{ {
int pixelIndex = 0; int pixelIndex = 0;
var colorCache = new ColorCache(); ColorCache colorCache = new(cacheBits);
colorCache.Init(cacheBits);
for (int idx = 0; idx < refs.Refs.Count; idx++) for (int idx = 0; idx < refs.Refs.Count; idx++)
{ {
PixOrCopy v = refs.Refs[idx]; PixOrCopy v = refs.Refs[idx];
@ -825,12 +822,12 @@ internal static class BackwardReferenceEncoder
} }
} }
private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs) private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache? colorCache, Vp8LBackwardRefs refs)
{ {
PixOrCopy v; PixOrCopy v;
if (useColorCache) if (useColorCache)
{ {
int key = colorCache.GetIndex(pixel); int key = colorCache!.GetIndex(pixel);
if (colorCache.Lookup(key) == pixel) if (colorCache.Lookup(key) == pixel)
{ {
v = PixOrCopy.CreateCacheIdx(key); v = PixOrCopy.CreateCacheIdx(key);

31
src/ImageSharp/Formats/Webp/Lossless/ColorCache.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.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -14,31 +13,31 @@ internal class ColorCache
private const uint HashMul = 0x1e35a7bdu; private const uint HashMul = 0x1e35a7bdu;
/// <summary> /// <summary>
/// Gets the color entries. /// Initializes a new instance of the <see cref="ColorCache"/> class.
/// </summary> /// </summary>
public uint[] Colors { get; private set; } /// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
public ColorCache(int hashBits)
{
int hashSize = 1 << hashBits;
this.Colors = new uint[hashSize];
this.HashBits = hashBits;
this.HashShift = 32 - hashBits;
}
/// <summary> /// <summary>
/// Gets the hash shift: 32 - hashBits. /// Gets the color entries.
/// </summary> /// </summary>
public int HashShift { get; private set; } public uint[] Colors { get; }
/// <summary> /// <summary>
/// Gets the hash bits. /// Gets the hash shift: 32 - hashBits.
/// </summary> /// </summary>
public int HashBits { get; private set; } public int HashShift { get; }
/// <summary> /// <summary>
/// Initializes a new color cache. /// Gets the hash bits.
/// </summary> /// </summary>
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param> public int HashBits { get; }
public void Init(int hashBits)
{
int hashSize = 1 << hashBits;
this.Colors = new uint[hashSize];
this.HashBits = hashBits;
this.HashShift = 32 - hashBits;
}
/// <summary> /// <summary>
/// Inserts a new color into the cache. /// Inserts a new color into the cache.

5
src/ImageSharp/Formats/Webp/Lossless/CostInterval.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.Diagnostics; using System.Diagnostics;
@ -33,7 +32,7 @@ internal class CostInterval
public int Index { get; set; } public int Index { get; set; }
public CostInterval Previous { get; set; } public CostInterval? Previous { get; set; }
public CostInterval Next { get; set; } public CostInterval? Next { get; set; }
} }

19
src/ImageSharp/Formats/Webp/Lossless/CostManager.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 SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
/// </summary> /// </summary>
internal sealed class CostManager : IDisposable internal sealed class CostManager : IDisposable
{ {
private CostInterval head; private CostInterval? head;
private const int FreeIntervalsStartCount = 25; private const int FreeIntervalsStartCount = 25;
@ -103,10 +102,10 @@ internal sealed class CostManager : IDisposable
/// <param name="doCleanIntervals">If 'doCleanIntervals' is true, intervals that end before 'i' will be popped.</param> /// <param name="doCleanIntervals">If 'doCleanIntervals' is true, intervals that end before 'i' will be popped.</param>
public void UpdateCostAtIndex(int i, bool doCleanIntervals) public void UpdateCostAtIndex(int i, bool doCleanIntervals)
{ {
CostInterval current = this.head; CostInterval? current = this.head;
while (current != null && current.Start <= i) while (current != null && current.Start <= i)
{ {
CostInterval next = current.Next; CostInterval? next = current.Next;
if (current.End <= i) if (current.End <= i)
{ {
if (doCleanIntervals) if (doCleanIntervals)
@ -155,7 +154,7 @@ internal sealed class CostManager : IDisposable
return; return;
} }
CostInterval interval = this.head; CostInterval? interval = this.head;
for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++)
{ {
// Define the intersection of the ith interval with the new one. // Define the intersection of the ith interval with the new one.
@ -163,7 +162,7 @@ internal sealed class CostManager : IDisposable
int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End);
float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); float cost = (float)(distanceCost + this.CacheIntervals[i].Cost);
CostInterval intervalNext; CostInterval? intervalNext;
for (; interval != null && interval.Start < end; interval = intervalNext) for (; interval != null && interval.Start < end; interval = intervalNext)
{ {
intervalNext = interval.Next; intervalNext = interval.Next;
@ -225,7 +224,7 @@ internal sealed class CostManager : IDisposable
/// Pop an interval from the manager. /// Pop an interval from the manager.
/// </summary> /// </summary>
/// <param name="interval">The interval to remove.</param> /// <param name="interval">The interval to remove.</param>
private void PopInterval(CostInterval interval) private void PopInterval(CostInterval? interval)
{ {
if (interval == null) if (interval == null)
{ {
@ -240,7 +239,7 @@ internal sealed class CostManager : IDisposable
this.freeIntervals.Push(interval); this.freeIntervals.Push(interval);
} }
private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) private void InsertInterval(CostInterval? intervalIn, float cost, int position, int start, int end)
{ {
if (start >= end) if (start >= end)
{ {
@ -271,7 +270,7 @@ internal sealed class CostManager : IDisposable
/// it was orphaned (which can be NULL), set it at the right place in the list /// it was orphaned (which can be NULL), set it at the right place in the list
/// of intervals using the start_ ordering and the previous interval as a hint. /// of intervals using the start_ ordering and the previous interval as a hint.
/// </summary> /// </summary>
private void PositionOrphanInterval(CostInterval current, CostInterval previous) private void PositionOrphanInterval(CostInterval current, CostInterval? previous)
{ {
previous ??= this.head; previous ??= this.head;
@ -292,7 +291,7 @@ internal sealed class CostManager : IDisposable
/// <summary> /// <summary>
/// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'.
/// </summary> /// </summary>
private void ConnectIntervals(CostInterval prev, CostInterval next) private void ConnectIntervals(CostInterval? prev, CostInterval? next)
{ {
if (prev != null) if (prev != null)
{ {

3
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -158,10 +158,9 @@ internal sealed class WebpLosslessDecoder
// Finish setting up the color-cache. // Finish setting up the color-cache.
if (isColorCachePresent) if (isColorCachePresent)
{ {
decoder.Metadata.ColorCache = new ColorCache(); decoder.Metadata.ColorCache = new ColorCache(colorCacheBits);
colorCacheSize = 1 << colorCacheBits; colorCacheSize = 1 << colorCacheBits;
decoder.Metadata.ColorCacheSize = colorCacheSize; decoder.Metadata.ColorCacheSize = colorCacheSize;
decoder.Metadata.ColorCache.Init(colorCacheBits);
} }
else else
{ {

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

@ -348,12 +348,18 @@ internal class Vp8Encoder : IDisposable
// Extract and encode alpha channel data, if present. // Extract and encode alpha channel data, if present.
int alphaDataSize = 0; int alphaDataSize = 0;
bool alphaCompressionSucceeded = false; bool alphaCompressionSucceeded = false;
using AlphaEncoder alphaEncoder = new();
Span<byte> alphaData = Span<byte>.Empty; Span<byte> alphaData = Span<byte>.Empty;
if (hasAlpha) if (hasAlpha)
{ {
// TODO: This can potentially run in an separate task. // TODO: This can potentially run in an separate task.
IMemoryOwner<byte> encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.skipMetadata, this.alphaCompression, out alphaDataSize); using IMemoryOwner<byte> encodedAlphaData = AlphaEncoder.EncodeAlpha(
image,
this.configuration,
this.memoryAllocator,
this.skipMetadata,
this.alphaCompression,
out alphaDataSize);
alphaData = encodedAlphaData.GetSpan(); alphaData = encodedAlphaData.GetSpan();
if (alphaDataSize < pixelCount) if (alphaDataSize < pixelCount)
{ {

25
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.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;
@ -46,17 +45,17 @@ internal class WebpAnimationDecoder : IDisposable
/// <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 WebpMetadata webpMetadata; private WebpMetadata? webpMetadata;
/// <summary> /// <summary>
/// The alpha data, if an ALPH chunk is present. /// The alpha data, if an ALPH chunk is present.
/// </summary> /// </summary>
private IMemoryOwner<byte> alphaData; private IMemoryOwner<byte>? alphaData;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class. /// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class.
@ -83,8 +82,8 @@ internal class WebpAnimationDecoder : IDisposable
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Image<TPixel> image = null; Image<TPixel>? image = null;
ImageFrame<TPixel> previousFrame = null; ImageFrame<TPixel>? previousFrame = null;
this.metadata = new ImageMetadata(); this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetWebpMetadata(); this.webpMetadata = this.metadata.GetWebpMetadata();
@ -99,12 +98,12 @@ internal class WebpAnimationDecoder : IDisposable
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Animation: case WebpChunkType.Animation:
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor.Value); uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor!.Value);
remainingBytes -= (int)dataSize; remainingBytes -= (int)dataSize;
break; break;
case WebpChunkType.Xmp: case WebpChunkType.Xmp:
case WebpChunkType.Exif: case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image.Metadata, false, this.buffer); WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, this.buffer);
break; break;
default: default:
WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data"); WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data");
@ -117,7 +116,7 @@ internal class WebpAnimationDecoder : IDisposable
} }
} }
return image; return image!;
} }
/// <summary> /// <summary>
@ -130,7 +129,7 @@ internal class WebpAnimationDecoder : IDisposable
/// <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="backgroundColor">The default background color of the canvas in.</param> /// <param name="backgroundColor">The default background color of the canvas in.</param>
private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, uint width, uint height, Color backgroundColor) private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, uint width, uint height, Color backgroundColor)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
AnimationFrameData frameData = this.ReadFrameHeader(stream); AnimationFrameData frameData = this.ReadFrameHeader(stream);
@ -146,7 +145,7 @@ internal class WebpAnimationDecoder : IDisposable
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
} }
WebpImageInfo webpInfo = null; WebpImageInfo? webpInfo = null;
WebpFeatures features = new(); WebpFeatures features = new();
switch (chunkType) switch (chunkType)
{ {
@ -163,7 +162,7 @@ internal class WebpAnimationDecoder : IDisposable
break; break;
} }
ImageFrame<TPixel> currentFrame = null; ImageFrame<TPixel>? currentFrame = null;
ImageFrame<TPixel> imageFrame; ImageFrame<TPixel> imageFrame;
if (previousFrame is null) if (previousFrame is null)
{ {
@ -175,7 +174,7 @@ internal class WebpAnimationDecoder : IDisposable
} }
else else
{ {
currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection. currentFrame = image!.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection.
SetFrameMetadata(currentFrame.Metadata, frameData.Duration); SetFrameMetadata(currentFrame.Metadata, frameData.Duration);

1
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.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.Binary; using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.BitReader;

220
src/ImageSharp/Formats/Webp/WebpDecoderCore.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;
@ -9,9 +8,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Webp; namespace SixLabors.ImageSharp.Formats.Webp;
@ -41,35 +38,20 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary> /// </summary>
private readonly uint maxFrames; private readonly uint maxFrames;
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private ImageMetadata metadata;
/// <summary> /// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present. /// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary> /// </summary>
private IMemoryOwner<byte> alphaData; private IMemoryOwner<byte>? alphaData;
/// <summary> /// <summary>
/// Used for allocating memory during the decoding operations. /// Used for allocating memory during the decoding operations.
/// </summary> /// </summary>
private readonly MemoryAllocator memoryAllocator; private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
private BufferedReadStream currentStream;
/// <summary>
/// The webp specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
/// <summary> /// <summary>
/// Information about the webp image. /// Information about the webp image.
/// </summary> /// </summary>
private WebpImageInfo webImageInfo; private WebpImageInfo? webImageInfo;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class. /// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
@ -88,25 +70,24 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
public DecoderOptions Options { get; } public DecoderOptions Options { get; }
/// <inheritdoc/> /// <inheritdoc/>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
/// <inheritdoc /> /// <inheritdoc />
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
{ {
this.metadata = new ImageMetadata(); ImageMetadata metadata = new();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader(); uint fileSize = this.ReadImageHeader(stream);
using (this.webImageInfo = this.ReadVp8Info()) using (this.webImageInfo = this.ReadVp8Info(stream, metadata))
{ {
if (this.webImageInfo.Features is { Animation: true }) if (this.webImageInfo.Features is { Animation: true })
{ {
using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames); using WebpAnimationDecoder animationDecoder = new(this.memoryAllocator, this.configuration, this.maxFrames);
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
} }
@ -115,23 +96,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
} }
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless) if (this.webImageInfo.IsLossless)
{ {
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); WebpLosslessDecoder losslessDecoder = new(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height); losslessDecoder.Decode(pixels, image.Width, image.Height);
} }
else else
{ {
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); WebpLossyDecoder lossyDecoder = new(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData);
} }
// There can be optional chunks after the image data, like EXIF and XMP. // There can be optional chunks after the image data, like EXIF and XMP.
if (this.webImageInfo.Features != null) if (this.webImageInfo.Features != null)
{ {
this.ParseOptionalChunks(this.webImageInfo.Features); this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features);
} }
return image; return image;
@ -147,31 +128,32 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <inheritdoc /> /// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
this.currentStream = stream; this.ReadImageHeader(stream);
this.ReadImageHeader(); ImageMetadata metadata = new();
using (this.webImageInfo = this.ReadVp8Info(true)) using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
{ {
return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
} }
} }
/// <summary> /// <summary>
/// Reads and skips over the image header. /// Reads and skips over the image header.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <returns>The file size in bytes.</returns> /// <returns>The file size in bytes.</returns>
private uint ReadImageHeader() private uint ReadImageHeader(BufferedReadStream stream)
{ {
// Skip FourCC header, we already know its a RIFF file at this point. // Skip FourCC header, we already know its a RIFF file at this point.
this.currentStream.Skip(4); stream.Skip(4);
// Read file size. // Read file size.
// The size of the file in bytes starting at offset 8. // The size of the file in bytes starting at offset 8.
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
// Skip 'WEBP' from the header. // Skip 'WEBP' from the header.
this.currentStream.Skip(4); stream.Skip(4);
return fileSize; return fileSize;
} }
@ -179,42 +161,43 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Reads information present in the image header, about the image content and how to decode the image. /// Reads information present in the image header, about the image content and how to decode the image.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param> /// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>Information about the webp image.</returns> /// <returns>Information about the webp image.</returns>
private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metadata, bool ignoreAlpha = false)
{ {
this.metadata = new ImageMetadata(); WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance);
this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
var features = new WebpFeatures(); WebpFeatures features = new();
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Vp8: case WebpChunkType.Vp8:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
case WebpChunkType.Vp8L: case WebpChunkType.Vp8L:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features); return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
case WebpChunkType.Vp8X: case WebpChunkType.Vp8X:
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(this.currentStream, this.buffer, features); WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features);
while (this.currentStream.Position < this.currentStream.Length) while (stream.Position < stream.Length)
{ {
chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
if (chunkType == WebpChunkType.Vp8) if (chunkType == WebpChunkType.Vp8)
{ {
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
} }
else if (chunkType == WebpChunkType.Vp8L) else if (chunkType == WebpChunkType.Vp8L)
{ {
this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features); webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
} }
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{ {
bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha); bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha);
if (isAnimationChunk) if (isAnimationChunk)
{ {
return webpInfos; return webpInfos;
@ -223,8 +206,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
else else
{ {
// Ignore unknown chunks. // Ignore unknown chunks.
uint chunkSize = this.ReadChunkSize(); uint chunkSize = this.ReadChunkSize(stream);
this.currentStream.Skip((int)chunkSize); stream.Skip((int)chunkSize);
} }
} }
@ -239,32 +222,39 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="chunkType">The chunk type.</param> /// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param> /// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param> /// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>true, if its a alpha chunk.</returns> /// <returns>true, if its a alpha chunk.</returns>
private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha) private bool ParseOptionalExtendedChunks(
BufferedReadStream stream,
ImageMetadata metadata,
WebpChunkType chunkType,
WebpFeatures features,
bool ignoreAlpha)
{ {
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Iccp: case WebpChunkType.Iccp:
this.ReadIccProfile(); this.ReadIccProfile(stream, metadata);
break; break;
case WebpChunkType.Exif: case WebpChunkType.Exif:
this.ReadExifProfile(); this.ReadExifProfile(stream, metadata);
break; break;
case WebpChunkType.Xmp: case WebpChunkType.Xmp:
this.ReadXmpProfile(); this.ReadXmpProfile(stream, metadata);
break; break;
case WebpChunkType.AnimationParameter: case WebpChunkType.AnimationParameter:
this.ReadAnimationParameters(features); this.ReadAnimationParameters(stream, features);
return true; return true;
case WebpChunkType.Alpha: case WebpChunkType.Alpha:
this.ReadAlphaData(features, ignoreAlpha); this.ReadAlphaData(stream, features, ignoreAlpha);
break; break;
default: default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -277,32 +267,34 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="features">The webp features.</param> /// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features) private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features)
{ {
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
{ {
return; return;
} }
long streamLength = this.currentStream.Length; long streamLength = stream.Length;
while (this.currentStream.Position < streamLength) while (stream.Position < streamLength)
{ {
// Read chunk header. // Read chunk header.
WebpChunkType chunkType = this.ReadChunkType(); WebpChunkType chunkType = this.ReadChunkType(stream);
if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null) if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null)
{ {
this.ReadExifProfile(); this.ReadExifProfile(stream, metadata);
} }
else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null) else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null)
{ {
this.ReadXmpProfile(); this.ReadXmpProfile(stream, metadata);
} }
else else
{ {
// Skip duplicate XMP or EXIF chunk. // Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize(); uint chunkLength = this.ReadChunkSize(stream);
this.currentStream.Skip((int)chunkLength); stream.Skip((int)chunkLength);
} }
} }
} }
@ -310,76 +302,80 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Reads the EXIF profile from the stream. /// Reads the EXIF profile from the stream.
/// </summary> /// </summary>
private void ReadExifProfile() /// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata)
{ {
uint exifChunkSize = this.ReadChunkSize(); uint exifChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata) if (this.skipMetadata)
{ {
this.currentStream.Skip((int)exifChunkSize); stream.Skip((int)exifChunkSize);
} }
else else
{ {
byte[] exifData = new byte[exifChunkSize]; byte[] exifData = new byte[exifChunkSize];
int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize); int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize) if (bytesRead != exifChunkSize)
{ {
// Ignore invalid chunk. // Ignore invalid chunk.
return; return;
} }
var profile = new ExifProfile(exifData); metadata.ExifProfile = new(exifData);
this.metadata.ExifProfile = profile;
} }
} }
/// <summary> /// <summary>
/// Reads the XMP profile the stream. /// Reads the XMP profile the stream.
/// </summary> /// </summary>
private void ReadXmpProfile() /// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata)
{ {
uint xmpChunkSize = this.ReadChunkSize(); uint xmpChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata) if (this.skipMetadata)
{ {
this.currentStream.Skip((int)xmpChunkSize); stream.Skip((int)xmpChunkSize);
} }
else else
{ {
byte[] xmpData = new byte[xmpChunkSize]; byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize); int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize) if (bytesRead != xmpChunkSize)
{ {
// Ignore invalid chunk. // Ignore invalid chunk.
return; return;
} }
var profile = new XmpProfile(xmpData); metadata.XmpProfile = new(xmpData);
this.metadata.XmpProfile = profile;
} }
} }
/// <summary> /// <summary>
/// Reads the ICCP chunk from the stream. /// Reads the ICCP chunk from the stream.
/// </summary> /// </summary>
private void ReadIccProfile() /// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata)
{ {
uint iccpChunkSize = this.ReadChunkSize(); uint iccpChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata) if (this.skipMetadata)
{ {
this.currentStream.Skip((int)iccpChunkSize); stream.Skip((int)iccpChunkSize);
} }
else else
{ {
byte[] iccpData = new byte[iccpChunkSize]; byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize) if (bytesRead != iccpChunkSize)
{ {
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
} }
var profile = new IccProfile(iccpData); IccProfile profile = new(iccpData);
if (profile.CheckIsValid()) if (profile.CheckIsValid())
{ {
this.metadata.IccProfile = profile; metadata.IccProfile = profile;
} }
} }
} }
@ -387,17 +383,18 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Reads the animation parameters chunk from the stream. /// Reads the animation parameters chunk from the stream.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The webp features.</param> /// <param name="features">The webp features.</param>
private void ReadAnimationParameters(WebpFeatures features) private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
{ {
features.Animation = true; features.Animation = true;
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
byte blue = (byte)this.currentStream.ReadByte(); byte blue = (byte)stream.ReadByte();
byte green = (byte)this.currentStream.ReadByte(); byte green = (byte)stream.ReadByte();
byte red = (byte)this.currentStream.ReadByte(); byte red = (byte)stream.ReadByte();
byte alpha = (byte)this.currentStream.ReadByte(); byte alpha = (byte)stream.ReadByte();
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha)); features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha));
int bytesRead = this.currentStream.Read(this.buffer, 0, 2); int bytesRead = stream.Read(this.buffer, 0, 2);
if (bytesRead != 2) if (bytesRead != 2)
{ {
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count"); WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count");
@ -409,22 +406,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Reads the alpha data chunk data from the stream. /// Reads the alpha data chunk data from the stream.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The features.</param> /// <param name="features">The features.</param>
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param> /// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha) private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha)
{ {
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
if (ignoreAlpha) if (ignoreAlpha)
{ {
this.currentStream.Skip((int)alphaChunkSize); stream.Skip((int)alphaChunkSize);
return; return;
} }
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); features.AlphaChunkHeader = (byte)stream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1); int alphaDataSize = (int)(alphaChunkSize - 1);
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize); this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.alphaData.GetSpan(); Span<byte> alphaData = this.alphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize); int bytesRead = stream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize) if (bytesRead != alphaDataSize)
{ {
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream"); WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream");
@ -434,15 +432,15 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Identifies the chunk type from the chunk. /// Identifies the chunk type from the chunk.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid. /// Thrown if the input stream is not valid.
/// </exception> /// </exception>
private WebpChunkType ReadChunkType() private WebpChunkType ReadChunkType(BufferedReadStream stream)
{ {
if (this.currentStream.Read(this.buffer, 0, 4) == 4) if (stream.Read(this.buffer, 0, 4) == 4)
{ {
var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return chunkType;
} }
throw new ImageFormatException("Invalid Webp data."); throw new ImageFormatException("Invalid Webp data.");
@ -452,10 +450,12 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
/// so the chunk size will be increased by 1 in those cases. /// so the chunk size will be increased by 1 in those cases.
/// </summary> /// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <returns>The chunk size in bytes.</returns> /// <returns>The chunk size in bytes.</returns>
private uint ReadChunkSize() /// <exception cref="ImageFormatException">Invalid data.</exception>
private uint ReadChunkSize(BufferedReadStream stream)
{ {
if (this.currentStream.Read(this.buffer, 0, 4) == 4) if (stream.Read(this.buffer, 0, 4) == 4)
{ {
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -82,7 +82,7 @@ public sealed class WebpEncoder : ImageEncoder
/// <inheritdoc/> /// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{ {
WebpEncoderCore encoder = new(this, image.GetMemoryAllocator()); WebpEncoderCore encoder = new(this, image.GetConfiguration());
encoder.Encode(image, stream, cancellationToken); encoder.Encode(image, stream, cancellationToken);
} }
} }

12
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -1,8 +1,6 @@
// 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.Advanced;
using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -81,16 +79,17 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
/// </summary> /// </summary>
private Configuration configuration; private readonly Configuration configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpEncoderCore"/> class. /// Initializes a new instance of the <see cref="WebpEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="encoder">The encoder with options.</param> /// <param name="encoder">The encoder with options.</param>
/// <param name="memoryAllocator">The memory manager.</param> /// <param name="configuration">The global configuration.</param>
public WebpEncoderCore(WebpEncoder encoder, MemoryAllocator memoryAllocator) public WebpEncoderCore(WebpEncoder encoder, Configuration configuration)
{ {
this.memoryAllocator = memoryAllocator; this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.alphaCompression = encoder.UseAlphaCompression; this.alphaCompression = encoder.UseAlphaCompression;
this.fileFormat = encoder.FileFormat; this.fileFormat = encoder.FileFormat;
this.quality = encoder.Quality; this.quality = encoder.Quality;
@ -117,7 +116,6 @@ internal sealed class WebpEncoderCore : 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();
bool lossless; bool lossless;
if (this.fileFormat is not null) if (this.fileFormat is not null)
{ {

9
src/ImageSharp/Formats/Webp/WebpImageInfo.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.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -36,7 +35,7 @@ internal class WebpImageInfo : IDisposable
/// <summary> /// <summary>
/// Gets or sets additional features present in a VP8X image. /// Gets or sets additional features present in a VP8X image.
/// </summary> /// </summary>
public WebpFeatures Features { get; set; } public WebpFeatures? Features { get; set; }
/// <summary> /// <summary>
/// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1.
@ -46,17 +45,17 @@ internal class WebpImageInfo : IDisposable
/// <summary> /// <summary>
/// Gets or sets the VP8 frame header. /// Gets or sets the VP8 frame header.
/// </summary> /// </summary>
public Vp8FrameHeader Vp8FrameHeader { get; set; } public Vp8FrameHeader? Vp8FrameHeader { get; set; }
/// <summary> /// <summary>
/// Gets or sets the VP8L bitreader. Will be <see langword="null"/>, if its not a lossless image. /// Gets or sets the VP8L bitreader. Will be <see langword="null"/>, if its not a lossless image.
/// </summary> /// </summary>
public Vp8LBitReader Vp8LBitReader { get; set; } public Vp8LBitReader? Vp8LBitReader { get; set; }
/// <summary> /// <summary>
/// Gets or sets the VP8 bitreader. Will be <see langword="null"/>, if its not a lossy image. /// Gets or sets the VP8 bitreader. Will be <see langword="null"/>, if its not a lossy image.
/// </summary> /// </summary>
public Vp8BitReader Vp8BitReader { get; set; } public Vp8BitReader? Vp8BitReader { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()

6
src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

@ -1,15 +1,21 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Webp; namespace SixLabors.ImageSharp.Formats.Webp;
internal static class WebpThrowHelper internal static class WebpThrowHelper
{ {
[DoesNotReturn]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[DoesNotReturn]
public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
[DoesNotReturn]
public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage);
[DoesNotReturn]
public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage); public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage);
} }

Loading…
Cancel
Save