diff --git a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs
index 5946aeb67..60cca018c 100644
--- a/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/WebP/BitWriter/BitWriterBase.cs
@@ -4,6 +4,7 @@
using System;
using System.Buffers.Binary;
using System.IO;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
@@ -55,7 +56,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
/// Writes the encoded image to the stream.
///
/// The stream to write to.
- public abstract void WriteEncodedImageToStream(Stream stream);
+ /// The exif profile.
+ /// The width of the image.
+ /// The height of the image.
+ public abstract void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height);
protected bool ResizeBuffer(int maxBytes, int sizeRequired)
{
@@ -84,12 +88,68 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
/// The block length.
protected void WriteRiffHeader(Stream stream, uint riffSize)
{
- Span buffer = stackalloc byte[4];
-
+ Span buf = stackalloc byte[4];
stream.Write(WebpConstants.RiffFourCc);
- BinaryPrimitives.WriteUInt32LittleEndian(buffer, riffSize);
- stream.Write(buffer);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, riffSize);
+ stream.Write(buf);
stream.Write(WebpConstants.WebPHeader);
}
+
+ ///
+ /// Writes the Exif profile to the stream.
+ ///
+ /// The stream to write to.
+ /// The exif profile bytes.
+ protected void WriteExifProfile(Stream stream, byte[] exifBytes)
+ {
+ DebugGuard.NotNull(exifBytes, nameof(exifBytes));
+
+ Span buf = stackalloc byte[4];
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, (uint)exifBytes.Length);
+ stream.Write(buf);
+ stream.Write(exifBytes);
+ }
+
+ ///
+ /// Writes a VP8X header to the stream.
+ ///
+ /// The stream to write to.
+ /// A exif profile or null, if it does not exist.
+ /// The width of the image.
+ /// The height of the image.
+ protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint width, uint height)
+ {
+ int maxDimension = 16777215;
+ if (width > maxDimension || 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 (width * height > 4294967295ul)
+ {
+ 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;
+ }
+
+ Span buf = stackalloc byte[4];
+ stream.Write(WebpConstants.Vp8XMagicBytes);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, flags);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1);
+ stream.Write(buf.Slice(0, 3));
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1);
+ stream.Write(buf.Slice(0, 3));
+ }
}
}
diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
index b9d326ada..97d5cd38c 100644
--- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8BitWriter.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
@@ -73,16 +74,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
/// The expected size in bytes.
/// The Vp8Encoder.
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
- : this(expectedSize)
- {
- this.enc = enc;
- }
+ : this(expectedSize) => this.enc = enc;
///
- public override int NumBytes()
- {
- return (int)this.pos;
- }
+ public override int NumBytes() => (int)this.pos;
public int PutCoeffs(int ctx, Vp8Residual residual)
{
@@ -290,10 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
}
}
- private bool PutBit(bool bit, int prob)
- {
- return this.PutBit(bit ? 1 : 0, prob);
- }
+ private bool PutBit(bool bit, int prob) => this.PutBit(bit ? 1 : 0, prob);
private bool PutBit(int bit, int prob)
{
@@ -408,8 +400,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
}
///
- public override void WriteEncodedImageToStream(Stream stream)
+ public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
{
+ bool isVp8X = false;
+ byte[] exifBytes = null;
+ uint riffSize = 0;
+ if (exifProfile != null)
+ {
+ isVp8X = true;
+ riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
+ exifBytes = exifProfile.ToByteArray();
+ riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
+ }
+
this.Finish();
uint numBytes = (uint)this.NumBytes();
int mbSize = this.enc.Mbw * this.enc.Mbh;
@@ -427,10 +430,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
// Compute RIFF size
// At the minimum it is: "WEBPVP8 nnnn" + VP8 data size.
- var riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
+ riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0
- this.WriteWebPHeaders(stream, size0, vp8Size, riffSize);
+ this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@@ -439,6 +442,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
stream.WriteByte(0);
}
+
+ if (exifProfile != null)
+ {
+ this.WriteExifProfile(stream, exifBytes);
+ }
}
private uint GeneratePartition0(Vp8BitWriter bitWriter)
@@ -608,9 +616,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
while (it.Next());
}
- private void WriteWebPHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize)
+ private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile)
{
this.WriteRiffHeader(stream, riffSize);
+
+ // Write VP8X, header if necessary.
+ if (isVp8X)
+ {
+ this.WriteVp8XHeader(stream, exifProfile, width, height);
+ }
+
this.WriteVp8Header(stream, vp8Size);
this.WriteFrameHeader(stream, size0);
}
diff --git a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
index 6fbb7e3b7..4a560940f 100644
--- a/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/WebP/BitWriter/Vp8LBitWriter.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
{
@@ -127,9 +128,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
}
///
- public override void WriteEncodedImageToStream(Stream stream)
+ public override void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height)
{
Span buffer = stackalloc byte[4];
+ bool isVp8X = false;
+ byte[] exifBytes = null;
+ uint riffSize = 0;
+ if (exifProfile != null)
+ {
+ isVp8X = true;
+ riffSize += WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize;
+ exifBytes = exifProfile.ToByteArray();
+ riffSize += WebpConstants.ChunkHeaderSize + (uint)exifBytes.Length;
+ }
this.Finish();
uint size = (uint)this.NumBytes();
@@ -137,8 +148,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
// Write RIFF header.
uint pad = size & 1;
- uint riffSize = WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad;
+ riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad;
this.WriteRiffHeader(stream, riffSize);
+
+ // Write VP8X, header if necessary.
+ if (isVp8X)
+ {
+ this.WriteVp8XHeader(stream, exifProfile, width, height);
+ }
+
+ // Write magic bytes indicating its a lossless webp.
stream.Write(WebpConstants.Vp8LMagicBytes);
// Write Vp8 Header.
@@ -146,11 +165,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.BitWriter
stream.Write(buffer);
stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte);
+ // Write the encoded bytes of the image to the stream.
this.WriteToStream(stream);
if (pad == 1)
{
stream.WriteByte(0);
}
+
+ if (exifProfile != null)
+ {
+ this.WriteExifProfile(stream, exifBytes);
+ }
}
///
diff --git a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
index b2a254a61..49591c04a 100644
--- a/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossless/Vp8LEncoder.cs
@@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossless
this.EncodeStream(image);
// Write bytes from the bitwriter buffer to the stream.
- this.bitWriter.WriteEncodedImageToStream(stream);
+ this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height);
}
///
diff --git a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
index 0a51c006a..f404fc610 100644
--- a/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/WebP/Lossy/Vp8Encoder.cs
@@ -321,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp.Lossy
this.AdjustFilterStrength();
// Write bytes from the bitwriter buffer to the stream.
- this.bitWriter.WriteEncodedImageToStream(stream);
+ this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height);
}
///
diff --git a/src/ImageSharp/Formats/WebP/WebpConstants.cs b/src/ImageSharp/Formats/WebP/WebpConstants.cs
index 98c783a40..239e02d75 100644
--- a/src/ImageSharp/Formats/WebP/WebpConstants.cs
+++ b/src/ImageSharp/Formats/WebP/WebpConstants.cs
@@ -57,6 +57,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
0x4C // L
};
+ ///
+ /// Signature bytes identifying a VP8X header.
+ ///
+ public static readonly byte[] Vp8XMagicBytes =
+ {
+ 0x56, // V
+ 0x50, // P
+ 0x38, // 8
+ 0x58 // X
+ };
+
///
/// The header bytes identifying RIFF file.
///
@@ -94,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
///
public const int Vp8FrameHeaderSize = 10;
+ ///
+ /// Size of a VP8X chunk in bytes.
+ ///
+ public const int Vp8XChunkSize = 10;
+
///
/// Size of a chunk header.
///
diff --git a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs
index 879139a5d..06527aec0 100644
--- a/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/WebP/WebpDecoderCore.cs
@@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
this.Metadata = new ImageMetadata();
this.currentStream = stream;
- this.ReadImageHeader();
+ var fileSize = this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info())
{
@@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
///
/// Reads and skips over the image header.
///
- /// The chunk size in bytes.
+ /// The file size in bytes.
private uint ReadImageHeader()
{
// Skip FourCC header, we already know its a RIFF file at this point.
@@ -140,12 +140,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
// 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 chunkSize = this.ReadChunkSize();
+ uint fileSize = this.ReadChunkSize();
// Skip 'WEBP' from the header.
this.currentStream.Skip(4);
- return chunkSize;
+ return fileSize;
}
///
@@ -185,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
private WebpImageInfo ReadVp8XHeader()
{
var features = new WebpFeatures();
- uint chunkSize = this.ReadChunkSize();
+ uint fileSize = this.ReadChunkSize();
// The first byte contains information about the image features used.
byte imageFeatures = (byte)this.currentStream.ReadByte();
@@ -474,7 +474,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
WebpChunkType chunkType = this.ReadChunkType();
uint chunkLength = this.ReadChunkSize();
- if (chunkType == WebpChunkType.Exif)
+ if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null)
{
var exifData = new byte[chunkLength];
this.currentStream.Read(exifData, 0, (int)chunkLength);
@@ -482,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
}
else
{
- // Skip XMP chunk data for now.
+ // Skip XMP chunk data or any duplicate EXIF chunk.
this.currentStream.Skip((int)chunkLength);
}
}
diff --git a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs
index 4918e1ed3..13a62454b 100644
--- a/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs
+++ b/src/ImageSharp/Formats/WebP/WebpThrowHelper.cs
@@ -13,19 +13,20 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Webp
///
/// The error message for the exception.
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowImageFormatException(string errorMessage)
- {
- throw new ImageFormatException(errorMessage);
- }
+ public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
///
/// Cold path optimization for throwing -s
///
/// The error message for the exception.
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowNotSupportedException(string errorMessage)
- {
- throw new NotSupportedException(errorMessage);
- }
+ public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage);
+
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage);
}
}