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.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -110,6 +110,7 @@ internal class AlphaDecoder : IDisposable
/// <summary>
/// Gets a value indicating whether the alpha channel uses compression.
/// </summary>
[MemberNotNullWhen(true, nameof(LosslessDecoder))]
private bool Compressed { get; }
/// <summary>
@ -120,7 +121,7 @@ internal class AlphaDecoder : IDisposable
/// <summary>
/// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
/// </summary>
private WebpLosslessDecoder LosslessDecoder { get; }
private WebpLosslessDecoder? LosslessDecoder { get; }
/// <summary>
/// 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.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// </summary>
internal class AlphaEncoder : IDisposable
{
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// Encodes the alpha channel data.

5
src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Webp.BitReader;
@ -17,7 +17,7 @@ internal abstract class BitReaderBase : IDisposable
/// <summary>
/// Gets or sets the raw encoded image data.
/// </summary>
public IMemoryOwner<byte> Data { get; set; }
public IMemoryOwner<byte>? Data { get; set; }
/// <summary>
/// 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="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>
[MemberNotNull(nameof(Data))]
protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
{
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)
{
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;
ulong bits = ByteSwap64(inBits);
bits >>= 64 - BitsCount;
@ -205,7 +205,7 @@ internal class Vp8BitReader : BitReaderBase
if (this.pos < this.bufferEnd)
{
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)
{

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

@ -193,7 +193,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)]
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)
{
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="metadataBytes">The metadata profile's bytes.</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));
@ -207,7 +207,7 @@ internal abstract class BitWriterBase
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
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)
{

38
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -58,7 +57,8 @@ internal class Vp8BitWriter : BitWriterBase
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary>
/// <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)
{
this.range = 255 - 1;
@ -67,15 +67,9 @@ internal class Vp8BitWriter : BitWriterBase
this.nbBits = -8;
this.pos = 0;
this.maxPos = 0;
}
/// <summary>
/// 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;
this.enc = enc;
}
/// <inheritdoc/>
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>
public void WriteEncodedImageToStream(
Stream stream,
ExifProfile exifProfile,
XmpProfile xmpProfile,
IccProfile iccProfile,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
IccProfile? iccProfile,
uint width,
uint height,
bool hasAlpha,
@ -424,22 +418,22 @@ internal class Vp8BitWriter : BitWriterBase
bool alphaDataIsCompressed)
{
bool isVp8X = false;
byte[] exifBytes = null;
byte[] xmpBytes = null;
byte[] iccProfileBytes = null;
byte[]? exifBytes = null;
byte[]? xmpBytes = null;
byte[]? iccProfileBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes);
riffSize += MetadataChunkSize(exifBytes!);
}
if (xmpProfile != null)
{
isVp8X = true;
xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes);
riffSize += MetadataChunkSize(xmpBytes!);
}
if (iccProfile != null)
@ -465,7 +459,7 @@ internal class Vp8BitWriter : BitWriterBase
int mbSize = this.enc.Mbw * this.enc.Mbh;
int expectedSize = mbSize * 7 / 8;
var bitWriterPartZero = new Vp8BitWriter(expectedSize);
Vp8BitWriter bitWriterPartZero = new(expectedSize, this.enc);
// Partition #0 with header and partition sizes.
uint size0 = this.GeneratePartition0(bitWriterPartZero);
@ -676,9 +670,9 @@ internal class Vp8BitWriter : BitWriterBase
bool isVp8X,
uint width,
uint height,
ExifProfile exifProfile,
XmpProfile xmpProfile,
byte[] iccProfileBytes,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
byte[]? iccProfileBytes,
bool hasAlpha,
Span<byte> alphaData,
bool alphaDataIsCompressed)

13
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
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="height">The height of the image.</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;
byte[] exifBytes = null;
byte[] xmpBytes = null;
byte[] iccBytes = null;
byte[]? exifBytes = null;
byte[]? xmpBytes = null;
byte[]? iccBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes);
riffSize += MetadataChunkSize(exifBytes!);
}
if (xmpProfile != null)
{
isVp8X = true;
xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes);
riffSize += MetadataChunkSize(xmpBytes!);
}
if (iccProfile != null)

