diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
index 2971b2638..e6f5dfe3e 100644
--- a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
+++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
@@ -14,7 +14,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.WebP
{
///
- /// Performs the bitmap decoding operation.
+ /// Performs the webp decoding operation.
///
internal sealed class WebPDecoderCore
{
@@ -140,6 +140,10 @@ namespace SixLabors.ImageSharp.Formats.WebP
return chunkSize;
}
+ ///
+ /// Reads information present in the image header, about the image content and how to decode the image.
+ ///
+ /// Information about the webp image.
private WebPImageInfo ReadVp8Info()
{
this.Metadata = new ImageMetadata();
@@ -173,29 +177,39 @@ namespace SixLabors.ImageSharp.Formats.WebP
/// Information about this webp image.
private WebPImageInfo ReadVp8XHeader()
{
+ var features = new WebPFeatures();
uint chunkSize = this.ReadChunkSize();
// The first byte contains information about the image features used.
- // The first two bit of it are reserved and should be 0. TODO: should an exception be thrown if its not the case, or just ignore it?
byte imageFeatures = (byte)this.currentStream.ReadByte();
+ // The first two bit of it are reserved and should be 0.
+ if (imageFeatures >> 6 != 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero");
+ }
+
// If bit 3 is set, a ICC Profile Chunk should be present.
- bool isIccPresent = (imageFeatures & (1 << 5)) != 0;
+ features.IccProfile = (imageFeatures & (1 << 5)) != 0;
// If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk).
- bool isAlphaPresent = (imageFeatures & (1 << 4)) != 0;
+ features.Alpha = (imageFeatures & (1 << 4)) != 0;
// If bit 5 is set, a EXIF metadata should be present.
- bool isExifPresent = (imageFeatures & (1 << 3)) != 0;
+ features.ExifProfile = (imageFeatures & (1 << 3)) != 0;
// If bit 6 is set, XMP metadata should be present.
- bool isXmpPresent = (imageFeatures & (1 << 2)) != 0;
+ features.XmpMetaData = (imageFeatures & (1 << 2)) != 0;
// If bit 7 is set, animation should be present.
- bool isAnimationPresent = (imageFeatures & (1 << 1)) != 0;
+ features.Animation = (imageFeatures & (1 << 1)) != 0;
// 3 reserved bytes should follow which are supposed to be zero.
this.currentStream.Read(this.buffer, 0, 3);
+ if (this.buffer[0] != 0 || this.buffer[1] != 0 | this.buffer[2] != 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("reserved bytes should be zero");
+ }
// 3 bytes for the width.
this.currentStream.Read(this.buffer, 0, 3);
@@ -208,76 +222,25 @@ namespace SixLabors.ImageSharp.Formats.WebP
uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
// Optional chunks ICCP, ALPH and ANIM can follow here.
- WebPChunkType chunkType;
- if (isIccPresent)
+ WebPChunkType chunkType = this.ReadChunkType();
+ while (IsOptionalVp8XChunk(chunkType))
{
+ this.ParseOptionalExtendedChunks(chunkType, features);
chunkType = this.ReadChunkType();
- if (chunkType is WebPChunkType.Iccp)
- {
- uint iccpChunkSize = this.ReadChunkSize();
- if (!this.IgnoreMetadata)
- {
- var iccpData = new byte[iccpChunkSize];
- this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
- var profile = new IccProfile(iccpData);
- if (profile.CheckIsValid())
- {
- this.Metadata.IccProfile = profile;
- }
- }
- else
- {
- this.currentStream.Skip((int)iccpChunkSize);
- }
- }
}
- if (isAnimationPresent)
+ if (features.Animation)
{
- this.webpMetadata.Animated = true;
-
+ // TODO: Animations are not yet supported.
return new WebPImageInfo()
{
Width = width,
Height = height,
- Features = new WebPFeatures()
- {
- Animation = true
- }
+ Features = features
};
}
- byte[] alphaData = null;
- byte alphaChunkHeader = 0;
- if (isAlphaPresent)
- {
- chunkType = this.ReadChunkType();
- if (chunkType != WebPChunkType.Alpha)
- {
- WebPThrowHelper.ThrowImageFormatException($"unexpected chunk type {chunkType}, expected ALPH chunk is missing");
- }
-
- uint alphaChunkSize = this.ReadChunkSize();
- alphaChunkHeader = (byte)this.currentStream.ReadByte();
- alphaData = new byte[alphaChunkSize - 1];
- this.currentStream.Read(alphaData, 0, alphaData.Length);
- }
-
- var features = new WebPFeatures()
- {
- Animation = isAnimationPresent,
- Alpha = isAlphaPresent,
- AlphaData = alphaData,
- AlphaChunkHeader = alphaChunkHeader,
- ExifProfile = isExifPresent,
- IccProfile = isIccPresent,
- XmpMetaData = isXmpPresent
- };
-
- // A VP8 or VP8L chunk should follow here.
- chunkType = this.ReadChunkType();
-
- // TOOD: check if VP8 or VP8L info about the dimensions match VP8X info
+ // TODO: check if VP8 or VP8L info about the dimensions match VP8X info
switch (chunkType)
{
case WebPChunkType.Vp8:
@@ -446,6 +409,47 @@ namespace SixLabors.ImageSharp.Formats.WebP
};
}
+ ///
+ /// Parses optional VP8X chunks, which can be ICCP, ANIM or ALPH chunks.
+ ///
+ /// The chunk type.
+ /// The webp image features.
+ private void ParseOptionalExtendedChunks(WebPChunkType chunkType, WebPFeatures features)
+ {
+ switch (chunkType)
+ {
+ case WebPChunkType.Iccp:
+ uint iccpChunkSize = this.ReadChunkSize();
+ if (!this.IgnoreMetadata)
+ {
+ var iccpData = new byte[iccpChunkSize];
+ this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
+ var profile = new IccProfile(iccpData);
+ if (profile.CheckIsValid())
+ {
+ this.Metadata.IccProfile = profile;
+ }
+ }
+ else
+ {
+ this.currentStream.Skip((int)iccpChunkSize);
+ }
+
+ break;
+
+ case WebPChunkType.Animation:
+ this.webpMetadata.Animated = true;
+ break;
+
+ case WebPChunkType.Alpha:
+ uint alphaChunkSize = this.ReadChunkSize();
+ features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
+ features.AlphaData = new byte[alphaChunkSize - 1];
+ this.currentStream.Read(features.AlphaData, 0, features.AlphaData.Length);
+ break;
+ }
+ }
+
///
/// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP ').
/// If there are more such chunks, readers MAY ignore all except the first one.
@@ -512,5 +516,21 @@ namespace SixLabors.ImageSharp.Formats.WebP
throw new ImageFormatException("Invalid WebP data.");
}
+
+ ///
+ /// Determines if the chunk type is an optional VP8X chunk.
+ ///
+ /// The chunk type.
+ /// True, if its an optional chunk type.
+ private static bool IsOptionalVp8XChunk(WebPChunkType chunkType)
+ {
+ return chunkType switch
+ {
+ WebPChunkType.Alpha => true,
+ WebPChunkType.Animation => true,
+ WebPChunkType.Iccp => true,
+ _ => false
+ };
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
index c16ee86fd..0b26727d5 100644
--- a/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
+++ b/src/ImageSharp/Formats/WebP/WebPLossyDecoder.cs
@@ -12,6 +12,12 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.WebP
{
+ ///
+ /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp
+ ///
+ ///
+ /// The lossy specification can be found here: https://tools.ietf.org/html/rfc6386
+ ///
internal sealed class WebPLossyDecoder
{
///
diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
index 5ec22cbad..ff1307b54 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs
@@ -18,9 +18,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private byte[] webpLosslessBytes;
private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy);
+
private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless);
- [Params(TestImages.WebP.Lossy.Bike)]
+ [Params(TestImages.WebP.Lossy.Alpha1)]
public string TestImageLossy { get; set; }
[Params(TestImages.WebP.Lossless.BikeThreeTransforms)]