Browse Source

introduce RiffHelper

pull/2569/head
Poker 3 years ago
parent
commit
1b0b877a14
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 222
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  2. 37
      src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs
  3. 140
      src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs
  4. 113
      src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs
  5. 23
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  6. 23
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  7. 3
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  8. 18
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  9. 2
      src/ImageSharp/Formats/Webp/WebpChunkType.cs
  10. 10
      src/ImageSharp/Formats/Webp/WebpConstants.cs
  11. 93
      src/ImageSharp/Formats/Webp/WebpFrameData.cs

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

@ -1,8 +1,9 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers.Binary; using System.Diagnostics;
using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
@ -15,8 +16,6 @@ internal abstract class BitWriterBase
private const ulong MaxCanvasPixels = 4294967295ul; private const ulong MaxCanvasPixels = 4294967295ul;
protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
/// <summary> /// <summary>
/// Buffer to write to. /// Buffer to write to.
/// </summary> /// </summary>
@ -79,48 +78,6 @@ internal abstract class BitWriterBase
Array.Resize(ref this.buffer, newSize); Array.Resize(ref this.buffer, newSize);
} }
/// <summary>
/// Writes the RIFF header to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="riffSize">The block length.</param>
protected static void WriteRiffHeader(Stream stream, uint riffSize)
{
stream.Write(WebpConstants.RiffFourCc);
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
stream.Write(buf);
stream.Write(WebpConstants.WebpHeader);
}
/// <summary>
/// Calculates the chunk size of EXIF, XMP or ICCP metadata.
/// </summary>
/// <param name="metadataBytes">The metadata profile bytes.</param>
/// <returns>The metadata chunk size in bytes.</returns>
protected static uint MetadataChunkSize(byte[] metadataBytes)
{
uint metaSize = (uint)metadataBytes.Length;
return WebpConstants.ChunkHeaderSize + metaSize + (metaSize & 1);
}
/// <summary>
/// Calculates the chunk size of a alpha chunk.
/// </summary>
/// <param name="alphaBytes">The alpha chunk bytes.</param>
/// <returns>The alpha data chunk size in bytes.</returns>
protected static uint AlphaChunkSize(Span<byte> alphaBytes)
{
uint alphaSize = (uint)alphaBytes.Length + 1;
return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
}
/// <summary>
/// Overwrites ides the write file size.
/// </summary>
/// <param name="stream">The stream to write to.</param>
protected static void OverwriteFileSize(Stream stream) => OverwriteFrameSize(stream, 4);
/// <summary> /// <summary>
/// Write the trunks before data trunk. /// Write the trunks before data trunk.
/// </summary> /// </summary>
@ -143,7 +100,9 @@ internal abstract class BitWriterBase
bool hasAnimation) bool hasAnimation)
{ {
// Write file size later // Write file size later
WriteRiffHeader(stream, 0); long pos = RiffHelper.BeginWriteRiffFile(stream, WebpConstants.WebpFourCc);
Debug.Assert(pos is 4, "Stream should be written from position 0.");
// Write VP8X, header if necessary. // Write VP8X, header if necessary.
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation; bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha || hasAnimation;
@ -153,7 +112,7 @@ internal abstract class BitWriterBase
if (iccProfile != null) if (iccProfile != null)
{ {
WriteColorProfile(stream, iccProfile.ToByteArray()); RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Iccp, iccProfile.ToByteArray());
} }
} }
} }
@ -177,49 +136,17 @@ internal abstract class BitWriterBase
{ {
if (exifProfile != null) if (exifProfile != null)
{ {
WriteMetadataProfile(stream, exifProfile.ToByteArray(), WebpChunkType.Exif); RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Exif, exifProfile.ToByteArray());
} }
if (xmpProfile != null) if (xmpProfile != null)
{ {
WriteMetadataProfile(stream, xmpProfile.Data, WebpChunkType.Xmp); RiffHelper.WriteChunk(stream, (uint)WebpChunkType.Xmp, xmpProfile.Data);
} }
OverwriteFileSize(stream); RiffHelper.EndWriteRiffFile(stream, 4);
}
/// <summary>
/// Writes a metadata profile (EXIF or XMP) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="metadataBytes">The metadata profile's bytes.</param>
/// <param name="chunkType">The chuck type to write.</param>
protected static void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
{
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
uint size = (uint)metadataBytes.Length;
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
stream.Write(metadataBytes);
// Add padding byte if needed.
if ((size & 1) == 1)
{
stream.WriteByte(0);
}
} }
/// <summary>
/// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="iccProfileBytes">The color profile bytes.</param>
protected static void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
/// <summary> /// <summary>
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream. /// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
/// </summary> /// </summary>
@ -233,55 +160,8 @@ internal abstract class BitWriterBase
/// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param> /// <param name="loopCount">The number of times to loop the animation. If it is 0, this means infinitely.</param>
public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount) public static void WriteAnimationParameter(Stream stream, Color background, ushort loopCount)
{ {
Span<byte> buf = stackalloc byte[4]; WebpAnimationParameter chunk = new(background.ToRgba32().Rgba, loopCount);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.AnimationParameter); chunk.WriteTo(stream);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, sizeof(uint) + sizeof(ushort));
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, background.ToRgba32().Rgba);
stream.Write(buf);
BinaryPrimitives.WriteUInt16LittleEndian(buf[..2], loopCount);
stream.Write(buf[..2]);
}
/// <summary>
/// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="animation">Animation frame data.</param>
public static long WriteAnimationFrame(Stream stream, WebpFrameData animation)
{
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Animation);
stream.Write(buf);
long position = stream.Position;
BinaryPrimitives.WriteUInt32BigEndian(buf, 0);
stream.Write(buf);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.X);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Y);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, animation.Duration);
byte flag = (byte)(((int)animation.BlendingMethod << 1) | (int)animation.DisposalMethod);
stream.WriteByte(flag);
return position;
}
/// <summary>
/// Overwrites ides the write frame size.
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="prevPosition">Previous position.</param>
public static void OverwriteFrameSize(Stream stream, long prevPosition)
{
uint position = (uint)stream.Position;
stream.Position = prevPosition;
byte[] buffer = new byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)(position - prevPosition - 4));
stream.Write(buffer);
stream.Position = position;
} }
/// <summary> /// <summary>
@ -292,27 +172,17 @@ internal abstract class BitWriterBase
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param> /// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
public static void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed) public static void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDataIsCompressed)
{ {
uint size = (uint)dataBytes.Length + 1; long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Alpha);
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
stream.Write(buf);
byte flags = 0; byte flags = 0;
if (alphaDataIsCompressed) if (alphaDataIsCompressed)
{ {
// TODO: Filtering and preprocessing
flags = 1; flags = 1;
} }
stream.WriteByte(flags); stream.WriteByte(flags);
stream.Write(dataBytes); stream.Write(dataBytes);
RiffHelper.EndWriteChunk(stream, pos);
// Add padding byte if needed.
if ((size & 1) == 1)
{
stream.WriteByte(0);
}
} }
/// <summary> /// <summary>
@ -328,66 +198,10 @@ internal abstract class BitWriterBase
/// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param> /// <param name="hasAnimation">Flag indicating, if an animation parameter is present.</param>
protected static void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation) protected static void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha, bool hasAnimation)
{ {
if (width > MaxDimension || height > MaxDimension) WebpVp8X chunk = new(hasAnimation, xmpProfile != null, exifProfile != null, hasAlpha, iccProfile != null, width, height);
{
WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {MaxDimension}");
}
// The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1. chunk.Validate(MaxDimension, MaxCanvasPixels);
if (width * height > MaxCanvasPixels)
{
WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
}
uint flags = 0;
if (exifProfile != null)
{
// Set exif bit.
flags |= 8;
}
if (hasAnimation)
{
// Set animated flag.
flags |= 2;
}
if (xmpProfile != null)
{
// Set xmp bit.
flags |= 4;
}
if (hasAlpha)
{
// Set alpha bit.
flags |= 16;
}
if (iccProfile != null)
{
// Set iccp flag.
flags |= 32;
}
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Vp8X);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);
stream.Write(buf);
BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1);
stream.Write(buf[..3]);
BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1);
stream.Write(buf[..3]);
}
private unsafe struct ScratchBuffer
{
private const int Size = 4;
private fixed byte scratch[Size];
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size); chunk.WriteTo(stream);
} }
} }

37
src/ImageSharp/Formats/Webp/Chunks/WebpAnimationParameter.cs

@ -0,0 +1,37 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
internal readonly struct WebpAnimationParameter
{
public WebpAnimationParameter(uint background, ushort loopCount)
{
this.Background = background;
this.LoopCount = loopCount;
}
/// <summary>
/// Gets default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
/// This color MAY be used to fill the unused space on the canvas around the frames,
/// as well as the transparent pixels of the first frame.
/// The background color is also used when the Disposal method is 1.
/// </summary>
public uint Background { get; }
/// <summary>
/// Gets number of times to loop the animation. If it is 0, this means infinitely.
/// </summary>
public ushort LoopCount { get; }
public void WriteTo(Stream stream)
{
Span<byte> buffer = stackalloc byte[6];
BinaryPrimitives.WriteUInt32LittleEndian(buffer[..4], this.Background);
BinaryPrimitives.WriteUInt16LittleEndian(buffer[4..], this.LoopCount);
RiffHelper.WriteChunk(stream, (uint)WebpChunkType.AnimationParameter, buffer);
}
}

140
src/ImageSharp/Formats/Webp/Chunks/WebpFrameData.cs

@ -0,0 +1,140 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
internal readonly struct WebpFrameData
{
/// <summary>
/// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags.
/// </summary>
public const uint HeaderSize = 16;
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
{
this.DataSize = dataSize;
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
this.Duration = duration;
this.DisposalMethod = disposalMethod;
this.BlendingMethod = blendingMethod;
}
public WebpFrameData(uint dataSize, uint x, uint y, uint width, uint height, uint duration, int flags)
: this(
dataSize,
x,
y,
width,
height,
duration,
(flags & 2) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending,
(flags & 1) == 1 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose)
{
}
public WebpFrameData(uint x, uint y, uint width, uint height, uint duration, WebpBlendingMethod blendingMethod, WebpDisposalMethod disposalMethod)
: this(0, x, y, width, height, duration, blendingMethod, disposalMethod)
{
}
/// <summary>
/// Gets the animation chunk size.
/// </summary>
public uint DataSize { get; }
/// <summary>
/// Gets the X coordinate of the upper left corner of the frame is Frame X * 2.
/// </summary>
public uint X { get; }
/// <summary>
/// Gets the Y coordinate of the upper left corner of the frame is Frame Y * 2.
/// </summary>
public uint Y { get; }
/// <summary>
/// Gets the width of the frame.
/// </summary>
public uint Width { get; }
/// <summary>
/// Gets the height of the frame.
/// </summary>
public uint Height { get; }
/// <summary>
/// Gets the time to wait before displaying the next frame, in 1 millisecond units.
/// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined.
/// </summary>
public uint Duration { get; }
/// <summary>
/// Gets how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public WebpBlendingMethod BlendingMethod { get; }
/// <summary>
/// Gets how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// </summary>
public WebpDisposalMethod DisposalMethod { get; }
public Rectangle Bounds => new((int)this.X * 2, (int)this.Y * 2, (int)this.Width, (int)this.Height);
/// <summary>
/// Writes the animation frame(<see cref="WebpChunkType.FrameData"/>) to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public long WriteHeaderTo(Stream stream)
{
byte flags = 0;
if (this.BlendingMethod is WebpBlendingMethod.DoNotBlend)
{
// Set blending flag.
flags |= 2;
}
if (this.DisposalMethod is WebpDisposalMethod.Dispose)
{
// Set disposal flag.
flags |= 1;
}
long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.FrameData);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.X);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Y);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Duration);
stream.WriteByte(flags);
return pos;
}
/// <summary>
/// Reads the animation frame header.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <returns>Animation frame data.</returns>
public static WebpFrameData Parse(Stream stream)
{
Span<byte> buffer = stackalloc byte[4];
WebpFrameData data = new(
dataSize: WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
x: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
y: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
width: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
height: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
duration: WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
flags: stream.ReadByte());
return data;
}
}

113
src/ImageSharp/Formats/Webp/Chunks/WebpVp8X.cs

@ -0,0 +1,113 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Webp.Chunks;
internal readonly struct WebpVp8X
{
public WebpVp8X(bool hasAnimation, bool hasXmp, bool hasExif, bool hasAlpha, bool hasIcc, uint width, uint height)
{
this.HasAnimation = hasAnimation;
this.HasXmp = hasXmp;
this.HasExif = hasExif;
this.HasAlpha = hasAlpha;
this.HasIcc = hasIcc;
this.Width = width;
this.Height = height;
}
/// <summary>
/// Gets a value indicating whether this is an animated image. Data in 'ANIM' and 'ANMF' Chunks should be used to control the animation.
/// </summary>
public bool HasAnimation { get; }
/// <summary>
/// Gets a value indicating whether the file contains XMP metadata.
/// </summary>
public bool HasXmp { get; }
/// <summary>
/// Gets a value indicating whether the file contains Exif metadata.
/// </summary>
public bool HasExif { get; }
/// <summary>
/// Gets a value indicating whether any of the frames of the image contain transparency information ("alpha").
/// </summary>
public bool HasAlpha { get; }
/// <summary>
/// Gets a value indicating whether the file contains an 'ICCP' Chunk.
/// </summary>
public bool HasIcc { get; }
/// <summary>
/// Gets width of the canvas in pixels. (uint24)
/// </summary>
public uint Width { get; }
/// <summary>
/// Gets height of the canvas in pixels. (uint24)
/// </summary>
public uint Height { get; }
public void Validate(uint maxDimension, ulong maxCanvasPixels)
{
if (this.Width > maxDimension || this.Height > maxDimension)
{
WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {maxDimension}");
}
// The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1.
if (this.Width * this.Height > maxCanvasPixels)
{
WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1");
}
}
public void WriteTo(Stream stream)
{
byte flags = 0;
if (this.HasAnimation)
{
// Set animated flag.
flags |= 2;
}
if (this.HasXmp)
{
// Set xmp bit.
flags |= 4;
}
if (this.HasExif)
{
// Set exif bit.
flags |= 8;
}
if (this.HasAlpha)
{
// Set alpha bit.
flags |= 16;
}
if (this.HasIcc)
{
// Set icc flag.
flags |= 32;
}
long pos = RiffHelper.BeginWriteChunk(stream, (uint)WebpChunkType.Vp8X);
stream.WriteByte(flags);
stream.Position += 3; // Reserved bytes
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Width - 1);
WebpChunkParsingUtils.WriteUInt24LittleEndian(stream, this.Height - 1);
RiffHelper.EndWriteChunk(stream, pos);
}
}

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

@ -6,7 +6,9 @@ using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -308,16 +310,15 @@ internal class Vp8LEncoder : IDisposable
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. // TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData prevPosition = new WebpFrameData(
{ 0,
X = 0, 0,
Y = 0, (uint)frame.Width,
Width = (uint)frame.Width, (uint)frame.Height,
Height = (uint)frame.Height, frameMetadata.FrameDelay,
Duration = frameMetadata.FrameDelay, frameMetadata.BlendMethod,
BlendingMethod = frameMetadata.BlendMethod, frameMetadata.DisposalMethod)
DisposalMethod = frameMetadata.DisposalMethod .WriteHeaderTo(stream);
});
} }
// Write bytes from the bitwriter buffer to the stream. // Write bytes from the bitwriter buffer to the stream.
@ -325,7 +326,7 @@ internal class Vp8LEncoder : IDisposable
if (hasAnimation) if (hasAnimation)
{ {
BitWriterBase.OverwriteFrameSize(stream, prevPosition); RiffHelper.EndWriteChunk(stream, prevPosition);
} }
} }

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

@ -4,7 +4,9 @@
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -478,16 +480,15 @@ internal class Vp8Encoder : IDisposable
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. // TODO: If we can clip the indexed frame for transparent bounds we can set properties here.
prevPosition = BitWriterBase.WriteAnimationFrame(stream, new WebpFrameData prevPosition = new WebpFrameData(
{ 0,
X = 0, 0,
Y = 0, (uint)frame.Width,
Width = (uint)frame.Width, (uint)frame.Height,
Height = (uint)frame.Height, frameMetadata.FrameDelay,
Duration = frameMetadata.FrameDelay, frameMetadata.BlendMethod,
BlendingMethod = frameMetadata.BlendMethod, frameMetadata.DisposalMethod)
DisposalMethod = frameMetadata.DisposalMethod .WriteHeaderTo(stream);
});
} }
if (hasAlpha) if (hasAlpha)
@ -501,7 +502,7 @@ internal class Vp8Encoder : IDisposable
if (hasAnimation) if (hasAnimation)
{ {
BitWriterBase.OverwriteFrameSize(stream, prevPosition); RiffHelper.EndWriteChunk(stream, prevPosition);
} }
} }
finally finally

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers; using System.Buffers;
using SixLabors.ImageSharp.Formats.Webp.Chunks;
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;
@ -99,7 +100,7 @@ internal class WebpAnimationDecoder : IDisposable
remainingBytes -= 4; remainingBytes -= 4;
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Animation: case WebpChunkType.FrameData:
Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
? new Color(new Bgra32(0, 0, 0, 0)) ? new Color(new Bgra32(0, 0, 0, 0))
: features.AnimationBackgroundColor!.Value; : features.AnimationBackgroundColor!.Value;

18
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -106,14 +106,14 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowImageFormatException("bad partition length"); WebpThrowHelper.ThrowImageFormatException("bad partition length");
} }
Vp8FrameHeader vp8FrameHeader = new Vp8FrameHeader Vp8FrameHeader vp8FrameHeader = new()
{ {
KeyFrame = true, KeyFrame = true,
Profile = (sbyte)version, Profile = (sbyte)version,
PartitionLength = partitionLength PartitionLength = partitionLength
}; };
Vp8BitReader bitReader = new Vp8BitReader(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining }; Vp8BitReader bitReader = new(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining };
return new WebpImageInfo return new WebpImageInfo
{ {
@ -139,7 +139,7 @@ internal static class WebpChunkParsingUtils
// VP8 data size. // VP8 data size.
uint imageDataSize = ReadChunkSize(stream, buffer); uint imageDataSize = ReadChunkSize(stream, buffer);
Vp8LBitReader bitReader = new Vp8LBitReader(stream, imageDataSize, memoryAllocator); Vp8LBitReader bitReader = new(stream, imageDataSize, memoryAllocator);
// One byte signature, should be 0x2f. // One byte signature, should be 0x2f.
uint signature = bitReader.ReadValue(8); uint signature = bitReader.ReadValue(8);
@ -231,7 +231,7 @@ internal static class WebpChunkParsingUtils
uint height = ReadUInt24LittleEndian(stream, buffer) + 1; uint height = ReadUInt24LittleEndian(stream, buffer) + 1;
// Read all the chunks in the order they occur. // Read all the chunks in the order they occur.
WebpImageInfo info = new WebpImageInfo WebpImageInfo info = new()
{ {
Width = width, Width = width,
Height = height, Height = height,
@ -247,7 +247,7 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read from.</param> /// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the read data into.</param> /// <param name="buffer">The buffer to store the read data into.</param>
/// <returns>A unsigned 24 bit integer.</returns> /// <returns>A unsigned 24 bit integer.</returns>
public static uint ReadUInt24LittleEndian(BufferedReadStream stream, Span<byte> buffer) public static uint ReadUInt24LittleEndian(Stream stream, Span<byte> buffer)
{ {
if (stream.Read(buffer, 0, 3) == 3) if (stream.Read(buffer, 0, 3) == 3)
{ {
@ -286,14 +286,14 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read the data from.</param> /// <param name="stream">The stream to read the data from.</param>
/// <param name="buffer">Buffer to store the data read from the stream.</param> /// <param name="buffer">Buffer to store the data read from the stream.</param>
/// <returns>The chunk size in bytes.</returns> /// <returns>The chunk size in bytes.</returns>
public static uint ReadChunkSize(BufferedReadStream stream, Span<byte> buffer) public static uint ReadChunkSize(Stream stream, Span<byte> buffer)
{ {
DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length"); DebugGuard.IsTrue(buffer.Length is 4, "buffer has wrong length");
if (stream.Read(buffer) == 4) if (stream.Read(buffer) is 4)
{ {
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return chunkSize % 2 == 0 ? chunkSize : chunkSize + 1; return chunkSize % 2 is 0 ? chunkSize : chunkSize + 1;
} }
throw new ImageFormatException("Invalid Webp data, could not read chunk size."); throw new ImageFormatException("Invalid Webp data, could not read chunk size.");

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

@ -61,5 +61,5 @@ internal enum WebpChunkType : uint
/// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present.
/// </summary> /// </summary>
/// <remarks>ANMF (Multiple)</remarks> /// <remarks>ANMF (Multiple)</remarks>
Animation = 0x414E4D46, FrameData = 0x414E4D46,
} }

10
src/ImageSharp/Formats/Webp/WebpConstants.cs

@ -55,6 +55,11 @@ internal static class WebpConstants
0x50 // P 0x50 // P
}; };
/// <summary>
/// The header bytes identifying a Webp.
/// </summary>
public const string WebpFourCc = "WEBP";
/// <summary> /// <summary>
/// 3 bits reserved for version. /// 3 bits reserved for version.
/// </summary> /// </summary>
@ -70,11 +75,6 @@ internal static class WebpConstants
/// </summary> /// </summary>
public const int Vp8FrameHeaderSize = 10; public const int Vp8FrameHeaderSize = 10;
/// <summary>
/// Size of a VP8X chunk in bytes.
/// </summary>
public const int Vp8XChunkSize = 10;
/// <summary> /// <summary>
/// Size of a chunk header. /// Size of a chunk header.
/// </summary> /// </summary>

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

@ -1,93 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Webp;
internal struct WebpFrameData
{
/// <summary>
/// The animation chunk size.
/// </summary>
public uint DataSize;
/// <summary>
/// X(3) + Y(3) + Width(3) + Height(3) + Duration(3) + 1 byte for flags.
/// </summary>
public const uint HeaderSize = 16;
/// <summary>
/// The X coordinate of the upper left corner of the frame is Frame X * 2.
/// </summary>
public uint X;
/// <summary>
/// The Y coordinate of the upper left corner of the frame is Frame Y * 2.
/// </summary>
public uint Y;
/// <summary>
/// The width of the frame.
/// </summary>
public uint Width;
/// <summary>
/// The height of the frame.
/// </summary>
public uint Height;
/// <summary>
/// The time to wait before displaying the next frame, in 1 millisecond units.
/// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined.
/// </summary>
public uint Duration;
/// <summary>
/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas.
/// </summary>
public WebpBlendingMethod BlendingMethod;
/// <summary>
/// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas.
/// </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>
/// <param name="stream">The stream to read from.</param>
/// <returns>Animation frame data.</returns>
public static WebpFrameData Parse(BufferedReadStream stream)
{
Span<byte> buffer = stackalloc byte[4];
WebpFrameData data = new()
{
DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer),
// 3 bytes for the X coordinate of the upper left corner of the frame.
X = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
// 3 bytes for the Y coordinate of the upper left corner of the frame.
Y = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer),
// Frame width Minus One.
Width = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
// Frame height Minus One.
Height = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer) + 1,
// Frame duration.
Duration = WebpChunkParsingUtils.ReadUInt24LittleEndian(stream, buffer)
};
byte flags = (byte)stream.ReadByte();
data.DisposalMethod = (flags & 1) == 1 ? WebpDisposalMethod.Dispose : WebpDisposalMethod.DoNotDispose;
data.BlendingMethod = (flags & (1 << 1)) != 0 ? WebpBlendingMethod.DoNotBlend : WebpBlendingMethod.AlphaBlending;
return data;
}
}
Loading…
Cancel
Save