47
src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -50,7 +49,7 @@ internal static class BackwardReferenceEncoder
int lz77TypeBest = 0;
double bitCostBest = -1;
int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null;
Vp8LHashChain? hashChainBox = null;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
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).
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);
var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
@ -140,8 +139,7 @@ internal static class BackwardReferenceEncoder
for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++)
{
histos[i] = new Vp8LHistogram(paletteCodeBits: i);
colorCache[i] = new ColorCache();
colorCache[i].Init(i);
colorCache[i] = new ColorCache(i);
}
// Find the cacheBits giving the lowest entropy.
@ -274,11 +272,11 @@ internal static class BackwardReferenceEncoder
double offsetCost = -1;
int firstOffsetIsConstant = -1; // initialized with 'impossible' value.
int reach = 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
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)
{
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
int i = 0;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
backwardRefs.Refs.Clear();
@ -396,7 +394,7 @@ internal static class BackwardReferenceEncoder
{
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
{
PixOrCopy v;
int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1;
int idx = useColorCache ? colorCache!.Contains(bgra[i]) : -1;
if (idx >= 0)
{
// useColorCache is true and color cache contains bgra[i]
@ -416,7 +414,7 @@ internal static class BackwardReferenceEncoder
{
if (useColorCache)
{
colorCache.Insert(bgra[i]);
colorCache!.Insert(bgra[i]);
}
v = PixOrCopy.CreateLiteral(bgra[i]);
@ -430,7 +428,7 @@ internal static class BackwardReferenceEncoder
private static void AddSingleLiteralWithCostModel(
ReadOnlySpan<uint> bgra,
ColorCache colorCache,
ColorCache? colorCache,
CostModel costModel,
int idx,
bool useColorCache,
@ -440,7 +438,7 @@ internal static class BackwardReferenceEncoder
{
double costVal = prevCost;
uint color = bgra[idx];
int ix = useColorCache ? colorCache.Contains(color) : -1;
int ix = useColorCache ? colorCache!.Contains(color) : -1;
if (ix >= 0)
{
double mul0 = 0.68;
@ -451,7 +449,7 @@ internal static class BackwardReferenceEncoder
double mul1 = 0.82;
if (useColorCache)
{
colorCache.Insert(color);
colorCache!.Insert(color);
}
costVal += costModel.GetLiteralCost(color) * mul1;
@ -469,10 +467,10 @@ internal static class BackwardReferenceEncoder
int iLastCheck = -1;
bool useColorCache = cacheBits > 0;
int pixCount = xSize * ySize;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
refs.Refs.Clear();
@ -529,7 +527,7 @@ internal static class BackwardReferenceEncoder
{
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;
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
refs.Refs.Clear();
@ -757,7 +755,7 @@ internal static class BackwardReferenceEncoder
{
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)
{
int pixelIndex = 0;
var colorCache = new ColorCache();
colorCache.Init(cacheBits);
ColorCache colorCache = new(cacheBits);
for (int idx = 0; idx < refs.Refs.Count; 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;
if (useColorCache)
{
int key = colorCache.GetIndex(pixel);
int key = colorCache!.GetIndex(pixel);
if (colorCache.Lookup(key) == pixel)
{
v = PixOrCopy.CreateCacheIdx(key);

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -13,6 +13,18 @@ internal class ColorCache
{
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>
/// Gets the color entries.
/// </summary>
@ -28,18 +40,6 @@ internal class ColorCache
/// </summary>
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>
/// Inserts a new color into the cache.
/// </summary>

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

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

25
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Runtime.CompilerServices;
@ -46,17 +45,17 @@ internal class WebpAnimationDecoder : IDisposable
/// <summary>
/// The abstract metadata.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// The gif specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
private WebpMetadata? webpMetadata;
/// <summary>
/// The alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// 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)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
Image<TPixel>? image = null;
ImageFrame<TPixel>? previousFrame = null;
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetWebpMetadata();
@ -99,12 +98,12 @@ internal class WebpAnimationDecoder : IDisposable
switch (chunkType)
{
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;
break;
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image.Metadata, false, this.buffer);
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, this.buffer);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data");
@ -117,7 +116,7 @@ internal class WebpAnimationDecoder : IDisposable
}
}
return image;
return image!;
}
/// <summary>
@ -130,7 +129,7 @@ internal class WebpAnimationDecoder : IDisposable
/// <param name="width">The width 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>
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>
{
AnimationFrameData frameData = this.ReadFrameHeader(stream);
@ -146,7 +145,7 @@ internal class WebpAnimationDecoder : IDisposable
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
}
WebpImageInfo webpInfo = null;
WebpImageInfo? webpInfo = null;
WebpFeatures features = new();
switch (chunkType)
{
@ -163,7 +162,7 @@ internal class WebpAnimationDecoder : IDisposable
break;
}
ImageFrame<TPixel> currentFrame = null;
ImageFrame<TPixel>? currentFrame = null;
ImageFrame<TPixel> imageFrame;
if (previousFrame is null)
{
@ -175,7 +174,7 @@ internal class WebpAnimationDecoder : IDisposable
}
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);

1
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.BitReader;

168
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
@ -44,32 +43,27 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// Used for allocating memory during the decoding operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
private BufferedReadStream currentStream;
/// <summary>
/// The webp specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
private WebpMetadata? webpMetadata;
/// <summary>
/// Information about the webp image.
/// </summary>
private WebpImageInfo webImageInfo;
private WebpImageInfo? webImageInfo;
/// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
@ -88,21 +82,20 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
public DecoderOptions Options { get; }
/// <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 />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
Image<TPixel>? image = null;
try
{
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 })
{
@ -131,7 +124,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
// There can be optional chunks after the image data, like EXIF and XMP.
if (this.webImageInfo.Features != null)
{
this.ParseOptionalChunks(this.webImageInfo.Features);
this.ParseOptionalChunks(stream, this.webImageInfo.Features);
}
return image;
@ -147,10 +140,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;
this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(true))
this.ReadImageHeader(stream);
using (this.webImageInfo = this.ReadVp8Info(stream, true))
{
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>
/// Reads and skips over the image header.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <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.
this.currentStream.Skip(4);
stream.Skip(4);
// Read file size.
// 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.
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
// Skip 'WEBP' from the header.
this.currentStream.Skip(4);
stream.Skip(4);
return fileSize;
}
@ -179,42 +171,43 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads information present in the image header, about the image content and how to decode the image.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <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.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();
switch (chunkType)
{
case WebpChunkType.Vp8:
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:
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:
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(this.currentStream, this.buffer, features);
while (this.currentStream.Position < this.currentStream.Length)
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features);
while (stream.Position < stream.Length)
{
chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
if (chunkType == WebpChunkType.Vp8)
{
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)
{
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))
{
bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha);
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, chunkType, features, ignoreAlpha);
if (isAnimationChunk)
{
return webpInfos;
@ -223,8 +216,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
else
{
// Ignore unknown chunks.
uint chunkSize = this.ReadChunkSize();
this.currentStream.Skip((int)chunkSize);
uint chunkSize = this.ReadChunkSize(stream);
stream.Skip((int)chunkSize);
}
}
@ -239,32 +232,33 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <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)
{
case WebpChunkType.Iccp:
this.ReadIccProfile();
this.ReadIccProfile(stream);
break;
case WebpChunkType.Exif:
this.ReadExifProfile();
this.ReadExifProfile(stream);
break;
case WebpChunkType.Xmp:
this.ReadXmpProfile();
this.ReadXmpProfile(stream);
break;
case WebpChunkType.AnimationParameter:
this.ReadAnimationParameters(features);
this.ReadAnimationParameters(stream, features);
return true;
case WebpChunkType.Alpha:
this.ReadAlphaData(features, ignoreAlpha);
this.ReadAlphaData(stream, features, ignoreAlpha);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -277,32 +271,33 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
/// </summary>
/// <param name="stream">The stream to decode from.</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))
{
return;
}
long streamLength = this.currentStream.Length;
while (this.currentStream.Position < streamLength)
long streamLength = stream.Length;
while (stream.Position < streamLength)
{
// Read chunk header.
WebpChunkType chunkType = this.ReadChunkType();
if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null)
WebpChunkType chunkType = this.ReadChunkType(stream);
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
{
// Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize();
this.currentStream.Skip((int)chunkLength);
uint chunkLength = this.ReadChunkSize(stream);
stream.Skip((int)chunkLength);
}
}
}
@ -310,17 +305,18 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the EXIF profile from the stream.
/// </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)
{
this.currentStream.Skip((int)exifChunkSize);
stream.Skip((int)exifChunkSize);
}
else
{
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)
{
// Ignore invalid chunk.
@ -328,24 +324,25 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
}
var profile = new ExifProfile(exifData);
this.metadata.ExifProfile = profile;
this.metadata!.ExifProfile = profile;
}
}
/// <summary>
/// Reads the XMP profile the stream.
/// </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)
{
this.currentStream.Skip((int)xmpChunkSize);
stream.Skip((int)xmpChunkSize);
}
else
{
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)
{
// Ignore invalid chunk.
@ -353,24 +350,25 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
}
var profile = new XmpProfile(xmpData);
this.metadata.XmpProfile = profile;
this.metadata!.XmpProfile = profile;
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </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)
{
this.currentStream.Skip((int)iccpChunkSize);
stream.Skip((int)iccpChunkSize);
}
else
{
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)
{
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);
if (profile.CheckIsValid())
{
this.metadata.IccProfile = profile;
this.metadata!.IccProfile = profile;
}
}
}
@ -387,17 +385,18 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the animation parameters chunk from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The webp features.</param>
private void ReadAnimationParameters(WebpFeatures features)
private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
{
features.Animation = true;
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
byte blue = (byte)this.currentStream.ReadByte();
byte green = (byte)this.currentStream.ReadByte();
byte red = (byte)this.currentStream.ReadByte();
byte alpha = (byte)this.currentStream.ReadByte();
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
byte blue = (byte)stream.ReadByte();
byte green = (byte)stream.ReadByte();
byte red = (byte)stream.ReadByte();
byte alpha = (byte)stream.ReadByte();
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)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count");
@ -409,22 +408,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the alpha data chunk data from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The features.</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)
{
this.currentStream.Skip((int)alphaChunkSize);
stream.Skip((int)alphaChunkSize);
return;
}
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
features.AlphaChunkHeader = (byte)stream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1);
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.alphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize);
int bytesRead = stream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream");
@ -434,12 +434,13 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </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 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,
/// so the chunk size will be increased by 1 in those cases.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <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);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -81,7 +80,7 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
private Configuration? configuration;
/// <summary>
/// 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.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -36,7 +35,7 @@ internal class WebpImageInfo : IDisposable
/// <summary>
/// Gets or sets additional features present in a VP8X image.
/// </summary>
public WebpFeatures Features { get; set; }
public WebpFeatures? Features { get; set; }
/// <summary>
/// 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>
/// Gets or sets the VP8 frame header.
/// </summary>
public Vp8FrameHeader Vp8FrameHeader { get; set; }
public Vp8FrameHeader? Vp8FrameHeader { get; set; }
/// <summary>
/// Gets or sets the VP8L bitreader. Will be <see langword="null"/>, if its not a lossless image.
/// </summary>
public Vp8LBitReader Vp8LBitReader { get; set; }
public Vp8LBitReader? Vp8LBitReader { get; set; }
/// <summary>
/// Gets or sets the VP8 bitreader. Will be <see langword="null"/>, if its not a lossy image.
/// </summary>
public Vp8BitReader Vp8BitReader { get; set; }
public Vp8BitReader? Vp8BitReader { get; set; }
/// <inheritdoc/>
public void Dispose()

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

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

Loading…
Cancel
Save