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 d45fa2a427..8875ae1150 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; @@ -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); @@ -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. @@ -173,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); } } @@ -261,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). @@ -327,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); @@ -365,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 292c31f6a5..596715b205 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; @@ -13,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. @@ -29,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) { @@ -55,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; } /// @@ -128,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 2586690fd3..83f9e797ab 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.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,10 +13,16 @@ 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. + /// 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. @@ -25,11 +30,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. - 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) @@ -41,7 +48,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 07bfcccd91..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); diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs index 057abf134a..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; @@ -193,7 +192,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..e683fb5605 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs @@ -1,6 +1,5 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable using System.Runtime.CompilerServices; @@ -14,31 +13,31 @@ internal class ColorCache private const uint HashMul = 0x1e35a7bdu; /// - /// Gets the color entries. + /// Initializes a new instance of the class. /// - public uint[] Colors { get; private set; } + /// 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 hash shift: 32 - hashBits. + /// Gets the color entries. /// - public int HashShift { get; private set; } + public uint[] Colors { get; } /// - /// Gets the hash bits. + /// Gets the hash shift: 32 - hashBits. /// - public int HashBits { get; private set; } + public int HashShift { get; } /// - /// Initializes a new color cache. + /// Gets the hash bits. /// - /// 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; - } + public int HashBits { get; } /// /// Inserts a new color into the cache. 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) { 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/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/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..181f01632f 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; @@ -9,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; @@ -41,35 +38,20 @@ 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. /// - 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; - /// /// Information about the webp image. /// - private WebpImageInfo webImageInfo; + private WebpImageInfo? webImageInfo; /// /// Initializes a new instance of the class. @@ -88,25 +70,24 @@ 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; + ImageMetadata metadata = new(); - uint fileSize = this.ReadImageHeader(); + uint fileSize = this.ReadImageHeader(stream); - using (this.webImageInfo = this.ReadVp8Info()) + using (this.webImageInfo = this.ReadVp8Info(stream, metadata)) { if (this.webImageInfo.Features is { Animation: true }) { - 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); } @@ -115,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(this.webImageInfo.Features); + this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features); } return image; @@ -147,31 +128,32 @@ public Image Decode(BufferedReadStream stream, CancellationToken /// public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.currentStream = stream; + this.ReadImageHeader(stream); - this.ReadImageHeader(); - using (this.webImageInfo = this.ReadVp8Info(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); } } /// /// 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 +161,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. + /// The image metadata. /// 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, 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(this.currentStream, this.buffer); + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); - var features = new WebpFeatures(); + WebpFeatures features = new(); switch (chunkType) { case WebpChunkType.Vp8: - this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; - return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); + webpMetadata.FileFormat = WebpFileFormatType.Lossy; + 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); + webpMetadata.FileFormat = WebpFileFormatType.Lossless; + 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); + webpMetadata.FileFormat = WebpFileFormatType.Lossy; + 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); + webpMetadata.FileFormat = WebpFileFormatType.Lossless; + 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, metadata, chunkType, features, ignoreAlpha); if (isAnimationChunk) { return webpInfos; @@ -223,8 +206,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 +222,39 @@ 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 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(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(); + this.ReadIccProfile(stream, metadata); break; case WebpChunkType.Exif: - this.ReadExifProfile(); + this.ReadExifProfile(stream, metadata); break; case WebpChunkType.Xmp: - this.ReadXmpProfile(); + this.ReadXmpProfile(stream, metadata); 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 +267,34 @@ 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 image metadata. /// The webp features. - private void ParseOptionalChunks(WebpFeatures features) + private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, 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 && metadata.ExifProfile == null) { - this.ReadExifProfile(); + this.ReadExifProfile(stream, metadata); } - else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null) + else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null) { - this.ReadXmpProfile(); + this.ReadXmpProfile(stream, metadata); } else { // 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,76 +302,80 @@ private void ParseOptionalChunks(WebpFeatures features) /// /// Reads the EXIF profile from the stream. /// - private void ReadExifProfile() + /// The stream to decode from. + /// The image metadata. + private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata) { - 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. return; } - var profile = new ExifProfile(exifData); - this.metadata.ExifProfile = profile; + metadata.ExifProfile = new(exifData); } } /// /// Reads the XMP profile the stream. /// - private void ReadXmpProfile() + /// The stream to decode from. + /// The image metadata. + private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata) { - 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. return; } - var profile = new XmpProfile(xmpData); - this.metadata.XmpProfile = profile; + metadata.XmpProfile = new(xmpData); } } /// /// Reads the ICCP chunk from the stream. /// - private void ReadIccProfile() + /// The stream to decode from. + /// The image metadata. + private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata) { - 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"); } - var profile = new IccProfile(iccpData); + IccProfile profile = new(iccpData); if (profile.CheckIsValid()) { - this.metadata.IccProfile = profile; + metadata.IccProfile = profile; } } } @@ -387,17 +383,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 +406,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,15 +432,15 @@ 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; + return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); } throw new ImageFormatException("Invalid Webp data."); @@ -452,10 +450,12 @@ 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() + /// Invalid data. + 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/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 68951e5ec0..33189ba845 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -#nullable disable -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; @@ -81,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; @@ -117,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) { 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); }