diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
index 4252f895b8..b82b764fc3 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
@@ -4,6 +4,7 @@
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
@@ -41,17 +42,23 @@ internal abstract class BitWriterBase
public byte[] Buffer => this.buffer;
+ ///
+ /// Gets the number of bytes of the encoded image data.
+ ///
+ /// The number of bytes of the image data.
+ public abstract int NumBytes { get; }
+
///
/// Writes the encoded bytes of the image to the stream. Call Finish() before this.
///
/// The stream to write to.
- public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes()));
+ public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes));
///
/// Writes the encoded bytes of the image to the given buffer. Call Finish() before this.
///
/// The destination buffer.
- public void WriteToBuffer(Span dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest);
+ public void WriteToBuffer(Span dest) => this.Buffer.AsSpan(0, this.NumBytes).CopyTo(dest);
///
/// Resizes the buffer to write to.
@@ -59,12 +66,6 @@ internal abstract class BitWriterBase
/// The extra size in bytes needed.
public abstract void BitWriterResize(int extraSize);
- ///
- /// Returns the number of bytes of the encoded image data.
- ///
- /// The number of bytes of the image data.
- public abstract int NumBytes();
-
///
/// Flush leftover bits.
///
@@ -86,6 +87,7 @@ internal abstract class BitWriterBase
///
/// Writes the RIFF header to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
/// The block length.
protected void WriteRiffHeader(Stream stream, uint riffSize)
@@ -99,6 +101,7 @@ internal abstract class BitWriterBase
///
/// Calculates the chunk size of EXIF, XMP or ICCP metadata.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The metadata profile bytes.
/// The metadata chunk size in bytes.
protected static uint MetadataChunkSize(byte[] metadataBytes)
@@ -118,9 +121,26 @@ internal abstract class BitWriterBase
return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
}
+ ///
+ /// Overwrites ides the write file size.
+ ///
+ /// The stream to write to.
+ protected static void OverwriteFileSize(Stream stream)
+ {
+ uint position = (uint)stream.Position;
+ stream.Position = 4;
+ byte[] buffer = new byte[4];
+
+ // "RIFF"(4)+uint32 size(4)
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer, position - WebpConstants.ChunkHeaderSize);
+ stream.Write(buffer);
+ stream.Position = position;
+ }
+
///
/// Writes a metadata profile (EXIF or XMP) to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
/// The metadata profile's bytes.
/// The chuck type to write.
@@ -146,6 +166,7 @@ internal abstract class BitWriterBase
///
/// Writes the color profile() to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
/// The color profile bytes.
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
@@ -153,6 +174,7 @@ internal abstract class BitWriterBase
///
/// Writes the animation parameter() to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
///
/// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
@@ -177,6 +199,7 @@ internal abstract class BitWriterBase
///
/// Writes the animation frame() to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
/// Animation frame data.
/// Frame data.
@@ -201,6 +224,7 @@ internal abstract class BitWriterBase
///
/// Writes the alpha chunk to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
/// The alpha channel data bytes.
/// Indicates, if the alpha channel data is compressed.
@@ -232,14 +256,15 @@ internal abstract class BitWriterBase
///
/// Writes a VP8X header to the stream.
///
+ /// Think of it as a static method — none of the other members are called except for
/// 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 color profile.
/// 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, byte[]? iccProfileBytes, uint width, uint height, bool hasAlpha)
+ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha)
{
if (width > MaxDimension || height > MaxDimension)
{
@@ -279,7 +304,7 @@ internal abstract class BitWriterBase
flags |= 16;
}
- if (iccProfileBytes != null)
+ if (iccProfile != null)
{
// Set iccp flag.
flags |= 32;
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
index cd84f109eb..5dd5d335de 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
@@ -72,7 +72,7 @@ internal class Vp8BitWriter : BitWriterBase
}
///
- public override int NumBytes() => (int)this.pos;
+ public override int NumBytes => (int)this.pos;
public int PutCoeffs(int ctx, Vp8Residual residual)
{
@@ -395,67 +395,58 @@ internal class Vp8BitWriter : BitWriterBase
}
///
- /// Writes the encoded image to the stream.
+ /// Write the trunks before data trunk.
///
+ /// Think of it as a static method — none of the other members are called except for
/// The stream to write to.
+ /// The width of the image.
+ /// The height of the image.
/// 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.
/// The alpha channel data.
/// Indicates, if the alpha data is compressed.
- public void WriteEncodedImageToStream(
+ public void WriteTrunksBeforeData(
Stream stream,
+ uint width,
+ uint height,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
IccProfile? iccProfile,
- uint width,
- uint height,
bool hasAlpha,
Span alphaData,
bool alphaDataIsCompressed)
{
- bool isVp8X = false;
- byte[]? exifBytes = null;
- byte[]? xmpBytes = null;
- byte[]? iccProfileBytes = null;
- uint riffSize = 0;
- if (exifProfile != null)
- {
- isVp8X = true;
- exifBytes = exifProfile.ToByteArray();
- riffSize += MetadataChunkSize(exifBytes!);
- }
+ // Write file size later
+ this.WriteRiffHeader(stream, 0);
- if (xmpProfile != null)
+ // Write VP8X, header if necessary.
+ bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha;
+ if (isVp8X)
{
- isVp8X = true;
- xmpBytes = xmpProfile.Data;
- riffSize += MetadataChunkSize(xmpBytes!);
- }
+ this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha);
- if (iccProfile != null)
- {
- isVp8X = true;
- iccProfileBytes = iccProfile.ToByteArray();
- riffSize += MetadataChunkSize(iccProfileBytes);
- }
+ if (iccProfile != null)
+ {
+ this.WriteColorProfile(stream, iccProfile.ToByteArray());
+ }
- if (hasAlpha)
- {
- isVp8X = true;
- riffSize += AlphaChunkSize(alphaData);
+ if (hasAlpha)
+ {
+ this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
+ }
}
+ }
- if (isVp8X)
- {
- riffSize += ExtendedFileChunkSize;
- }
+ ///
+ /// Writes the encoded image to the stream.
+ ///
+ /// The stream to write to.
+ public void WriteEncodedImageToStream(Stream stream)
+ {
+ uint numBytes = (uint)this.NumBytes;
- this.Finish();
- uint numBytes = (uint)this.NumBytes();
int mbSize = this.enc.Mbw * this.enc.Mbh;
int expectedSize = (int)((uint)mbSize * 7 / 8);
@@ -469,12 +460,10 @@ internal class Vp8BitWriter : BitWriterBase
uint pad = vp8Size & 1;
vp8Size += pad;
- // Compute RIFF size.
- // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size.
- riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
+ // Emit header and partition #0
+ this.WriteVp8Header(stream, vp8Size);
+ this.WriteFrameHeader(stream, size0);
- // Emit headers and partition #0
- 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.
@@ -483,16 +472,31 @@ internal class Vp8BitWriter : BitWriterBase
{
stream.WriteByte(0);
}
+ }
+ ///
+ /// Write the trunks after data trunk.
+ ///
+ /// Think of it as a static method — none of the other members are called except for
+ /// The stream to write to.
+ /// The exif profile.
+ /// The XMP profile.
+ public void WriteTrunksAfterData(
+ Stream stream,
+ ExifProfile? exifProfile,
+ XmpProfile? xmpProfile)
+ {
if (exifProfile != null)
{
- this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif);
+ this.WriteMetadataProfile(stream, exifProfile.ToByteArray(), WebpChunkType.Exif);
}
if (xmpProfile != null)
{
- this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp);
+ this.WriteMetadataProfile(stream, xmpProfile.Data, WebpChunkType.Xmp);
}
+
+ OverwriteFileSize(stream);
}
private uint GeneratePartition0()
@@ -512,7 +516,7 @@ internal class Vp8BitWriter : BitWriterBase
this.Finish();
- return (uint)this.NumBytes();
+ return (uint)this.NumBytes;
}
private void WriteSegmentHeader()
@@ -662,43 +666,6 @@ internal class Vp8BitWriter : BitWriterBase
while (it.Next());
}
- private void WriteWebpHeaders(
- Stream stream,
- uint size0,
- uint vp8Size,
- uint riffSize,
- bool isVp8X,
- uint width,
- uint height,
- ExifProfile? exifProfile,
- XmpProfile? xmpProfile,
- byte[]? iccProfileBytes,
- bool hasAlpha,
- Span alphaData,
- bool alphaDataIsCompressed)
- {
- this.WriteRiffHeader(stream, riffSize);
-
- // Write VP8X, header if necessary.
- if (isVp8X)
- {
- this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfileBytes, width, height, hasAlpha);
-
- if (iccProfileBytes != null)
- {
- this.WriteColorProfile(stream, iccProfileBytes);
- }
-
- if (hasAlpha)
- {
- this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
- }
- }
-
- this.WriteVp8Header(stream, vp8Size);
- this.WriteFrameHeader(stream, size0);
- }
-
private void WriteVp8Header(Stream stream, uint size)
{
Span buf = stackalloc byte[WebpConstants.TagSize];
diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
index a042f68968..0ac1b4038a 100644
--- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
+++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
@@ -47,6 +47,9 @@ internal class Vp8LBitWriter : BitWriterBase
{
}
+ ///
+ public override int NumBytes => this.cur + ((this.used + 7) >> 3);
+
///
/// Initializes a new instance of the class.
/// Used internally for cloning.
@@ -98,9 +101,6 @@ internal class Vp8LBitWriter : BitWriterBase
this.PutBits((uint)((bits << depth) | symbol), depth + nBits);
}
- ///
- public override int NumBytes() => this.cur + ((this.used + 7) >> 3);
-
public Vp8LBitWriter Clone()
{
byte[] clonedBuffer = new byte[this.Buffer.Length];
@@ -166,7 +166,7 @@ internal class Vp8LBitWriter : BitWriterBase
}
this.Finish();
- uint size = (uint)this.NumBytes();
+ uint size = (uint)this.NumBytes;
size++; // One byte extra for the VP8L signature.
// Write RIFF header.
@@ -177,7 +177,7 @@ internal class Vp8LBitWriter : BitWriterBase
// Write VP8X, header if necessary.
if (isVp8X)
{
- this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccBytes, width, height, hasAlpha);
+ this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha);
if (iccBytes != null)
{
diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
index 9b82cc5983..d27bfcd956 100644
--- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
@@ -291,7 +291,7 @@ internal class Vp8LEncoder : IDisposable
// The image-stream will NOT contain any headers describing the image dimension, the dimension is already known.
this.EncodeStream(frame);
this.bitWriter.Finish();
- int size = this.bitWriter.NumBytes();
+ int size = this.bitWriter.NumBytes;
if (size >= pixelCount)
{
// Compressing would not yield in smaller data -> leave the data uncompressed.
@@ -425,9 +425,9 @@ internal class Vp8LEncoder : IDisposable
lowEffort);
// If we are better than what we already have.
- if (isFirstConfig || this.bitWriter.NumBytes() < bestSize)
+ if (isFirstConfig || this.bitWriter.NumBytes < bestSize)
{
- bestSize = this.bitWriter.NumBytes();
+ bestSize = this.bitWriter.NumBytes;
BitWriterSwap(ref this.bitWriter, ref bitWriterBest);
}
@@ -676,7 +676,7 @@ internal class Vp8LEncoder : IDisposable
this.StoreImageToBitMask(width, this.HistoBits, refsBest, histogramSymbols, huffmanCodes);
// Keep track of the smallest image so far.
- if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes()))
+ if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes < bitWriterBest.NumBytes))
{
Vp8LBitWriter tmp = this.bitWriter;
this.bitWriter = bitWriterBest;
diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
index c65099af88..56397e66d4 100644
--- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
+++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Webp.BitWriter;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
using SixLabors.ImageSharp.PixelFormats;
@@ -408,16 +409,21 @@ internal class Vp8Encoder : IDisposable
}
}
- this.bitWriter.WriteEncodedImageToStream(
+ this.bitWriter.Finish();
+ this.bitWriter.WriteTrunksBeforeData(
stream,
+ (uint)width,
+ (uint)height,
exifProfile,
xmpProfile,
metadata.IccProfile,
- (uint)width,
- (uint)height,
hasAlpha,
alphaData[..alphaDataSize],
this.alphaCompression && alphaCompressionSucceeded);
+
+ this.bitWriter.WriteEncodedImageToStream(stream);
+
+ this.bitWriter.WriteTrunksAfterData(stream, exifProfile, xmpProfile);
}
finally
{
@@ -862,10 +868,11 @@ internal class Vp8Encoder : IDisposable
this.ResetSegments();
}
- this.SegmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) +
- (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) +
- (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) +
- (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2])));
+ this.SegmentHeader.Size =
+ (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) +
+ (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) +
+ (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) +
+ (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2])));
}
else
{
@@ -1027,7 +1034,7 @@ internal class Vp8Encoder : IDisposable
it.NzToBytes();
- int pos1 = this.bitWriter.NumBytes();
+ int pos1 = this.bitWriter.NumBytes;
if (i16)
{
residual.Init(0, 1, this.Proba);
@@ -1054,7 +1061,7 @@ internal class Vp8Encoder : IDisposable
}
}
- int pos2 = this.bitWriter.NumBytes();
+ int pos2 = this.bitWriter.NumBytes;
// U/V
residual.Init(0, 2, this.Proba);
@@ -1072,7 +1079,7 @@ internal class Vp8Encoder : IDisposable
}
}
- int pos3 = this.bitWriter.NumBytes();
+ int pos3 = this.bitWriter.NumBytes;
it.LumaBits = pos2 - pos1;
it.UvBits = pos3 - pos2;
it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits;
diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
index becd622e17..9e9f0f7f62 100644
--- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
+++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
@@ -76,7 +76,7 @@ internal static class WebpChunkParsingUtils
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes");
}
- if (!buffer.Slice(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
+ if (!buffer[..3].SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
{
WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found");
}
@@ -111,14 +111,7 @@ internal static class WebpChunkParsingUtils
PartitionLength = partitionLength
};
- Vp8BitReader bitReader = new(
- stream,
- remaining,
- memoryAllocator,
- partitionLength)
- {
- Remaining = remaining
- };
+ Vp8BitReader bitReader = new(stream, remaining, memoryAllocator, partitionLength) { Remaining = remaining };
return new()
{
diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
index 3b5e438299..be7350bc44 100644
--- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
@@ -158,8 +158,7 @@ public sealed class IccProfile : IDeepCloneable
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) &&
Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) &&
Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) &&
- this.Header.Size >= minSize &&
- this.Header.Size < maxSize;
+ this.Header.Size is >= minSize and < maxSize;
}
///
@@ -175,7 +174,6 @@ public sealed class IccProfile : IDeepCloneable
return copy;
}
- IccWriter writer = new();
return IccWriter.Write(this);
}