Browse Source

#2231 First steps for removing nullable disable in webp

pull/2364/head
Stefan Nikolei 3 years ago
parent
commit
d9f72786ad
  1. 5
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  2. 3
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  3. 5
      src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs
  4. 4
      src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs
  5. 2
      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. 26
      src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
  11. 3
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  12. 25
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  13. 1
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  14. 168
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  15. 3
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  16. 9
      src/ImageSharp/Formats/Webp/WebpImageInfo.cs
  17. 6
      src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

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

3
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;
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary> /// </summary>
internal class AlphaEncoder : IDisposable internal class AlphaEncoder : IDisposable
{ {
private IMemoryOwner<byte> alphaData; private IMemoryOwner<byte>? alphaData;
/// <summary> /// <summary>
/// Encodes the alpha channel data. /// Encodes the alpha channel data.

5
src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.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 SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Webp.BitReader; namespace SixLabors.ImageSharp.Formats.Webp.BitReader;
@ -17,7 +17,7 @@ internal abstract class BitReaderBase : IDisposable
/// <summary> /// <summary>
/// Gets or sets the raw encoded image data. /// Gets or sets the raw encoded image data.
/// </summary> /// </summary>
public IMemoryOwner<byte> Data { get; set; } public IMemoryOwner<byte>? Data { get; set; }
/// <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,6 +25,7 @@ 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>
[MemberNotNull(nameof(Data))]
protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
{ {
this.Data = memoryAllocator.Allocate<byte>(bytesToRead); this.Data = memoryAllocator.Allocate<byte>(bytesToRead);

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

@ -186,7 +186,7 @@ internal class Vp8BitReader : BitReaderBase
{ {
if (this.pos < this.bufferMax) if (this.pos < this.bufferMax)
{ {
ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.Memory.Span.Slice((int)this.pos, 8)); ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data!.Memory.Span.Slice((int)this.pos, 8));
this.pos += BitsCount >> 3; this.pos += BitsCount >> 3;
ulong bits = ByteSwap64(inBits); ulong bits = ByteSwap64(inBits);
bits >>= 64 - BitsCount; bits >>= 64 - BitsCount;
@ -205,7 +205,7 @@ internal class Vp8BitReader : BitReaderBase
if (this.pos < this.bufferEnd) if (this.pos < this.bufferEnd)
{ {
this.bits += 8; this.bits += 8;
this.value = this.Data.Memory.Span[(int)this.pos++] | (this.value << 8); this.value = this.Data!.Memory.Span[(int)this.pos++] | (this.value << 8);
} }
else if (!this.eof) else if (!this.eof)
{ {

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

@ -193,7 +193,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);

26
src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs

@ -1,7 +1,7 @@
// 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.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless; namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -13,6 +13,18 @@ internal class ColorCache
{ {
private const uint HashMul = 0x1e35a7bdu; private const uint HashMul = 0x1e35a7bdu;
/// <summary>
/// Initializes a new instance of the <see cref="ColorCache"/> class.
/// </summary>
/// <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 color entries. /// Gets the color entries.
/// </summary> /// </summary>
@ -28,18 +40,6 @@ internal class ColorCache
/// </summary> /// </summary>
public int HashBits { get; private set; } public int HashBits { get; private set; }
/// <summary>
/// Initializes a new color cache.
/// </summary>
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
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.
/// </summary> /// </summary>

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

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;

168
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;
@ -44,32 +43,27 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary> /// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance. /// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary> /// </summary>
private ImageMetadata metadata; 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> /// <summary>
/// The webp specific metadata. /// The webp specific metadata.
/// </summary> /// </summary>
private WebpMetadata webpMetadata; 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,21 +82,20 @@ 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(); this.metadata = new ImageMetadata();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader(); uint fileSize = this.ReadImageHeader(stream);
using (this.webImageInfo = this.ReadVp8Info()) using (this.webImageInfo = this.ReadVp8Info(stream))
{ {
if (this.webImageInfo.Features is { Animation: true }) if (this.webImageInfo.Features is { Animation: true })
{ {
@ -131,7 +124,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
// 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, this.webImageInfo.Features);
} }
return image; return image;
@ -147,10 +140,8 @@ 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);
using (this.webImageInfo = this.ReadVp8Info(stream, true))
this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(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, this.metadata);
} }
@ -159,19 +150,20 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <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 +171,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="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, bool ignoreAlpha = false)
{ {
this.metadata = new ImageMetadata(); this.metadata = new ImageMetadata();
this.webpMetadata = this.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(); var features = new WebpFeatures();
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Vp8: case WebpChunkType.Vp8:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; this.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; this.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; this.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; this.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, chunkType, features, ignoreAlpha);
if (isAnimationChunk) if (isAnimationChunk)
{ {
return webpInfos; return webpInfos;
@ -223,8 +216,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 +232,33 @@ 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="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, WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha)
{ {
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Iccp: case WebpChunkType.Iccp:
this.ReadIccProfile(); this.ReadIccProfile(stream);
break; break;
case WebpChunkType.Exif: case WebpChunkType.Exif:
this.ReadExifProfile(); this.ReadExifProfile(stream);
break; break;
case WebpChunkType.Xmp: case WebpChunkType.Xmp:
this.ReadXmpProfile(); this.ReadXmpProfile(stream);
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 +271,33 @@ 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="features">The webp features.</param> /// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features) private void ParseOptionalChunks(BufferedReadStream stream, 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 && this.metadata!.ExifProfile == null)
{ {
this.ReadExifProfile(); this.ReadExifProfile(stream);
} }
else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null) else if (chunkType == WebpChunkType.Xmp && this.metadata!.XmpProfile == null)
{ {
this.ReadXmpProfile(); this.ReadXmpProfile(stream);
} }
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,17 +305,18 @@ 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>
private void ReadExifProfile(BufferedReadStream stream)
{ {
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.
@ -328,24 +324,25 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
} }
var profile = new ExifProfile(exifData); var profile = new ExifProfile(exifData);
this.metadata.ExifProfile = profile; 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>
private void ReadXmpProfile(BufferedReadStream stream)
{ {
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.
@ -353,24 +350,25 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
} }
var profile = new XmpProfile(xmpData); var profile = new XmpProfile(xmpData);
this.metadata.XmpProfile = profile; 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>
private void ReadIccProfile(BufferedReadStream stream)
{ {
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");
@ -379,7 +377,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
var profile = new IccProfile(iccpData); var profile = new IccProfile(iccpData);
if (profile.CheckIsValid()) if (profile.CheckIsValid())
{ {
this.metadata.IccProfile = profile; this.metadata!.IccProfile = profile;
} }
} }
} }
@ -387,17 +385,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 +408,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,12 +434,13 @@ 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); var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return chunkType; return chunkType;
@ -452,10 +453,11 @@ 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() 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;

3
src/ImageSharp/Formats/Webp/WebpEncoderCore.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.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -81,7 +80,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
/// </summary> /// </summary>
private Configuration configuration; private Configuration? configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebpEncoderCore"/> class. /// Initializes a new instance of the <see cref="WebpEncoderCore"/> class.

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