Browse Source

fix

pull/2569/head
Poker 3 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. 27
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  4. 4
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  5. 46
      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.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration); 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); byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
stream.WriteByte(flag); stream.WriteByte(flag);
return position; return position;

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

@ -306,8 +306,12 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation) if (hasAnimation)
{ {
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata(); 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 prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
{ {
X = 0,
Y = 0,
Width = (uint)frame.Width, Width = (uint)frame.Width,
Height = (uint)frame.Height, Height = (uint)frame.Height,
Duration = frameMetadata.FrameDelay, Duration = frameMetadata.FrameDelay,

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

@ -95,13 +95,11 @@ internal sealed class WebpLosslessDecoder
public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height) public void Decode<TPixel>(Buffer2D<TPixel> pixels, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Vp8LDecoder decoder = new Vp8LDecoder(width, height, this.memoryAllocator)) using Vp8LDecoder decoder = new(width, height, this.memoryAllocator);
{
this.DecodeImageStream(decoder, width, height, true); this.DecodeImageStream(decoder, width, height, true);
this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); this.DecodeImageData(decoder, decoder.Pixels.Memory.Span);
this.DecodePixelValues(decoder, pixels, width, height); this.DecodePixelValues(decoder, pixels, width, height);
} }
}
public IMemoryOwner<uint> DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) public IMemoryOwner<uint> DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0)
{ {
@ -616,16 +614,13 @@ internal sealed class WebpLosslessDecoder
private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder)
{ {
Vp8LTransformType transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); 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. // 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) switch (transformType)
{ {
@ -744,7 +739,9 @@ internal sealed class WebpLosslessDecoder
this.bitReader.FillBitWindow(); this.bitReader.FillBitWindow();
int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]);
if (code < WebpConstants.NumLiteralCodes) switch (code)
{
case < WebpConstants.NumLiteralCodes:
{ {
// Literal // Literal
data[pos] = (byte)code; data[pos] = (byte)code;
@ -760,8 +757,11 @@ internal sealed class WebpLosslessDecoder
dec.ExtractPalettedAlphaRows(row); dec.ExtractPalettedAlphaRows(row);
} }
} }
break;
} }
else if (code < lenCodeLimit)
case < lenCodeLimit:
{ {
// Backward reference // Backward reference
int lengthSym = code - WebpConstants.NumLiteralCodes; int lengthSym = code - WebpConstants.NumLiteralCodes;
@ -795,10 +795,13 @@ internal sealed class WebpLosslessDecoder
{ {
htreeGroup = GetHTreeGroupForPos(hdr, col, row); htreeGroup = GetHTreeGroupForPos(hdr, col, row);
} }
break;
} }
else
{ default:
WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data");
break;
} }
this.bitReader.Eos = this.bitReader.IsEndOfStream(); this.bitReader.Eos = this.bitReader.IsEndOfStream();

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

@ -476,8 +476,12 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation) if (hasAnimation)
{ {
WebpFrameMetadata frameMetadata = frame.Metadata.GetWebpMetadata(); 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 prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData
{ {
X = 0,
Y = 0,
Width = (uint)frame.Width, Width = (uint)frame.Width,
Height = (uint)frame.Height, Height = (uint)frame.Height,
Duration = frameMetadata.FrameDelay, Duration = frameMetadata.FrameDelay,

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

@ -62,7 +62,7 @@ internal sealed class WebpLossyDecoder
// Paragraph 9.2: color space and clamp type follow. // Paragraph 9.2: color space and clamp type follow.
sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1);
sbyte clampType = (sbyte)this.bitReader.ReadValue(1); sbyte clampType = (sbyte)this.bitReader.ReadValue(1);
Vp8PictureHeader pictureHeader = new Vp8PictureHeader Vp8PictureHeader pictureHeader = new()
{ {
Width = (uint)width, Width = (uint)width,
Height = (uint)height, Height = (uint)height,
@ -73,16 +73,15 @@ internal sealed class WebpLossyDecoder
}; };
// Paragraph 9.3: Parse the segment header. // Paragraph 9.3: Parse the segment header.
Vp8Proba proba = new Vp8Proba(); Vp8Proba proba = new();
Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba);
using (Vp8Decoder decoder = new Vp8Decoder( using Vp8Decoder decoder = new(
info.Vp8FrameHeader, info.Vp8FrameHeader,
pictureHeader, pictureHeader,
vp8SegmentHeader, vp8SegmentHeader,
proba, proba,
this.memoryAllocator)) this.memoryAllocator);
{
Vp8Io io = InitializeVp8Io(decoder, pictureHeader); Vp8Io io = InitializeVp8Io(decoder, pictureHeader);
// Paragraph 9.4: Parse the filter specs. // Paragraph 9.4: Parse the filter specs.
@ -106,24 +105,21 @@ internal sealed class WebpLossyDecoder
if (info.Features?.Alpha == true) if (info.Features?.Alpha == true)
{ {
using (AlphaDecoder alphaDecoder = new AlphaDecoder( using AlphaDecoder alphaDecoder = new(
width, width,
height, height,
alphaData, alphaData,
info.Features.AlphaChunkHeader, info.Features.AlphaChunkHeader,
this.memoryAllocator, this.memoryAllocator,
this.configuration)) this.configuration);
{
alphaDecoder.Decode(); alphaDecoder.Decode();
DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha);
} }
}
else else
{ {
this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels);
} }
} }
}
private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, Buffer2D<TPixel> decodedPixels) private void DecodePixelValues<TPixel>(int width, int height, Span<byte> pixelData, Buffer2D<TPixel> decodedPixels)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -595,7 +591,9 @@ internal sealed class WebpLossyDecoder
return; return;
} }
if (dec.Filter == LoopFilter.Simple) switch (dec.Filter)
{
case LoopFilter.Simple:
{ {
int offset = dec.CacheYOffset + (mbx * 16); int offset = dec.CacheYOffset + (mbx * 16);
if (mbx > 0) if (mbx > 0)
@ -617,8 +615,11 @@ internal sealed class WebpLossyDecoder
{ {
LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit);
} }
break;
} }
else if (dec.Filter == LoopFilter.Complex)
case LoopFilter.Complex:
{ {
int uvBps = dec.CacheUvStride; int uvBps = dec.CacheUvStride;
int yOffset = dec.CacheYOffset + (mbx * 16); int yOffset = dec.CacheYOffset + (mbx * 16);
@ -647,6 +648,9 @@ internal sealed class WebpLossyDecoder
LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); 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); 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) private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba)
{ {
Vp8SegmentHeader vp8SegmentHeader = new Vp8SegmentHeader Vp8SegmentHeader vp8SegmentHeader = new()
{ {
UseSegment = this.bitReader.ReadBool() UseSegment = this.bitReader.ReadBool()
}; };
@ -1333,18 +1337,12 @@ internal sealed class WebpLossyDecoder
private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz)
{ {
nzCoeffs <<= 2; nzCoeffs <<= 2;
if (nz > 3) nzCoeffs |= nz switch
{ {
nzCoeffs |= 3; > 3 => 3,
} > 1 => 2,
else if (nz > 1) _ => (uint)dcNz
{ };
nzCoeffs |= 2;
}
else
{
nzCoeffs |= (uint)dcNz;
}
return nzCoeffs; return nzCoeffs;
} }

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

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

Loading…
Cancel
Save