|
|
|
@ -18,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Webp; |
|
|
|
/// </summary>
|
|
|
|
internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// Reusable buffer.
|
|
|
|
/// </summary>
|
|
|
|
private readonly byte[] buffer = new byte[4]; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// General configuration options.
|
|
|
|
/// </summary>
|
|
|
|
@ -80,8 +75,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
try |
|
|
|
{ |
|
|
|
ImageMetadata metadata = new(); |
|
|
|
Span<byte> buffer = stackalloc byte[4]; |
|
|
|
|
|
|
|
uint fileSize = this.ReadImageHeader(stream); |
|
|
|
uint fileSize = ReadImageHeader(stream, buffer); |
|
|
|
|
|
|
|
using (this.webImageInfo = this.ReadVp8Info(stream, metadata)) |
|
|
|
{ |
|
|
|
@ -112,7 +108,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
// There can be optional chunks after the image data, like EXIF and XMP.
|
|
|
|
if (this.webImageInfo.Features != null) |
|
|
|
{ |
|
|
|
this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features); |
|
|
|
this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features, buffer); |
|
|
|
} |
|
|
|
|
|
|
|
return image; |
|
|
|
@ -128,7 +124,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// <inheritdoc />
|
|
|
|
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
this.ReadImageHeader(stream); |
|
|
|
ReadImageHeader(stream, stackalloc byte[4]); |
|
|
|
|
|
|
|
ImageMetadata metadata = new(); |
|
|
|
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true)) |
|
|
|
@ -144,8 +140,9 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// Reads and skips over the image header.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
/// <returns>The file size in bytes.</returns>
|
|
|
|
private uint ReadImageHeader(BufferedReadStream stream) |
|
|
|
private static uint ReadImageHeader(BufferedReadStream stream, Span<byte> buffer) |
|
|
|
{ |
|
|
|
// Skip FourCC header, we already know its a RIFF file at this point.
|
|
|
|
stream.Skip(4); |
|
|
|
@ -153,7 +150,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
// Read file size.
|
|
|
|
// The size of the file in bytes starting at offset 8.
|
|
|
|
// The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
|
|
|
|
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); |
|
|
|
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); |
|
|
|
|
|
|
|
// Skip 'WEBP' from the header.
|
|
|
|
stream.Skip(4); |
|
|
|
@ -172,35 +169,36 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
{ |
|
|
|
WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance); |
|
|
|
|
|
|
|
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); |
|
|
|
Span<byte> buffer = stackalloc byte[4]; |
|
|
|
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); |
|
|
|
|
|
|
|
WebpFeatures features = new(); |
|
|
|
switch (chunkType) |
|
|
|
{ |
|
|
|
case WebpChunkType.Vp8: |
|
|
|
webpMetadata.FileFormat = WebpFileFormatType.Lossy; |
|
|
|
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); |
|
|
|
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); |
|
|
|
case WebpChunkType.Vp8L: |
|
|
|
webpMetadata.FileFormat = WebpFileFormatType.Lossless; |
|
|
|
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); |
|
|
|
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); |
|
|
|
case WebpChunkType.Vp8X: |
|
|
|
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features); |
|
|
|
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, buffer, features); |
|
|
|
while (stream.Position < stream.Length) |
|
|
|
{ |
|
|
|
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); |
|
|
|
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); |
|
|
|
if (chunkType == WebpChunkType.Vp8) |
|
|
|
{ |
|
|
|
webpMetadata.FileFormat = WebpFileFormatType.Lossy; |
|
|
|
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); |
|
|
|
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, buffer, features); |
|
|
|
} |
|
|
|
else if (chunkType == WebpChunkType.Vp8L) |
|
|
|
{ |
|
|
|
webpMetadata.FileFormat = WebpFileFormatType.Lossless; |
|
|
|
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); |
|
|
|
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, buffer, features); |
|
|
|
} |
|
|
|
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) |
|
|
|
{ |
|
|
|
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha); |
|
|
|
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer); |
|
|
|
if (isAnimationChunk) |
|
|
|
{ |
|
|
|
return webpInfos; |
|
|
|
@ -209,7 +207,7 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
else |
|
|
|
{ |
|
|
|
// Ignore unknown chunks.
|
|
|
|
uint chunkSize = this.ReadChunkSize(stream); |
|
|
|
uint chunkSize = ReadChunkSize(stream, buffer); |
|
|
|
stream.Skip((int)chunkSize); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -230,34 +228,36 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// <param name="chunkType">The chunk type.</param>
|
|
|
|
/// <param name="features">The webp image features.</param>
|
|
|
|
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param></param>
|
|
|
|
/// <returns>true, if its a alpha chunk.</returns>
|
|
|
|
private bool ParseOptionalExtendedChunks( |
|
|
|
BufferedReadStream stream, |
|
|
|
ImageMetadata metadata, |
|
|
|
WebpChunkType chunkType, |
|
|
|
WebpFeatures features, |
|
|
|
bool ignoreAlpha) |
|
|
|
bool ignoreAlpha, |
|
|
|
Span<byte> buffer) |
|
|
|
{ |
|
|
|
switch (chunkType) |
|
|
|
{ |
|
|
|
case WebpChunkType.Iccp: |
|
|
|
this.ReadIccProfile(stream, metadata); |
|
|
|
this.ReadIccProfile(stream, metadata, buffer); |
|
|
|
break; |
|
|
|
|
|
|
|
case WebpChunkType.Exif: |
|
|
|
this.ReadExifProfile(stream, metadata); |
|
|
|
this.ReadExifProfile(stream, metadata, buffer); |
|
|
|
break; |
|
|
|
|
|
|
|
case WebpChunkType.Xmp: |
|
|
|
this.ReadXmpProfile(stream, metadata); |
|
|
|
this.ReadXmpProfile(stream, metadata, buffer); |
|
|
|
break; |
|
|
|
|
|
|
|
case WebpChunkType.AnimationParameter: |
|
|
|
this.ReadAnimationParameters(stream, features); |
|
|
|
ReadAnimationParameters(stream, features, buffer); |
|
|
|
return true; |
|
|
|
|
|
|
|
case WebpChunkType.Alpha: |
|
|
|
this.ReadAlphaData(stream, features, ignoreAlpha); |
|
|
|
this.ReadAlphaData(stream, features, ignoreAlpha, buffer); |
|
|
|
break; |
|
|
|
default: |
|
|
|
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); |
|
|
|
@ -273,7 +273,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="metadata">The image metadata.</param>
|
|
|
|
/// <param name="features">The webp features.</param>
|
|
|
|
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features) |
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span<byte> buffer) |
|
|
|
{ |
|
|
|
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) |
|
|
|
{ |
|
|
|
@ -284,19 +285,19 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
while (stream.Position < streamLength) |
|
|
|
{ |
|
|
|
// Read chunk header.
|
|
|
|
WebpChunkType chunkType = this.ReadChunkType(stream); |
|
|
|
WebpChunkType chunkType = ReadChunkType(stream, buffer); |
|
|
|
if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null) |
|
|
|
{ |
|
|
|
this.ReadExifProfile(stream, metadata); |
|
|
|
this.ReadExifProfile(stream, metadata, buffer); |
|
|
|
} |
|
|
|
else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null) |
|
|
|
{ |
|
|
|
this.ReadXmpProfile(stream, metadata); |
|
|
|
this.ReadXmpProfile(stream, metadata, buffer); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Skip duplicate XMP or EXIF chunk.
|
|
|
|
uint chunkLength = this.ReadChunkSize(stream); |
|
|
|
uint chunkLength = ReadChunkSize(stream, buffer); |
|
|
|
stream.Skip((int)chunkLength); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -307,9 +308,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="metadata">The image metadata.</param>
|
|
|
|
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata) |
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer) |
|
|
|
{ |
|
|
|
uint exifChunkSize = this.ReadChunkSize(stream); |
|
|
|
uint exifChunkSize = ReadChunkSize(stream, buffer); |
|
|
|
if (this.skipMetadata) |
|
|
|
{ |
|
|
|
stream.Skip((int)exifChunkSize); |
|
|
|
@ -333,9 +335,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="metadata">The image metadata.</param>
|
|
|
|
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata) |
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer) |
|
|
|
{ |
|
|
|
uint xmpChunkSize = this.ReadChunkSize(stream); |
|
|
|
uint xmpChunkSize = ReadChunkSize(stream, buffer); |
|
|
|
if (this.skipMetadata) |
|
|
|
{ |
|
|
|
stream.Skip((int)xmpChunkSize); |
|
|
|
@ -359,9 +362,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="metadata">The image metadata.</param>
|
|
|
|
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata) |
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer) |
|
|
|
{ |
|
|
|
uint iccpChunkSize = this.ReadChunkSize(stream); |
|
|
|
uint iccpChunkSize = ReadChunkSize(stream, buffer); |
|
|
|
if (this.skipMetadata) |
|
|
|
{ |
|
|
|
stream.Skip((int)iccpChunkSize); |
|
|
|
@ -388,22 +392,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="features">The webp features.</param>
|
|
|
|
private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features) |
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private static void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features, Span<byte> buffer) |
|
|
|
{ |
|
|
|
features.Animation = true; |
|
|
|
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); |
|
|
|
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); |
|
|
|
byte blue = (byte)stream.ReadByte(); |
|
|
|
byte green = (byte)stream.ReadByte(); |
|
|
|
byte red = (byte)stream.ReadByte(); |
|
|
|
byte alpha = (byte)stream.ReadByte(); |
|
|
|
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha)); |
|
|
|
int bytesRead = stream.Read(this.buffer, 0, 2); |
|
|
|
int bytesRead = stream.Read(buffer, 0, 2); |
|
|
|
if (bytesRead != 2) |
|
|
|
{ |
|
|
|
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count"); |
|
|
|
} |
|
|
|
|
|
|
|
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer); |
|
|
|
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -412,9 +417,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="features">The features.</param>
|
|
|
|
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
|
|
|
|
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha) |
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha, Span<byte> buffer) |
|
|
|
{ |
|
|
|
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); |
|
|
|
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer); |
|
|
|
if (ignoreAlpha) |
|
|
|
{ |
|
|
|
stream.Skip((int)alphaChunkSize); |
|
|
|
@ -436,14 +442,15 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// Identifies the chunk type from the chunk.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
/// <exception cref="ImageFormatException">
|
|
|
|
/// Thrown if the input stream is not valid.
|
|
|
|
/// </exception>
|
|
|
|
private WebpChunkType ReadChunkType(BufferedReadStream stream) |
|
|
|
private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer) |
|
|
|
{ |
|
|
|
if (stream.Read(this.buffer, 0, 4) == 4) |
|
|
|
if (stream.Read(buffer, 0, 4) == 4) |
|
|
|
{ |
|
|
|
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); |
|
|
|
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); |
|
|
|
} |
|
|
|
|
|
|
|
throw new ImageFormatException("Invalid Webp data."); |
|
|
|
@ -454,13 +461,14 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable |
|
|
|
/// so the chunk size will be increased by 1 in those cases.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="stream">The stream to decode from.</param>
|
|
|
|
/// <param name="buffer">Temporary buffer.</param>
|
|
|
|
/// <returns>The chunk size in bytes.</returns>
|
|
|
|
/// <exception cref="ImageFormatException">Invalid data.</exception>
|
|
|
|
private uint ReadChunkSize(BufferedReadStream stream) |
|
|
|
private static uint ReadChunkSize(BufferedReadStream stream, Span<byte> buffer) |
|
|
|
{ |
|
|
|
if (stream.Read(this.buffer, 0, 4) == 4) |
|
|
|
if (stream.Read(buffer, 0, 4) == 4) |
|
|
|
{ |
|
|
|
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); |
|
|
|
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); |
|
|
|
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; |
|
|
|
} |
|
|
|
|
|
|
|
|