diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index c1860c9c59..6422008dce 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -257,13 +257,14 @@ public static long WriteAnimationFrame(Stream stream, WebpFrameData animation) long position = stream.Position; BinaryPrimitives.WriteUInt32BigEndian(buf, 0); stream.Write(buf); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.X); - WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Y); + + // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, 0); + WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, 0); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1); WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration); - // TODO: If we can clip the indexed frame for transparent bounds we can set properties here. byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod); stream.WriteByte(flag); return position; diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 54dd1d6ed1..7ed6d1eba8 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -95,12 +95,10 @@ public WebpLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAlloca public void Decode(Buffer2D pixels, int width, int height) where TPixel : unmanaged, IPixel { - using (Vp8LDecoder decoder = new Vp8LDecoder(width, height, this.memoryAllocator)) - { - this.DecodeImageStream(decoder, width, height, true); - this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); - this.DecodePixelValues(decoder, pixels, width, height); - } + using Vp8LDecoder decoder = new(width, height, this.memoryAllocator); + this.DecodeImageStream(decoder, width, height, true); + this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); + this.DecodePixelValues(decoder, pixels, width, height); } public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) @@ -616,15 +614,12 @@ private void ReadHuffmanCodeLengths(Span table, int[] codeLengthCod private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) { Vp8LTransformType transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); - Vp8LTransform transform = new Vp8LTransform(transformType, xSize, ySize); + Vp8LTransform transform = new(transformType, xSize, ySize); // Each transform is allowed to be used only once. - foreach (Vp8LTransform decoderTransform in decoder.Transforms) + if (decoder.Transforms.Any(decoderTransform => decoderTransform.TransformType == transform.TransformType)) { - if (decoderTransform.TransformType == transform.TransformType) - { - WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); - } + WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); } switch (transformType) @@ -744,61 +739,67 @@ public void DecodeAlphaData(AlphaDecoder dec) this.bitReader.FillBitWindow(); int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); - if (code < WebpConstants.NumLiteralCodes) + switch (code) { - // Literal - data[pos] = (byte)code; - ++pos; - ++col; - - if (col >= width) + case < WebpConstants.NumLiteralCodes: { - col = 0; - ++row; - if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) + // Literal + data[pos] = (byte)code; + ++pos; + ++col; + + if (col >= width) { - dec.ExtractPalettedAlphaRows(row); + col = 0; + ++row; + if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) + { + dec.ExtractPalettedAlphaRows(row); + } } + + break; } - } - else if (code < lenCodeLimit) - { - // Backward reference - int lengthSym = code - WebpConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym); - int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); - this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance(distSymbol); - int dist = PlaneCodeToDistance(width, distCode); - if (pos >= dist && end - pos >= length) - { - CopyBlock8B(data, pos, dist, length); - } - else + case < lenCodeLimit: { - WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data"); - } + // Backward reference + int lengthSym = code - WebpConstants.NumLiteralCodes; + int length = this.GetCopyLength(lengthSym); + int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); + this.bitReader.FillBitWindow(); + int distCode = this.GetCopyDistance(distSymbol); + int dist = PlaneCodeToDistance(width, distCode); + if (pos >= dist && end - pos >= length) + { + CopyBlock8B(data, pos, dist, length); + } + else + { + WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data"); + } - pos += length; - col += length; - while (col >= width) - { - col -= width; - ++row; - if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) + pos += length; + col += length; + while (col >= width) { - dec.ExtractPalettedAlphaRows(row); + col -= width; + ++row; + if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) + { + dec.ExtractPalettedAlphaRows(row); + } } - } - if (pos < last && (col & mask) > 0) - { - htreeGroup = GetHTreeGroupForPos(hdr, col, row); + if (pos < last && (col & mask) > 0) + { + htreeGroup = GetHTreeGroupForPos(hdr, col, row); + } + + break; } - } - else - { - WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); + default: + WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); + break; } this.bitReader.Eos = this.bitReader.IsEndOfStream(); diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 354bcdbb44..903e448cb6 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -62,7 +62,7 @@ public void Decode(Buffer2D pixels, int width, int height, WebpI // Paragraph 9.2: color space and clamp type follow. sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); sbyte clampType = (sbyte)this.bitReader.ReadValue(1); - Vp8PictureHeader pictureHeader = new Vp8PictureHeader + Vp8PictureHeader pictureHeader = new() { Width = (uint)width, Height = (uint)height, @@ -73,55 +73,51 @@ public void Decode(Buffer2D pixels, int width, int height, WebpI }; // Paragraph 9.3: Parse the segment header. - Vp8Proba proba = new Vp8Proba(); + Vp8Proba proba = new(); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); - using (Vp8Decoder decoder = new Vp8Decoder( - info.Vp8FrameHeader, - pictureHeader, - vp8SegmentHeader, - proba, - this.memoryAllocator)) - { - Vp8Io io = InitializeVp8Io(decoder, pictureHeader); + using Vp8Decoder decoder = new( + info.Vp8FrameHeader, + pictureHeader, + vp8SegmentHeader, + proba, + this.memoryAllocator); + Vp8Io io = InitializeVp8Io(decoder, pictureHeader); - // Paragraph 9.4: Parse the filter specs. - this.ParseFilterHeader(decoder); - decoder.PrecomputeFilterStrengths(); + // Paragraph 9.4: Parse the filter specs. + this.ParseFilterHeader(decoder); + decoder.PrecomputeFilterStrengths(); - // Paragraph 9.5: Parse partitions. - this.ParsePartitions(decoder); + // Paragraph 9.5: Parse partitions. + this.ParsePartitions(decoder); - // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(decoder); + // Paragraph 9.6: Dequantization Indices. + this.ParseDequantizationIndices(decoder); - // Ignore the value of update probabilities. - this.bitReader.ReadBool(); + // Ignore the value of update probabilities. + this.bitReader.ReadBool(); - // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(decoder); + // Paragraph 13.4: Parse probabilities. + this.ParseProbabilities(decoder); - // Decode image data. - this.ParseFrame(decoder, io); + // Decode image data. + this.ParseFrame(decoder, io); - if (info.Features?.Alpha == true) - { - using (AlphaDecoder alphaDecoder = new AlphaDecoder( - width, - height, - alphaData, - info.Features.AlphaChunkHeader, - this.memoryAllocator, - this.configuration)) - { - alphaDecoder.Decode(); - DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); - } - } - else - { - this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); - } + if (info.Features?.Alpha == true) + { + using AlphaDecoder alphaDecoder = new( + width, + height, + alphaData, + info.Features.AlphaChunkHeader, + this.memoryAllocator, + this.configuration); + alphaDecoder.Decode(); + DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); + } + else + { + this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); } } @@ -199,8 +195,8 @@ private void ParseIntraMode(Vp8Decoder dec, int mbX) { // Hardcoded tree parsing. block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) == 0 - ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) - : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); + ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) + : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); } else { @@ -595,57 +591,64 @@ private static void DoFilter(Vp8Decoder dec, int mbx, int mby) return; } - if (dec.Filter == LoopFilter.Simple) + switch (dec.Filter) { - int offset = dec.CacheYOffset + (mbx * 16); - if (mbx > 0) + case LoopFilter.Simple: { - LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); - } + int offset = dec.CacheYOffset + (mbx * 16); + if (mbx > 0) + { + LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); + } - if (filterInfo.UseInnerFiltering) - { - LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); - } + if (filterInfo.UseInnerFiltering) + { + LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); + } - if (mby > 0) - { - LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); - } + if (mby > 0) + { + LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); + } - if (filterInfo.UseInnerFiltering) - { - LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); + if (filterInfo.UseInnerFiltering) + { + LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); + } + + break; } - } - else if (dec.Filter == LoopFilter.Complex) - { - int uvBps = dec.CacheUvStride; - int yOffset = dec.CacheYOffset + (mbx * 16); - int uvOffset = dec.CacheUvOffset + (mbx * 8); - int hevThresh = filterInfo.HighEdgeVarianceThreshold; - if (mbx > 0) + case LoopFilter.Complex: { - LossyUtils.HFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); - } + int uvBps = dec.CacheUvStride; + int yOffset = dec.CacheYOffset + (mbx * 16); + int uvOffset = dec.CacheUvOffset + (mbx * 8); + int hevThresh = filterInfo.HighEdgeVarianceThreshold; + if (mbx > 0) + { + LossyUtils.HFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); + } - if (filterInfo.UseInnerFiltering) - { - LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); - } + if (filterInfo.UseInnerFiltering) + { + LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); + } - if (mby > 0) - { - LossyUtils.VFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); - } + if (mby > 0) + { + LossyUtils.VFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); + } - if (filterInfo.UseInnerFiltering) - { - LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); + if (filterInfo.UseInnerFiltering) + { + LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); + } + + break; } } } @@ -1067,7 +1070,7 @@ private static int GetLargeValue(Vp8BitReader br, byte[] p) private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) { - Vp8SegmentHeader vp8SegmentHeader = new Vp8SegmentHeader + Vp8SegmentHeader vp8SegmentHeader = new() { UseSegment = this.bitReader.ReadBool() }; @@ -1333,18 +1336,12 @@ private static Vp8Io InitializeVp8Io(Vp8Decoder dec, Vp8PictureHeader pictureHea private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) { nzCoeffs <<= 2; - if (nz > 3) + nzCoeffs |= nz switch { - nzCoeffs |= 3; - } - else if (nz > 1) - { - nzCoeffs |= 2; - } - else - { - nzCoeffs |= (uint)dcNz; - } + > 3 => 3, + > 1 => 2, + _ => (uint)dcNz + }; return nzCoeffs; } @@ -1358,13 +1355,13 @@ private static int CheckMode(int mbx, int mby, int mode) if (mbx == 0) { return mby == 0 - ? 6 // B_DC_PRED_NOTOPLEFT - : 5; // B_DC_PRED_NOLEFT + ? 6 // B_DC_PRED_NOTOPLEFT + : 5; // B_DC_PRED_NOLEFT } return mby == 0 - ? 4 // B_DC_PRED_NOTOP - : 0; // B_DC_PRED + ? 4 // B_DC_PRED_NOTOP + : 0; // B_DC_PRED } return mode; diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 87657dfabb..7c1aad094b 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -193,11 +193,7 @@ private uint ReadFrame(BufferedReadStream stream, ref Image? ima imageFrame = currentFrame; } - int frameX = (int)(frameData.X * 2); - int frameY = (int)(frameData.Y * 2); - int frameWidth = (int)frameData.Width; - int frameHeight = (int)frameData.Height; - Rectangle regionRectangle = Rectangle.FromLTRB(frameX, frameY, frameX + frameWidth, frameY + frameHeight); + Rectangle regionRectangle = frameData.Bounds; if (frameData.DisposalMethod is WebpDisposalMethod.Dispose) { @@ -205,11 +201,11 @@ private uint ReadFrame(BufferedReadStream stream, ref Image? ima } using Buffer2D decodedImage = this.DecodeImageData(frameData, webpInfo); - DrawDecodedImageOnCanvas(decodedImage, imageFrame, frameX, frameY, frameWidth, frameHeight); + DrawDecodedImageOnCanvas(decodedImage, imageFrame, regionRectangle); if (previousFrame != null && frameData.BlendingMethod is WebpBlendingMethod.AlphaBlending) { - this.AlphaBlend(previousFrame, imageFrame, frameX, frameY, frameWidth, frameHeight); + this.AlphaBlend(previousFrame, imageFrame, regionRectangle); } previousFrame = currentFrame ?? image.Frames.RootFrame; @@ -245,7 +241,7 @@ private byte ReadAlphaData(BufferedReadStream stream) byte alphaChunkHeader = (byte)stream.ReadByte(); Span alphaData = this.alphaData.GetSpan(); - stream.Read(alphaData, 0, alphaDataSize); + _ = stream.Read(alphaData, 0, alphaDataSize); return alphaChunkHeader; } @@ -260,11 +256,11 @@ private byte ReadAlphaData(BufferedReadStream stream) private Buffer2D DecodeImageData(WebpFrameData frameData, WebpImageInfo webpInfo) where TPixel : unmanaged, IPixel { - Image decodedImage = new((int)frameData.Width, (int)frameData.Height); + ImageFrame decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height); try { - Buffer2D pixelBufferDecoded = decodedImage.GetRootFramePixelBuffer(); + Buffer2D pixelBufferDecoded = decodedFrame.PixelBuffer; if (webpInfo.IsLossless) { WebpLosslessDecoder losslessDecoder = @@ -282,7 +278,7 @@ private Buffer2D DecodeImageData(WebpFrameData frameData, WebpIm } catch { - decodedImage?.Dispose(); + decodedFrame?.Dispose(); throw; } finally @@ -297,20 +293,17 @@ private Buffer2D DecodeImageData(WebpFrameData frameData, WebpIm /// The type of the pixel. /// The decoded image. /// The image frame to draw into. - /// The frame x coordinate. - /// The frame y coordinate. - /// The width of the frame. - /// The height of the frame. - private static void DrawDecodedImageOnCanvas(Buffer2D decodedImage, ImageFrame imageFrame, int frameX, int frameY, int frameWidth, int frameHeight) + /// The area of the frame. + private static void DrawDecodedImageOnCanvas(Buffer2D decodedImage, ImageFrame imageFrame, Rectangle restoreArea) where TPixel : unmanaged, IPixel { - Buffer2D imageFramePixels = imageFrame.PixelBuffer; + Buffer2DRegion imageFramePixels = imageFrame.PixelBuffer.GetRegion(restoreArea); int decodedRowIdx = 0; - for (int y = frameY; y < frameY + frameHeight; y++) + for (int y = 0; y < restoreArea.Height; y++) { Span framePixelRow = imageFramePixels.DangerousGetRowSpan(y); - Span decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..frameWidth]; - decodedPixelRow.TryCopyTo(framePixelRow[frameX..]); + Span decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..restoreArea.Width]; + decodedPixelRow.TryCopyTo(framePixelRow); } } @@ -321,22 +314,19 @@ private static void DrawDecodedImageOnCanvas(Buffer2D decodedIma /// The pixel format. /// The source image. /// The destination image. - /// The frame x coordinate. - /// The frame y coordinate. - /// The width of the frame. - /// The height of the frame. - private void AlphaBlend(ImageFrame src, ImageFrame dst, int frameX, int frameY, int frameWidth, int frameHeight) + /// The area of the frame. + private void AlphaBlend(ImageFrame src, ImageFrame dst, Rectangle restoreArea) where TPixel : unmanaged, IPixel { - Buffer2D srcPixels = src.PixelBuffer; - Buffer2D dstPixels = dst.PixelBuffer; + Buffer2DRegion srcPixels = src.PixelBuffer.GetRegion(restoreArea); + Buffer2DRegion dstPixels = dst.PixelBuffer.GetRegion(restoreArea); PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - for (int y = frameY; y < frameY + frameHeight; y++) + for (int y = 0; y < restoreArea.Height; y++) { - Span srcPixelRow = srcPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth); - Span dstPixelRow = dstPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth); + Span srcPixelRow = srcPixels.DangerousGetRowSpan(y); + Span dstPixelRow = dstPixels.DangerousGetRowSpan(y); - blender.Blend(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1.0f); + blender.Blend(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f); } } diff --git a/src/ImageSharp/Formats/Webp/WebpFrameData.cs b/src/ImageSharp/Formats/Webp/WebpFrameData.cs index e2bcfd7c36..93c5d10dcd 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameData.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameData.cs @@ -53,6 +53,8 @@ internal struct WebpFrameData /// public WebpDisposalMethod DisposalMethod; + public readonly Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height); + /// /// Reads the animation frame header. ///