diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index fc1accfdee..0e1c9f4d95 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -5,6 +5,7 @@
using System.Buffers.Binary;
using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
@@ -97,7 +98,7 @@ protected void WriteRiffHeader(Stream stream, uint riffSize)
}
///
- /// Calculates the chunk size of EXIF or XMP metadata.
+ /// Calculates the chunk size of EXIF, XMP or ICCP metadata.
///
/// The metadata profile bytes.
/// The metadata chunk size in bytes.
@@ -178,16 +179,41 @@ protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDa
}
}
+ ///
+ /// Writes the color profile to the stream.
+ ///
+ /// The stream to write to.
+ /// The color profile bytes.
+ protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes)
+ {
+ uint size = (uint)iccProfileBytes.Length;
+
+ Span buf = this.scratchBuffer.AsSpan(0, 4);
+ BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp);
+ stream.Write(buf);
+ BinaryPrimitives.WriteUInt32LittleEndian(buf, size);
+ stream.Write(buf);
+
+ stream.Write(iccProfileBytes);
+
+ // Add padding byte if needed.
+ if ((size & 1) == 1)
+ {
+ stream.WriteByte(0);
+ }
+ }
+
///
/// Writes a VP8X header to the stream.
///
/// The stream to write to.
/// A exif profile or null, if it does not exist.
/// A XMP profile or null, if it does not exist.
+ /// The color profile bytes.
/// The width of the image.
/// The height of the image.
/// Flag indicating, if a alpha channel is present.
- protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha)
+ protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, byte[] iccProfileBytes, uint width, uint height, bool hasAlpha)
{
if (width > MaxDimension || height > MaxDimension)
{
@@ -219,6 +245,12 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfil
flags |= 16;
}
+ if (iccProfileBytes != null)
+ {
+ // Set iccp flag.
+ flags |= 32;
+ }
+
Span buf = this.scratchBuffer.AsSpan(0, 4);
stream.Write(WebpConstants.Vp8XMagicBytes);
BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize);
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index fa6e09d875..7cc915e18f 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -6,6 +6,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
@@ -406,6 +407,7 @@ private void Flush()
/// The stream to write to.
/// The exif profile.
/// The XMP profile.
+ /// The color profile.
/// The width of the image.
/// The height of the image.
/// Flag indicating, if a alpha channel is present.
@@ -415,6 +417,7 @@ public void WriteEncodedImageToStream(
Stream stream,
ExifProfile exifProfile,
XmpProfile xmpProfile,
+ IccProfile iccProfile,
uint width,
uint height,
bool hasAlpha,
@@ -424,6 +427,7 @@ public void WriteEncodedImageToStream(
bool isVp8X = false;
byte[] exifBytes = null;
byte[] xmpBytes = null;
+ byte[] iccProfileBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
@@ -439,6 +443,13 @@ public void WriteEncodedImageToStream(
riffSize += this.MetadataChunkSize(xmpBytes);
}
+ if (iccProfile != null)
+ {
+ isVp8X = true;
+ iccProfileBytes = iccProfile.ToByteArray();
+ riffSize += this.MetadataChunkSize(iccProfileBytes);
+ }
+
if (hasAlpha)
{
isVp8X = true;
@@ -457,7 +468,7 @@ public void WriteEncodedImageToStream(
var bitWriterPartZero = new Vp8BitWriter(expectedSize);
- // Partition #0 with header and partition sizes
+ // Partition #0 with header and partition sizes.
uint size0 = this.GeneratePartition0(bitWriterPartZero);
uint vp8Size = WebpConstants.Vp8FrameHeaderSize + size0;
@@ -465,12 +476,12 @@ public void WriteEncodedImageToStream(
uint pad = vp8Size & 1;
vp8Size += pad;
- // Compute RIFF size
+ // Compute RIFF size.
// At the minimum it is: "WEBPVP8 nnnn" + VP8 data size.
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
// Emit headers and partition #0
- this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha, alphaData, alphaDataIsCompressed);
+ this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, iccProfileBytes, hasAlpha, alphaData, alphaDataIsCompressed);
bitWriterPartZero.WriteToStream(stream);
// Write the encoded image to the stream.
@@ -668,6 +679,7 @@ private void WriteWebpHeaders(
uint height,
ExifProfile exifProfile,
XmpProfile xmpProfile,
+ byte[] iccProfileBytes,
bool hasAlpha,
Span alphaData,
bool alphaDataIsCompressed)
@@ -677,7 +689,13 @@ private void WriteWebpHeaders(
// Write VP8X, header if necessary.
if (isVp8X)
{
- this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
+ this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfileBytes, width, height, hasAlpha);
+
+ if (iccProfileBytes != null)
+ {
+ this.WriteColorProfile(stream, iccProfileBytes);
+ }
+
if (hasAlpha)
{
this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
index d41224f908..7bd6febeb6 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
@@ -6,6 +6,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
@@ -134,19 +135,20 @@ public override void Finish()
/// The stream to write to.
/// The exif profile.
/// The XMP profile.
+ /// The color profile.
/// The width of the image.
/// The height of the image.
/// Flag indicating, if a alpha channel is present.
- public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha)
+ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, IccProfile iccProfile, uint width, uint height, bool hasAlpha)
{
bool isVp8X = false;
byte[] exifBytes = null;
byte[] xmpBytes = null;
+ byte[] iccBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
- riffSize += ExtendedFileChunkSize;
exifBytes = exifProfile.ToByteArray();
riffSize += this.MetadataChunkSize(exifBytes);
}
@@ -154,11 +156,22 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, Xm
if (xmpProfile != null)
{
isVp8X = true;
- riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.Data;
riffSize += this.MetadataChunkSize(xmpBytes);
}
+ if (iccProfile != null)
+ {
+ isVp8X = true;
+ iccBytes = iccProfile.ToByteArray();
+ riffSize += this.MetadataChunkSize(iccBytes);
+ }
+
+ if (isVp8X)
+ {
+ riffSize += ExtendedFileChunkSize;
+ }
+
this.Finish();
uint size = (uint)this.NumBytes();
size++; // One byte extra for the VP8L signature.
@@ -171,7 +184,12 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, Xm
// Write VP8X, header if necessary.
if (isVp8X)
{
- this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha);
+ this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccBytes, width, height, hasAlpha);
+
+ if (iccBytes != null)
+ {
+ this.WriteColorProfile(stream, iccBytes);
+ }
}
// Write magic bytes indicating its a lossless webp.
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index 30d65562ae..8de1ccc420 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -255,7 +255,7 @@ public void Encode(Image image, Stream stream)
this.EncodeStream(image);
// Write bytes from the bitwriter buffer to the stream.
- this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha);
+ this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha);
}
///
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index 695359e5ea..f24fdb45d8 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -378,6 +378,7 @@ public void Encode(Image image, Stream stream)
stream,
metadata.ExifProfile,
metadata.XmpProfile,
+ metadata.IccProfile,
(uint)width,
(uint)height,
hasAlpha,
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
index 456b9a3f52..d1f148be02 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
@@ -152,6 +152,37 @@ public void EncodeLosslessWebp_PreservesExif(TestImageProvider p
Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count);
}
+ [Theory]
+ [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossless)]
+ [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossy)]
+ public void Encode_PreservesColorProfile(TestImageProvider provider, WebpFileFormatType fileFormat)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image input = provider.GetImage(new WebpDecoder()))
+ {
+ ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile;
+ byte[] expectedProfileBytes = expectedProfile.ToByteArray();
+
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, new WebpEncoder()
+ {
+ FileFormat = fileFormat
+ });
+
+ memStream.Position = 0;
+ using (var output = Image.Load(memStream))
+ {
+ ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile;
+ byte[] actualProfileBytes = actualProfile.ToByteArray();
+
+ Assert.NotNull(actualProfile);
+ Assert.Equal(expectedProfileBytes, actualProfileBytes);
+ }
+ }
+ }
+ }
+
[Theory]
[WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)]
public void WebpDecoder_IgnoresInvalidExifChunk(TestImageProvider provider)