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