mirror of https://github.com/SixLabors/ImageSharp
11 changed files with 349 additions and 335 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue