Browse Source

fix

pull/2569/head
Poker 2 years ago
parent
commit
b4e1b7f4e1
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 1
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  2. 4
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  3. 115
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  4. 4
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  5. 198
      src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs
  6. 53
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  7. 2
      src/ImageSharp/Formats/Webp/WebpFrameData.cs

1
src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

@ -263,7 +263,6 @@ internal abstract class BitWriterBase
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;

4
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -306,8 +306,12 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation)
{
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata();
// TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
{
X = 0,
Y = 0,
Width = (uint)frame.Width,
Height = (uint)frame.Height,
Duration = frameMetadata.FrameDelay,

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

@ -95,12 +95,10 @@ internal sealed class WebpLosslessDecoder
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
{
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<uint> DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0)
@ -616,15 +614,12 @@ internal sealed class WebpLosslessDecoder
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,69 @@ internal sealed class WebpLosslessDecoder
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);
}
}
}
}
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
{
WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data");
break;
}
pos += length;
col += length;
while (col >= width)
case < lenCodeLimit:
{
col -= width;
++row;
if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0)
// 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)
{
dec.ExtractPalettedAlphaRows(row);
CopyBlock8B(data, pos, dist, length);
}
else
{
WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data");
}
}
if (pos < last && (col & mask) > 0)
{
htreeGroup = GetHTreeGroupForPos(hdr, col, row);
pos += length;
col += length;
while (col >= width)
{
col -= width;
++row;
if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0)
{
dec.ExtractPalettedAlphaRows(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();

4
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -476,8 +476,12 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation)
{
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata();
// TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
{
X = 0,
Y = 0,
Width = (uint)frame.Width,
Height = (uint)frame.Height,
Duration = frameMetadata.FrameDelay,

198
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs

@ -62,7 +62,7 @@ internal sealed class WebpLossyDecoder
// 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 @@ internal sealed class WebpLossyDecoder
};
// 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 @@ internal sealed class WebpLossyDecoder
{
// 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,65 @@ internal sealed class WebpLossyDecoder
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);
}
}
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)
{
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.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit);
}
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);
break;
}
if (mby > 0)
case LoopFilter.Complex:
{
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);
}
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.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.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 (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 +1071,7 @@ internal sealed class WebpLossyDecoder
private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba)
{
Vp8SegmentHeader vp8SegmentHeader = new Vp8SegmentHeader
Vp8SegmentHeader vp8SegmentHeader = new()
{
UseSegment = this.bitReader.ReadBool()
};
@ -1333,18 +1337,12 @@ internal sealed class WebpLossyDecoder
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 +1356,13 @@ internal sealed class WebpLossyDecoder
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;

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

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
@ -193,11 +192,7 @@ internal class WebpAnimationDecoder : IDisposable
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 +200,11 @@ internal class WebpAnimationDecoder : IDisposable
}
using Buffer2D<TPixel> decodedImage = this.DecodeImageData<TPixel>(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 +240,7 @@ internal class WebpAnimationDecoder : IDisposable
byte alphaChunkHeader = (byte)stream.ReadByte();
Span<byte> alphaData = this.alphaData.GetSpan();
stream.Read(alphaData, 0, alphaDataSize);
_ = stream.Read(alphaData, 0, alphaDataSize);
return alphaChunkHeader;
}
@ -260,11 +255,11 @@ internal class WebpAnimationDecoder : IDisposable
private Buffer2D<TPixel> DecodeImageData<TPixel>(WebpFrameData frameData, WebpImageInfo webpInfo)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> decodedImage = new((int)frameData.Width, (int)frameData.Height);
ImageFrame<TPixel> decodedFrame = new(Configuration.Default, (int)frameData.Width, (int)frameData.Height);
try
{
Buffer2D<TPixel> pixelBufferDecoded = decodedImage.GetRootFramePixelBuffer();
Buffer2D<TPixel> pixelBufferDecoded = decodedFrame.PixelBuffer;
if (webpInfo.IsLossless)
{
WebpLosslessDecoder losslessDecoder =
@ -282,7 +277,7 @@ internal class WebpAnimationDecoder : IDisposable
}
catch
{
decodedImage?.Dispose();
decodedFrame?.Dispose();
throw;
}
finally
@ -297,20 +292,17 @@ internal class WebpAnimationDecoder : IDisposable
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="decodedImage">The decoded image.</param>
/// <param name="imageFrame">The image frame to draw into.</param>
/// <param name="frameX">The frame x coordinate.</param>
/// <param name="frameY">The frame y coordinate.</param>
/// <param name="frameWidth">The width of the frame.</param>
/// <param name="frameHeight">The height of the frame.</param>
private static void DrawDecodedImageOnCanvas<TPixel>(Buffer2D<TPixel> decodedImage, ImageFrame<TPixel> imageFrame, int frameX, int frameY, int frameWidth, int frameHeight)
/// <param name="restoreArea">The area of the frame.</param>
private static void DrawDecodedImageOnCanvas<TPixel>(Buffer2D<TPixel> decodedImage, ImageFrame<TPixel> imageFrame, Rectangle restoreArea)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> imageFramePixels = imageFrame.PixelBuffer;
Buffer2DRegion<TPixel> 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<TPixel> framePixelRow = imageFramePixels.DangerousGetRowSpan(y);
Span<TPixel> decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..frameWidth];
decodedPixelRow.TryCopyTo(framePixelRow[frameX..]);
Span<TPixel> decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..restoreArea.Width];
decodedPixelRow.TryCopyTo(framePixelRow);
}
}
@ -321,22 +313,19 @@ internal class WebpAnimationDecoder : IDisposable
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="src">The source image.</param>
/// <param name="dst">The destination image.</param>
/// <param name="frameX">The frame x coordinate.</param>
/// <param name="frameY">The frame y coordinate.</param>
/// <param name="frameWidth">The width of the frame.</param>
/// <param name="frameHeight">The height of the frame.</param>
private void AlphaBlend<TPixel>(ImageFrame<TPixel> src, ImageFrame<TPixel> dst, int frameX, int frameY, int frameWidth, int frameHeight)
/// <param name="restoreArea">The area of the frame.</param>
private void AlphaBlend<TPixel>(ImageFrame<TPixel> src, ImageFrame<TPixel> dst, Rectangle restoreArea)
where TPixel : unmanaged, IPixel<TPixel>
{
Buffer2D<TPixel> srcPixels = src.PixelBuffer;
Buffer2D<TPixel> dstPixels = dst.PixelBuffer;
Buffer2DRegion<TPixel> srcPixels = src.PixelBuffer.GetRegion(restoreArea);
Buffer2DRegion<TPixel> dstPixels = dst.PixelBuffer.GetRegion(restoreArea);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver);
for (int y = frameY; y < frameY + frameHeight; y++)
for (int y = 0; y < restoreArea.Height; y++)
{
Span<TPixel> srcPixelRow = srcPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth);
Span<TPixel> dstPixelRow = dstPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth);
Span<TPixel> srcPixelRow = srcPixels.DangerousGetRowSpan(y);
Span<TPixel> dstPixelRow = dstPixels.DangerousGetRowSpan(y);
blender.Blend<TPixel>(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1.0f);
blender.Blend<TPixel>(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1f);
}
}

2
src/ImageSharp/Formats/Webp/WebpFrameData.cs

@ -53,6 +53,8 @@ internal struct WebpFrameData
/// </summary>
public WebpDisposalMethod DisposalMethod;
public readonly Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height);
/// <summary>
/// Reads the animation frame header.
/// </summary>

Loading…
Cancel
Save