From d9f72786ad8576277fdc595e8b86dce8bce701ae Mon Sep 17 00:00:00 2001 From: Stefan Nikolei Date: Tue, 21 Feb 2023 14:52:55 +0100 Subject: [PATCH 1/4] #2231 First steps for removing nullable disable in webp --- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 5 +- src/ImageSharp/Formats/Webp/AlphaEncoder.cs | 3 +- .../Formats/Webp/BitReader/BitReaderBase.cs | 5 +- .../Formats/Webp/BitReader/Vp8BitReader.cs | 4 +- .../Formats/Webp/BitReader/Vp8LBitReader.cs | 2 +- .../Formats/Webp/BitWriter/BitWriterBase.cs | 4 +- .../Formats/Webp/BitWriter/Vp8BitWriter.cs | 38 ++-- .../Formats/Webp/BitWriter/Vp8LBitWriter.cs | 13 +- .../Webp/Lossless/BackwardReferenceEncoder.cs | 47 +++-- .../Formats/Webp/Lossless/ColorCache.cs | 26 +-- .../Webp/Lossless/WebpLosslessDecoder.cs | 3 +- .../Formats/Webp/WebpAnimationDecoder.cs | 25 ++- .../Formats/Webp/WebpChunkParsingUtils.cs | 1 - .../Formats/Webp/WebpDecoderCore.cs | 168 +++++++++--------- .../Formats/Webp/WebpEncoderCore.cs | 3 +- src/ImageSharp/Formats/Webp/WebpImageInfo.cs | 9 +- .../Formats/Webp/WebpThrowHelper.cs | 6 + 17 files changed, 178 insertions(+), 184 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index d45fa2a427..4a7b505363 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/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 @@ public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaCh /// /// Gets a value indicating whether the alpha channel uses compression. /// + [MemberNotNullWhen(true, nameof(LosslessDecoder))] private bool Compressed { get; } /// @@ -120,7 +121,7 @@ public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaCh /// /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. /// - private WebpLosslessDecoder LosslessDecoder { get; } + private WebpLosslessDecoder? LosslessDecoder { get; } /// /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs index 292c31f6a5..dd21365bc3 100644 --- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs +++ b/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; /// internal class AlphaEncoder : IDisposable { - private IMemoryOwner alphaData; + private IMemoryOwner? alphaData; /// /// Encodes the alpha channel data. diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs index 2586690fd3..fa121be4f5 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/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 /// /// Gets or sets the raw encoded image data. /// - public IMemoryOwner Data { get; set; } + public IMemoryOwner? Data { get; set; } /// /// Copies the raw encoded image data from the stream into a byte array. @@ -25,6 +25,7 @@ internal abstract class BitReaderBase : IDisposable /// The input stream. /// Number of bytes to read as indicated from the chunk size. /// Used for allocating memory during reading data from the stream. + [MemberNotNull(nameof(Data))] protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) { this.Data = memoryAllocator.Allocate(bytesToRead); diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs index 07bfcccd91..1fa4deda97 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs @@ -186,7 +186,7 @@ private void LoadNewBytes() { 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 @@ private void LoadFinalBytes() 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) { diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs index 057abf134a..df95f01f4a 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs @@ -193,7 +193,7 @@ public void FillBitWindow() [MethodImpl(InliningOptions.ShortMethod)] private void ShiftBytes() { - System.Span dataSpan = this.Data.Memory.Span; + System.Span dataSpan = this.Data!.Memory.Span; while (this.bitPos >= 8 && this.pos < this.len) { this.value >>= 8; diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 2df02727e0..02b1d0ab6a 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -123,7 +123,7 @@ protected static uint AlphaChunkSize(Span alphaBytes) /// The stream to write to. /// The metadata profile's bytes. /// The chuck type to write. - 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 @@ protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) /// The width of the image. /// The height of the image. /// Flag indicating, if a alpha channel is present. - 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) { diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 6fa02c1161..b83b44fa14 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/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 class. /// /// The expected size in bytes. - public Vp8BitWriter(int expectedSize) + /// The Vp8Encoder. + public Vp8BitWriter(int expectedSize, Vp8Encoder enc) : base(expectedSize) { this.range = 255 - 1; @@ -67,15 +67,9 @@ public Vp8BitWriter(int expectedSize) this.nbBits = -8; this.pos = 0; this.maxPos = 0; - } - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - /// The Vp8Encoder. - public Vp8BitWriter(int expectedSize, Vp8Encoder enc) - : this(expectedSize) => this.enc = enc; + this.enc = enc; + } /// public override int NumBytes() => (int)this.pos; @@ -414,9 +408,9 @@ private void Flush() /// Indicates, if the alpha data is compressed. 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 @@ public void WriteEncodedImageToStream( 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 @@ public void WriteEncodedImageToStream( 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 @@ private void WriteWebpHeaders( bool isVp8X, uint width, uint height, - ExifProfile exifProfile, - XmpProfile xmpProfile, - byte[] iccProfileBytes, + ExifProfile? exifProfile, + XmpProfile? xmpProfile, + byte[]? iccProfileBytes, bool hasAlpha, Span alphaData, bool alphaDataIsCompressed) diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index 42c1af8040..22bc195d64 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/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 @@ public override void Finish() /// The width of the image. /// The height of the image. /// Flag indicating, if a alpha channel is present. - 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) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index b3589b52c7..df19c26e0b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/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 @@ public static Vp8LBackwardRefs GetBackwardReferences( 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 @@ public static Vp8LBackwardRefs GetBackwardReferences( // 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 @@ private static int CalculateBestCacheSize(ReadOnlySpan bgra, int quality, 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 @@ private static void BackwardReferencesHashChainDistanceOnly( 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 @@ private static int TraceBackwards(Span distArray, int distArraySize) private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan bgra, int cacheBits, Span 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 @@ private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan= 0) { // useColorCache is true and color cache contains bgra[i] @@ -416,7 +414,7 @@ private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan bgra, - ColorCache colorCache, + ColorCache? colorCache, CostModel costModel, int idx, bool useColorCache, @@ -440,7 +438,7 @@ private static void AddSingleLiteralWithCostModel( { 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 @@ private static void AddSingleLiteralWithCostModel( double mul1 = 0.82; if (useColorCache) { - colorCache.Insert(color); + colorCache!.Insert(color); } costVal += costModel.GetLiteralCost(color) * mul1; @@ -469,10 +467,10 @@ private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan 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 @@ private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan 0; - var colorCache = new ColorCache(); + ColorCache? colorCache = null; if (useColorCache) { - colorCache.Init(cacheBits); + colorCache = new ColorCache(cacheBits); } refs.Refs.Clear(); @@ -757,7 +755,7 @@ private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan 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 @@ private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs ref } } - 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); diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs index 08ff60b69f..7e1c4e2fe1 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs +++ b/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; + /// + /// Initializes a new instance of the class. + /// + /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. + public ColorCache(int hashBits) + { + int hashSize = 1 << hashBits; + this.Colors = new uint[hashSize]; + this.HashBits = hashBits; + this.HashShift = 32 - hashBits; + } + /// /// Gets the color entries. /// @@ -28,18 +40,6 @@ internal class ColorCache /// public int HashBits { get; private set; } - /// - /// Initializes a new color cache. - /// - /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. - public void Init(int hashBits) - { - int hashSize = 1 << hashBits; - this.Colors = new uint[hashSize]; - this.HashBits = hashBits; - this.HashShift = 32 - hashBits; - } - /// /// Inserts a new color into the cache. /// diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index abfb67bc7e..84ddd4b785 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -158,10 +158,9 @@ public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int // 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 { diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 4c1c60591d..abaa85ef18 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/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 /// /// The abstract metadata. /// - private ImageMetadata metadata; + private ImageMetadata? metadata; /// /// The gif specific metadata. /// - private WebpMetadata webpMetadata; + private WebpMetadata? webpMetadata; /// /// The alpha data, if an ALPH chunk is present. /// - private IMemoryOwner alphaData; + private IMemoryOwner? alphaData; /// /// Initializes a new instance of the class. @@ -83,8 +82,8 @@ public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration confi public Image Decode(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize) where TPixel : unmanaged, IPixel { - Image image = null; - ImageFrame previousFrame = null; + Image? image = null; + ImageFrame? previousFrame = null; this.metadata = new ImageMetadata(); this.webpMetadata = this.metadata.GetWebpMetadata(); @@ -99,12 +98,12 @@ public Image Decode(BufferedReadStream stream, WebpFeatures feat 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 @@ public Image Decode(BufferedReadStream stream, WebpFeatures feat } } - return image; + return image!; } /// @@ -130,7 +129,7 @@ public Image Decode(BufferedReadStream stream, WebpFeatures feat /// The width of the image. /// The height of the image. /// The default background color of the canvas in. - private uint ReadFrame(BufferedReadStream stream, ref Image image, ref ImageFrame previousFrame, uint width, uint height, Color backgroundColor) + private uint ReadFrame(BufferedReadStream stream, ref Image? image, ref ImageFrame? previousFrame, uint width, uint height, Color backgroundColor) where TPixel : unmanaged, IPixel { AnimationFrameData frameData = this.ReadFrameHeader(stream); @@ -146,7 +145,7 @@ private uint ReadFrame(BufferedReadStream stream, ref Image imag chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); } - WebpImageInfo webpInfo = null; + WebpImageInfo? webpInfo = null; WebpFeatures features = new(); switch (chunkType) { @@ -163,7 +162,7 @@ private uint ReadFrame(BufferedReadStream stream, ref Image imag break; } - ImageFrame currentFrame = null; + ImageFrame? currentFrame = null; ImageFrame imageFrame; if (previousFrame is null) { @@ -175,7 +174,7 @@ private uint ReadFrame(BufferedReadStream stream, ref Image imag } 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); diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index bfe415a6e4..e4acdf311c 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/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; diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 29be86e22f..83298237dd 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/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 /// /// Gets the decoded by this decoder instance. /// - private ImageMetadata metadata; + private ImageMetadata? metadata; /// /// Gets or sets the alpha data, if an ALPH chunk is present. /// - private IMemoryOwner alphaData; + private IMemoryOwner? alphaData; /// /// Used for allocating memory during the decoding operations. /// private readonly MemoryAllocator memoryAllocator; - /// - /// The stream to decode from. - /// - private BufferedReadStream currentStream; - /// /// The webp specific metadata. /// - private WebpMetadata webpMetadata; + private WebpMetadata? webpMetadata; /// /// Information about the webp image. /// - private WebpImageInfo webImageInfo; + private WebpImageInfo? webImageInfo; /// /// Initializes a new instance of the class. @@ -88,21 +82,20 @@ public WebpDecoderCore(DecoderOptions options) public DecoderOptions Options { get; } /// - public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - Image image = null; + Image? 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 @@ public Image Decode(BufferedReadStream stream, CancellationToken // 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 @@ public Image Decode(BufferedReadStream stream, CancellationToken /// 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 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat /// /// Reads and skips over the image header. /// + /// The stream to decode from. /// The file size in bytes. - 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 @@ private uint ReadImageHeader() /// /// Reads information present in the image header, about the image content and how to decode the image. /// + /// The stream to decode from. /// For identify, the alpha data should not be read. /// Information about the webp image. - 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 @@ private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) 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 @@ private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) /// /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. /// + /// The stream to decode from. /// The chunk type. /// The webp image features. /// For identify, the alpha data should not be read. /// true, if its a alpha chunk. - 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 @@ private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures f /// /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. /// + /// The stream to decode from. /// The webp features. - 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 @@ private void ParseOptionalChunks(WebpFeatures features) /// /// Reads the EXIF profile from the stream. /// - private void ReadExifProfile() + /// The stream to decode from. + 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 @@ private void ReadExifProfile() } var profile = new ExifProfile(exifData); - this.metadata.ExifProfile = profile; + this.metadata!.ExifProfile = profile; } } /// /// Reads the XMP profile the stream. /// - private void ReadXmpProfile() + /// The stream to decode from. + 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 @@ private void ReadXmpProfile() } var profile = new XmpProfile(xmpData); - this.metadata.XmpProfile = profile; + this.metadata!.XmpProfile = profile; } } /// /// Reads the ICCP chunk from the stream. /// - private void ReadIccProfile() + /// The stream to decode from. + 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 @@ private void ReadIccProfile() var profile = new IccProfile(iccpData); if (profile.CheckIsValid()) { - this.metadata.IccProfile = profile; + this.metadata!.IccProfile = profile; } } } @@ -387,17 +385,18 @@ private void ReadIccProfile() /// /// Reads the animation parameters chunk from the stream. /// + /// The stream to decode from. /// The webp features. - 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 @@ private void ReadAnimationParameters(WebpFeatures features) /// /// Reads the alpha data chunk data from the stream. /// + /// The stream to decode from. /// The features. /// if set to true, skips the chunk data. - 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(alphaDataSize); Span 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 @@ private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha) /// /// Identifies the chunk type from the chunk. /// + /// The stream to decode from. /// /// Thrown if the input stream is not valid. /// - 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 @@ private WebpChunkType ReadChunkType() /// 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. /// + /// The stream to decode from. /// The chunk size in bytes. - 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; diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 68951e5ec0..8d707fed92 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/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 /// /// The global configuration. /// - private Configuration configuration; + private Configuration? configuration; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs index c41403211f..5f7301b262 100644 --- a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs +++ b/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 /// /// Gets or sets additional features present in a VP8X image. /// - public WebpFeatures Features { get; set; } + public WebpFeatures? Features { get; set; } /// /// 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 /// /// Gets or sets the VP8 frame header. /// - public Vp8FrameHeader Vp8FrameHeader { get; set; } + public Vp8FrameHeader? Vp8FrameHeader { get; set; } /// /// Gets or sets the VP8L bitreader. Will be , if its not a lossless image. /// - public Vp8LBitReader Vp8LBitReader { get; set; } + public Vp8LBitReader? Vp8LBitReader { get; set; } /// /// Gets or sets the VP8 bitreader. Will be , if its not a lossy image. /// - public Vp8BitReader Vp8BitReader { get; set; } + public Vp8BitReader? Vp8BitReader { get; set; } /// public void Dispose() diff --git a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs index 944cefa745..c633c52738 100644 --- a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs +++ b/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); } From a27df8a72145e884c86e18787be474094beb87dc Mon Sep 17 00:00:00 2001 From: Stefan Nikolei Date: Tue, 21 Feb 2023 17:14:23 +0100 Subject: [PATCH 2/4] Remove nullable disable from CostManager and CostInterval --- .../Formats/Webp/Lossless/CostInterval.cs | 5 ++--- .../Formats/Webp/Lossless/CostManager.cs | 19 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs index a63c786049..0cc4a30fd7 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Diagnostics; @@ -33,7 +32,7 @@ internal class CostInterval public int Index { get; set; } - public CostInterval Previous { get; set; } + public CostInterval? Previous { get; set; } - public CostInterval Next { get; set; } + public CostInterval? Next { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 7b9fdff247..e393c065ec 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.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; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless; /// internal sealed class CostManager : IDisposable { - private CostInterval head; + private CostInterval? head; private const int FreeIntervalsStartCount = 25; @@ -103,10 +102,10 @@ public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner distArr /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped. public void UpdateCostAtIndex(int i, bool doCleanIntervals) { - CostInterval current = this.head; + CostInterval? current = this.head; while (current != null && current.Start <= i) { - CostInterval next = current.Next; + CostInterval? next = current.Next; if (current.End <= i) { if (doCleanIntervals) @@ -155,7 +154,7 @@ public void PushInterval(double distanceCost, int position, int len) return; } - CostInterval interval = this.head; + CostInterval? interval = this.head; for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) { // Define the intersection of the ith interval with the new one. @@ -163,7 +162,7 @@ public void PushInterval(double distanceCost, int position, int len) int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); - CostInterval intervalNext; + CostInterval? intervalNext; for (; interval != null && interval.Start < end; interval = intervalNext) { intervalNext = interval.Next; @@ -225,7 +224,7 @@ public void PushInterval(double distanceCost, int position, int len) /// Pop an interval from the manager. /// /// The interval to remove. - private void PopInterval(CostInterval interval) + private void PopInterval(CostInterval? interval) { if (interval == null) { @@ -240,7 +239,7 @@ private void PopInterval(CostInterval interval) this.freeIntervals.Push(interval); } - private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) + private void InsertInterval(CostInterval? intervalIn, float cost, int position, int start, int end) { if (start >= end) { @@ -271,7 +270,7 @@ private void InsertInterval(CostInterval intervalIn, float cost, int position, i /// it was orphaned (which can be NULL), set it at the right place in the list /// of intervals using the start_ ordering and the previous interval as a hint. /// - private void PositionOrphanInterval(CostInterval current, CostInterval previous) + private void PositionOrphanInterval(CostInterval current, CostInterval? previous) { previous ??= this.head; @@ -292,7 +291,7 @@ private void PositionOrphanInterval(CostInterval current, CostInterval previous) /// /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. /// - private void ConnectIntervals(CostInterval prev, CostInterval next) + private void ConnectIntervals(CostInterval? prev, CostInterval? next) { if (prev != null) { From ea2c4fa5f892fed1aee7e228b618cd9d40f5fd9c Mon Sep 17 00:00:00 2001 From: Stefan Nikolei Date: Wed, 22 Feb 2023 15:04:46 +0100 Subject: [PATCH 3/4] Refactor BitReaderBase --- .../Formats/Webp/BitReader/BitReaderBase.cs | 18 +++++++++++------- .../Formats/Webp/BitReader/Vp8BitReader.cs | 8 ++++---- .../Formats/Webp/BitReader/Vp8LBitReader.cs | 5 ++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs index fa121be4f5..37997d3e29 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.BitReader; @@ -14,10 +13,14 @@ internal abstract class BitReaderBase : IDisposable { private bool isDisposed; + protected BitReaderBase(IMemoryOwner data) => this.Data = data; + + protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator) => this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator); + /// /// Gets or sets the raw encoded image data. /// - public IMemoryOwner? Data { get; set; } + public IMemoryOwner Data { get; set; } /// /// Copies the raw encoded image data from the stream into a byte array. @@ -25,12 +28,13 @@ internal abstract class BitReaderBase : IDisposable /// The input stream. /// Number of bytes to read as indicated from the chunk size. /// Used for allocating memory during reading data from the stream. - [MemberNotNull(nameof(Data))] - protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) + protected static IMemoryOwner ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) { - this.Data = memoryAllocator.Allocate(bytesToRead); - Span dataSpan = this.Data.Memory.Span; + IMemoryOwner data = memoryAllocator.Allocate(bytesToRead); + Span dataSpan = data.Memory.Span; input.Read(dataSpan[..bytesToRead], 0, bytesToRead); + + return data; } protected virtual void Dispose(bool disposing) @@ -42,7 +46,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { - this.Data?.Dispose(); + this.Data.Dispose(); } this.isDisposed = true; diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs index 1fa4deda97..7b64d8329c 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs @@ -57,12 +57,12 @@ internal class Vp8BitReader : BitReaderBase /// The partition length. /// Start index in the data array. Defaults to 0. public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) + : base(inputStream, (int)imageDataSize, memoryAllocator) { Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize)); this.ImageDataSize = imageDataSize; this.PartitionLength = partitionLength; - this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); this.InitBitreader(partitionLength, startPos); } @@ -73,8 +73,8 @@ public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memo /// The partition length. /// Start index in the data array. Defaults to 0. public Vp8BitReader(IMemoryOwner imageData, uint partitionLength, int startPos = 0) + : base(imageData) { - this.Data = imageData; this.ImageDataSize = (uint)imageData.Memory.Length; this.PartitionLength = partitionLength; this.InitBitreader(partitionLength, startPos); @@ -186,7 +186,7 @@ private void LoadNewBytes() { 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 @@ private void LoadFinalBytes() 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) { diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs index df95f01f4a..8da717545f 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs @@ -63,8 +63,8 @@ internal class Vp8LBitReader : BitReaderBase /// /// Lossless compressed image data. public Vp8LBitReader(IMemoryOwner data) + : base(data) { - this.Data = data; this.len = data.Memory.Length; this.value = 0; this.bitPos = 0; @@ -88,11 +88,10 @@ public Vp8LBitReader(IMemoryOwner data) /// The raw image data size in bytes. /// Used for allocating memory during reading data from the stream. public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) + : base(inputStream, (int)imageDataSize, memoryAllocator) { long length = imageDataSize; - this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); - this.len = length; this.value = 0; this.bitPos = 0; From 992361f9670db83fa8d56293f511d7aacc59384c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Feb 2023 23:43:49 +1000 Subject: [PATCH 4/4] Cleanup --- .gitattributes | 5 +- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 63 ++++++------ src/ImageSharp/Formats/Webp/AlphaEncoder.cs | 25 ++--- .../Formats/Webp/BitReader/BitReaderBase.cs | 10 +- .../Formats/Webp/Lossless/ColorCache.cs | 7 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 10 +- .../Formats/Webp/WebpDecoderCore.cs | 98 +++++++++---------- src/ImageSharp/Formats/Webp/WebpEncoder.cs | 2 +- .../Formats/Webp/WebpEncoderCore.cs | 11 +-- 9 files changed, 115 insertions(+), 116 deletions(-) diff --git a/.gitattributes b/.gitattributes index 2fdea90e17..ff4ec94087 100644 --- a/.gitattributes +++ b/.gitattributes @@ -64,18 +64,19 @@ # Set explicit file behavior to: # treat as text # normalize to Unix-style line endings and -# use a union merge when resoling conflicts +# use a union merge when resolving conflicts ############################################################################### *.csproj text eol=lf merge=union *.dbproj text eol=lf merge=union *.fsproj text eol=lf merge=union *.ncrunchproject text eol=lf merge=union *.vbproj text eol=lf merge=union +*.shproj text eol=lf merge=union ############################################################################### # Set explicit file behavior to: # treat as text # normalize to Windows-style line endings and -# use a union merge when resoling conflicts +# use a union merge when resolving conflicts ############################################################################### *.sln text eol=crlf merge=union ############################################################################### diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index 4a7b505363..8875ae1150 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -38,7 +38,7 @@ public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaCh this.LastRow = 0; int totalPixels = width * height; - var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); + WebpAlphaCompressionMethod compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression) { WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); @@ -59,7 +59,7 @@ public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaCh if (this.Compressed) { - var bitReader = new Vp8LBitReader(data); + Vp8LBitReader bitReader = new(data); this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration); this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); @@ -174,17 +174,14 @@ public void Decode() dst = dst[this.Width..]; } } + else if (this.Use8BDecode) + { + this.LosslessDecoder.DecodeAlphaData(this); + } else { - if (this.Use8BDecode) - { - this.LosslessDecoder.DecodeAlphaData(this); - } - else - { - this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); - this.ExtractAlphaRows(this.Vp8LDec); - } + this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); + this.ExtractAlphaRows(this.Vp8LDec); } } @@ -262,8 +259,7 @@ private void ExtractAlphaRows(Vp8LDecoder dec) { int numRowsToProcess = dec.Height; int width = dec.Width; - Span pixels = dec.Pixels.Memory.Span; - Span input = pixels; + Span input = dec.Pixels.Memory.Span; Span output = this.Alpha.Memory.Span; // Extract alpha (which is stored in the green plane). @@ -328,7 +324,7 @@ private static void HorizontalUnfilter(Span prev, Span input, Span(ref Unsafe.Add(ref srcRef, i)), 0); + Vector128 a0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, i)), 0); Vector128 a1 = Sse2.Add(a0.AsByte(), last.AsByte()); Vector128 a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1); Vector128 a3 = Sse2.Add(a1, a2); @@ -366,32 +362,29 @@ private static void VerticalUnfilter(Span prev, Span input, Span a0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i)); - Vector256 c0 = Avx2.Add(a0.AsByte(), b0.AsByte()); - ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); - Unsafe.As>(ref outputRef) = c0; - } + Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i)); + Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i)); + Vector256 c0 = Avx2.Add(a0.AsByte(), b0.AsByte()); + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); + Unsafe.As>(ref outputRef) = c0; + } - for (; i < width; i++) - { - dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]); - } + for (; i < width; i++) + { + dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]); } - else + } + else + { + for (int i = 0; i < width; i++) { - for (int i = 0; i < width; i++) - { - dst[i] = (byte)(prev[i] + input[i]); - } + dst[i] = (byte)(prev[i] + input[i]); } } } diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs index dd21365bc3..596715b205 100644 --- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs @@ -12,10 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Webp; /// /// Methods for encoding the alpha data of a VP8 image. /// -internal class AlphaEncoder : IDisposable +internal static class AlphaEncoder { - private IMemoryOwner? alphaData; - /// /// Encodes the alpha channel data. /// Data is either compressed as lossless webp image or uncompressed. @@ -28,12 +26,18 @@ internal class AlphaEncoder : IDisposable /// Indicates, if the data should be compressed with the lossless webp compression. /// The size in bytes of the alpha data. /// The encoded alpha data. - public IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool skipMetadata, bool compress, out int size) + public static IMemoryOwner EncodeAlpha( + Image image, + Configuration configuration, + MemoryAllocator memoryAllocator, + bool skipMetadata, + bool compress, + out int size) where TPixel : unmanaged, IPixel { int width = image.Width; int height = image.Height; - this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); + IMemoryOwner alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); if (compress) { @@ -54,15 +58,15 @@ public IMemoryOwner EncodeAlpha(Image image, Configuration // The transparency information will be stored in the green channel of the ARGB quadruplet. // The green channel is allowed extra transformation steps in the specification -- unlike the other channels, // that can improve compression. - using Image alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan()); + using Image alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan()); - size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData); + size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData); - return this.alphaData; + return alphaData; } size = width * height; - return this.alphaData; + return alphaData; } /// @@ -127,7 +131,4 @@ private static IMemoryOwner ExtractAlphaChannel(Image imag return alphaDataBuffer; } - - /// - public void Dispose() => this.alphaData?.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs index 37997d3e29..83f9e797ab 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs @@ -13,14 +13,16 @@ internal abstract class BitReaderBase : IDisposable { private bool isDisposed; - protected BitReaderBase(IMemoryOwner data) => this.Data = data; + protected BitReaderBase(IMemoryOwner data) + => this.Data = data; - protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator) => this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator); + protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator) + => this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator); /// - /// Gets or sets the raw encoded image data. + /// Gets the raw encoded image data. /// - public IMemoryOwner Data { get; set; } + public IMemoryOwner Data { get; } /// /// Copies the raw encoded image data from the stream into a byte array. diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs index 7e1c4e2fe1..e683fb5605 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossless; @@ -28,17 +27,17 @@ public ColorCache(int hashBits) /// /// Gets the color entries. /// - public uint[] Colors { get; private set; } + public uint[] Colors { get; } /// /// Gets the hash shift: 32 - hashBits. /// - public int HashShift { get; private set; } + public int HashShift { get; } /// /// Gets the hash bits. /// - public int HashBits { get; private set; } + public int HashBits { get; } /// /// Inserts a new color into the cache. diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index a5d1900b2a..16b4c827ef 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -348,12 +348,18 @@ public void Encode(Image image, Stream stream) // Extract and encode alpha channel data, if present. int alphaDataSize = 0; bool alphaCompressionSucceeded = false; - using AlphaEncoder alphaEncoder = new(); Span alphaData = Span.Empty; if (hasAlpha) { // TODO: This can potentially run in an separate task. - IMemoryOwner encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.skipMetadata, this.alphaCompression, out alphaDataSize); + using IMemoryOwner encodedAlphaData = AlphaEncoder.EncodeAlpha( + image, + this.configuration, + this.memoryAllocator, + this.skipMetadata, + this.alphaCompression, + out alphaDataSize); + alphaData = encodedAlphaData.GetSpan(); if (alphaDataSize < pixelCount) { diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 83298237dd..181f01632f 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -8,9 +8,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp; @@ -40,11 +38,6 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// private readonly uint maxFrames; - /// - /// Gets the decoded by this decoder instance. - /// - private ImageMetadata? metadata; - /// /// Gets or sets the alpha data, if an ALPH chunk is present. /// @@ -55,11 +48,6 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable /// private readonly MemoryAllocator memoryAllocator; - /// - /// The webp specific metadata. - /// - private WebpMetadata? webpMetadata; - /// /// Information about the webp image. /// @@ -91,15 +79,15 @@ public Image Decode(BufferedReadStream stream, CancellationToken Image? image = null; try { - this.metadata = new ImageMetadata(); + ImageMetadata metadata = new(); uint fileSize = this.ReadImageHeader(stream); - using (this.webImageInfo = this.ReadVp8Info(stream)) + using (this.webImageInfo = this.ReadVp8Info(stream, metadata)) { if (this.webImageInfo.Features is { Animation: true }) { - using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames); + using WebpAnimationDecoder animationDecoder = new(this.memoryAllocator, this.configuration, this.maxFrames); return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); } @@ -108,23 +96,23 @@ public Image Decode(BufferedReadStream stream, CancellationToken WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); } - image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); + image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); if (this.webImageInfo.IsLossless) { - var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + WebpLosslessDecoder losslessDecoder = new(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); losslessDecoder.Decode(pixels, image.Width, image.Height); } else { - var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + WebpLossyDecoder lossyDecoder = new(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); } // There can be optional chunks after the image data, like EXIF and XMP. if (this.webImageInfo.Features != null) { - this.ParseOptionalChunks(stream, this.webImageInfo.Features); + this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features); } return image; @@ -141,9 +129,11 @@ public Image Decode(BufferedReadStream stream, CancellationToken public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.ReadImageHeader(stream); - using (this.webImageInfo = this.ReadVp8Info(stream, true)) + + ImageMetadata metadata = new(); + using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true)) { - return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); + return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata); } } @@ -172,23 +162,23 @@ private uint ReadImageHeader(BufferedReadStream stream) /// Reads information present in the image header, about the image content and how to decode the image. /// /// The stream to decode from. + /// The image metadata. /// For identify, the alpha data should not be read. /// Information about the webp image. - private WebpImageInfo ReadVp8Info(BufferedReadStream stream, bool ignoreAlpha = false) + private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metadata, bool ignoreAlpha = false) { - this.metadata = new ImageMetadata(); - this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance); + WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance); WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); - var features = new WebpFeatures(); + WebpFeatures features = new(); switch (chunkType) { case WebpChunkType.Vp8: - this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; + webpMetadata.FileFormat = WebpFileFormatType.Lossy; return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); case WebpChunkType.Vp8L: - this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; + webpMetadata.FileFormat = WebpFileFormatType.Lossless; return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); case WebpChunkType.Vp8X: WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features); @@ -197,17 +187,17 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, bool ignoreAlpha = chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); if (chunkType == WebpChunkType.Vp8) { - this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; + webpMetadata.FileFormat = WebpFileFormatType.Lossy; webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); } else if (chunkType == WebpChunkType.Vp8L) { - this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; + webpMetadata.FileFormat = WebpFileFormatType.Lossless; webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); } else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) { - bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, chunkType, features, ignoreAlpha); + bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha); if (isAnimationChunk) { return webpInfos; @@ -233,24 +223,30 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, bool ignoreAlpha = /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. /// /// The stream to decode from. + /// The image metadata. /// The chunk type. /// The webp image features. /// For identify, the alpha data should not be read. /// true, if its a alpha chunk. - private bool ParseOptionalExtendedChunks(BufferedReadStream stream, WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha) + private bool ParseOptionalExtendedChunks( + BufferedReadStream stream, + ImageMetadata metadata, + WebpChunkType chunkType, + WebpFeatures features, + bool ignoreAlpha) { switch (chunkType) { case WebpChunkType.Iccp: - this.ReadIccProfile(stream); + this.ReadIccProfile(stream, metadata); break; case WebpChunkType.Exif: - this.ReadExifProfile(stream); + this.ReadExifProfile(stream, metadata); break; case WebpChunkType.Xmp: - this.ReadXmpProfile(stream); + this.ReadXmpProfile(stream, metadata); break; case WebpChunkType.AnimationParameter: @@ -272,8 +268,9 @@ private bool ParseOptionalExtendedChunks(BufferedReadStream stream, WebpChunkTyp /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. /// /// The stream to decode from. + /// The image metadata. /// The webp features. - private void ParseOptionalChunks(BufferedReadStream stream, WebpFeatures features) + private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features) { if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) { @@ -285,13 +282,13 @@ private void ParseOptionalChunks(BufferedReadStream stream, WebpFeatures feature { // Read chunk header. WebpChunkType chunkType = this.ReadChunkType(stream); - if (chunkType == WebpChunkType.Exif && this.metadata!.ExifProfile == null) + if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null) { - this.ReadExifProfile(stream); + this.ReadExifProfile(stream, metadata); } - else if (chunkType == WebpChunkType.Xmp && this.metadata!.XmpProfile == null) + else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null) { - this.ReadXmpProfile(stream); + this.ReadXmpProfile(stream, metadata); } else { @@ -306,7 +303,8 @@ private void ParseOptionalChunks(BufferedReadStream stream, WebpFeatures feature /// Reads the EXIF profile from the stream. /// /// The stream to decode from. - private void ReadExifProfile(BufferedReadStream stream) + /// The image metadata. + private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata) { uint exifChunkSize = this.ReadChunkSize(stream); if (this.skipMetadata) @@ -323,8 +321,7 @@ private void ReadExifProfile(BufferedReadStream stream) return; } - var profile = new ExifProfile(exifData); - this.metadata!.ExifProfile = profile; + metadata.ExifProfile = new(exifData); } } @@ -332,7 +329,8 @@ private void ReadExifProfile(BufferedReadStream stream) /// Reads the XMP profile the stream. /// /// The stream to decode from. - private void ReadXmpProfile(BufferedReadStream stream) + /// The image metadata. + private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata) { uint xmpChunkSize = this.ReadChunkSize(stream); if (this.skipMetadata) @@ -349,8 +347,7 @@ private void ReadXmpProfile(BufferedReadStream stream) return; } - var profile = new XmpProfile(xmpData); - this.metadata!.XmpProfile = profile; + metadata.XmpProfile = new(xmpData); } } @@ -358,7 +355,8 @@ private void ReadXmpProfile(BufferedReadStream stream) /// Reads the ICCP chunk from the stream. /// /// The stream to decode from. - private void ReadIccProfile(BufferedReadStream stream) + /// The image metadata. + private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata) { uint iccpChunkSize = this.ReadChunkSize(stream); if (this.skipMetadata) @@ -374,10 +372,10 @@ private void ReadIccProfile(BufferedReadStream stream) WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); } - var profile = new IccProfile(iccpData); + IccProfile profile = new(iccpData); if (profile.CheckIsValid()) { - this.metadata!.IccProfile = profile; + metadata.IccProfile = profile; } } } @@ -442,8 +440,7 @@ private WebpChunkType ReadChunkType(BufferedReadStream stream) { if (stream.Read(this.buffer, 0, 4) == 4) { - var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); - return chunkType; + return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); } throw new ImageFormatException("Invalid Webp data."); @@ -455,6 +452,7 @@ private WebpChunkType ReadChunkType(BufferedReadStream stream) /// /// The stream to decode from. /// The chunk size in bytes. + /// Invalid data. private uint ReadChunkSize(BufferedReadStream stream) { if (stream.Read(this.buffer, 0, 4) == 4) diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index e314d38017..bd8303f1c8 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -82,7 +82,7 @@ public sealed class WebpEncoder : ImageEncoder /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { - WebpEncoderCore encoder = new(this, image.GetMemoryAllocator()); + WebpEncoderCore encoder = new(this, image.GetConfiguration()); encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 8d707fed92..33189ba845 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; @@ -80,16 +79,17 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals /// /// The global configuration. /// - private Configuration? configuration; + private readonly Configuration configuration; /// /// Initializes a new instance of the class. /// /// The encoder with options. - /// The memory manager. - public WebpEncoderCore(WebpEncoder encoder, MemoryAllocator memoryAllocator) + /// The global configuration. + public WebpEncoderCore(WebpEncoder encoder, Configuration configuration) { - this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; this.alphaCompression = encoder.UseAlphaCompression; this.fileFormat = encoder.FileFormat; this.quality = encoder.Quality; @@ -116,7 +116,6 @@ public void Encode(Image image, Stream stream, CancellationToken Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - this.configuration = image.GetConfiguration(); bool lossless; if (this.fileFormat is not null) {