Skip to content

Commit 95d36af

Browse files
committed
(Vp8) Write total size after writing. Separate the writes of each block
1 parent 62ab3a1 commit 95d36af

7 files changed

Lines changed: 116 additions & 126 deletions

File tree

src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Buffers.Binary;
55
using System.Runtime.InteropServices;
66
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
7+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
78
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
89

910
namespace SixLabors.ImageSharp.Formats.Webp.BitWriter;
@@ -41,30 +42,30 @@ internal abstract class BitWriterBase
4142

4243
public byte[] Buffer => this.buffer;
4344

45+
/// <summary>
46+
/// Gets the number of bytes of the encoded image data.
47+
/// </summary>
48+
/// <returns>The number of bytes of the image data.</returns>
49+
public abstract int NumBytes { get; }
50+
4451
/// <summary>
4552
/// Writes the encoded bytes of the image to the stream. Call Finish() before this.
4653
/// </summary>
4754
/// <param name="stream">The stream to write to.</param>
48-
public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes()));
55+
public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes));
4956

5057
/// <summary>
5158
/// Writes the encoded bytes of the image to the given buffer. Call Finish() before this.
5259
/// </summary>
5360
/// <param name="dest">The destination buffer.</param>
54-
public void WriteToBuffer(Span<byte> dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest);
61+
public void WriteToBuffer(Span<byte> dest) => this.Buffer.AsSpan(0, this.NumBytes).CopyTo(dest);
5562

5663
/// <summary>
5764
/// Resizes the buffer to write to.
5865
/// </summary>
5966
/// <param name="extraSize">The extra size in bytes needed.</param>
6067
public abstract void BitWriterResize(int extraSize);
6168

62-
/// <summary>
63-
/// Returns the number of bytes of the encoded image data.
64-
/// </summary>
65-
/// <returns>The number of bytes of the image data.</returns>
66-
public abstract int NumBytes();
67-
6869
/// <summary>
6970
/// Flush leftover bits.
7071
/// </summary>
@@ -86,6 +87,7 @@ protected void ResizeBuffer(int maxBytes, int sizeRequired)
8687
/// <summary>
8788
/// Writes the RIFF header to the stream.
8889
/// </summary>
90+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
8991
/// <param name="stream">The stream to write to.</param>
9092
/// <param name="riffSize">The block length.</param>
9193
protected void WriteRiffHeader(Stream stream, uint riffSize)
@@ -99,6 +101,7 @@ protected void WriteRiffHeader(Stream stream, uint riffSize)
99101
/// <summary>
100102
/// Calculates the chunk size of EXIF, XMP or ICCP metadata.
101103
/// </summary>
104+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
102105
/// <param name="metadataBytes">The metadata profile bytes.</param>
103106
/// <returns>The metadata chunk size in bytes.</returns>
104107
protected static uint MetadataChunkSize(byte[] metadataBytes)
@@ -118,9 +121,26 @@ protected static uint AlphaChunkSize(Span<byte> alphaBytes)
118121
return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
119122
}
120123

124+
/// <summary>
125+
/// Overwrites ides the write file size.
126+
/// </summary>
127+
/// <param name="stream">The stream to write to.</param>
128+
protected static void OverwriteFileSize(Stream stream)
129+
{
130+
uint position = (uint)stream.Position;
131+
stream.Position = 4;
132+
byte[] buffer = new byte[4];
133+
134+
// "RIFF"(4)+uint32 size(4)
135+
BinaryPrimitives.WriteUInt32LittleEndian(buffer, position - WebpConstants.ChunkHeaderSize);
136+
stream.Write(buffer);
137+
stream.Position = position;
138+
}
139+
121140
/// <summary>
122141
/// Writes a metadata profile (EXIF or XMP) to the stream.
123142
/// </summary>
143+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
124144
/// <param name="stream">The stream to write to.</param>
125145
/// <param name="metadataBytes">The metadata profile's bytes.</param>
126146
/// <param name="chunkType">The chuck type to write.</param>
@@ -146,13 +166,15 @@ protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpCh
146166
/// <summary>
147167
/// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
148168
/// </summary>
169+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
149170
/// <param name="stream">The stream to write to.</param>
150171
/// <param name="iccProfileBytes">The color profile bytes.</param>
151172
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
152173

153174
/// <summary>
154175
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
155176
/// </summary>
177+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
156178
/// <param name="stream">The stream to write to.</param>
157179
/// <param name="background">
158180
/// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
@@ -177,6 +199,7 @@ protected void WriteAnimationParameter(Stream stream, uint background, ushort lo
177199
/// <summary>
178200
/// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
179201
/// </summary>
202+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
180203
/// <param name="stream">The stream to write to.</param>
181204
/// <param name="animation">Animation frame data.</param>
182205
/// <param name="data">Frame data.</param>
@@ -201,6 +224,7 @@ protected void WriteAnimationFrame(Stream stream, AnimationFrameData animation,
201224
/// <summary>
202225
/// Writes the alpha chunk to the stream.
203226
/// </summary>
227+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
204228
/// <param name="stream">The stream to write to.</param>
205229
/// <param name="dataBytes">The alpha channel data bytes.</param>
206230
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
@@ -232,14 +256,15 @@ protected void WriteAlphaChunk(Stream stream, Span<byte> dataBytes, bool alphaDa
232256
/// <summary>
233257
/// Writes a VP8X header to the stream.
234258
/// </summary>
259+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
235260
/// <param name="stream">The stream to write to.</param>
236261
/// <param name="exifProfile">A exif profile or null, if it does not exist.</param>
237262
/// <param name="xmpProfile">A XMP profile or null, if it does not exist.</param>
238-
/// <param name="iccProfileBytes">The color profile bytes.</param>
263+
/// <param name="iccProfile">The color profile.</param>
239264
/// <param name="width">The width of the image.</param>
240265
/// <param name="height">The height of the image.</param>
241266
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
242-
protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, byte[]? iccProfileBytes, uint width, uint height, bool hasAlpha)
267+
protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfile? xmpProfile, IccProfile? iccProfile, uint width, uint height, bool hasAlpha)
243268
{
244269
if (width > MaxDimension || height > MaxDimension)
245270
{
@@ -279,7 +304,7 @@ protected void WriteVp8XHeader(Stream stream, ExifProfile? exifProfile, XmpProfi
279304
flags |= 16;
280305
}
281306

282-
if (iccProfileBytes != null)
307+
if (iccProfile != null)
283308
{
284309
// Set iccp flag.
285310
flags |= 32;

src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

Lines changed: 51 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
7272
}
7373

7474
/// <inheritdoc/>
75-
public override int NumBytes() => (int)this.pos;
75+
public override int NumBytes => (int)this.pos;
7676

7777
public int PutCoeffs(int ctx, Vp8Residual residual)
7878
{
@@ -395,67 +395,58 @@ private void Flush()
395395
}
396396

397397
/// <summary>
398-
/// Writes the encoded image to the stream.
398+
/// Write the trunks before data trunk.
399399
/// </summary>
400+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="BitWriterBase.scratchBuffer"/></remarks>
400401
/// <param name="stream">The stream to write to.</param>
402+
/// <param name="width">The width of the image.</param>
403+
/// <param name="height">The height of the image.</param>
401404
/// <param name="exifProfile">The exif profile.</param>
402405
/// <param name="xmpProfile">The XMP profile.</param>
403406
/// <param name="iccProfile">The color profile.</param>
404-
/// <param name="width">The width of the image.</param>
405-
/// <param name="height">The height of the image.</param>
406407
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
407408
/// <param name="alphaData">The alpha channel data.</param>
408409
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
409-
public void WriteEncodedImageToStream(
410+
public void WriteTrunksBeforeData(
410411
Stream stream,
412+
uint width,
413+
uint height,
411414
ExifProfile? exifProfile,
412415
XmpProfile? xmpProfile,
413416
IccProfile? iccProfile,
414-
uint width,
415-
uint height,
416417
bool hasAlpha,
417418
Span<byte> alphaData,
418419
bool alphaDataIsCompressed)
419420
{
420-
bool isVp8X = false;
421-
byte[]? exifBytes = null;
422-
byte[]? xmpBytes = null;
423-
byte[]? iccProfileBytes = null;
424-
uint riffSize = 0;
425-
if (exifProfile != null)
426-
{
427-
isVp8X = true;
428-
exifBytes = exifProfile.ToByteArray();
429-
riffSize += MetadataChunkSize(exifBytes!);
430-
}
421+
// Write file size later
422+
this.WriteRiffHeader(stream, 0);
431423

432-
if (xmpProfile != null)
424+
// Write VP8X, header if necessary.
425+
bool isVp8X = exifProfile != null || xmpProfile != null || iccProfile != null || hasAlpha;
426+
if (isVp8X)
433427
{
434-
isVp8X = true;
435-
xmpBytes = xmpProfile.Data;
436-
riffSize += MetadataChunkSize(xmpBytes!);
437-
}
428+
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha);
438429

439-
if (iccProfile != null)
440-
{
441-
isVp8X = true;
442-
iccProfileBytes = iccProfile.ToByteArray();
443-
riffSize += MetadataChunkSize(iccProfileBytes);
444-
}
430+
if (iccProfile != null)
431+
{
432+
this.WriteColorProfile(stream, iccProfile.ToByteArray());
433+
}
445434

446-
if (hasAlpha)
447-
{
448-
isVp8X = true;
449-
riffSize += AlphaChunkSize(alphaData);
435+
if (hasAlpha)
436+
{
437+
this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
438+
}
450439
}
440+
}
451441

452-
if (isVp8X)
453-
{
454-
riffSize += ExtendedFileChunkSize;
455-
}
442+
/// <summary>
443+
/// Writes the encoded image to the stream.
444+
/// </summary>
445+
/// <param name="stream">The stream to write to.</param>
446+
public void WriteEncodedImageToStream(Stream stream)
447+
{
448+
uint numBytes = (uint)this.NumBytes;
456449

457-
this.Finish();
458-
uint numBytes = (uint)this.NumBytes();
459450
int mbSize = this.enc.Mbw * this.enc.Mbh;
460451
int expectedSize = (int)((uint)mbSize * 7 / 8);
461452

@@ -469,12 +460,10 @@ public void WriteEncodedImageToStream(
469460
uint pad = vp8Size & 1;
470461
vp8Size += pad;
471462

472-
// Compute RIFF size.
473-
// At the minimum it is: "WEBPVP8 nnnn" + VP8 data size.
474-
riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size;
463+
// Emit header and partition #0
464+
this.WriteVp8Header(stream, vp8Size);
465+
this.WriteFrameHeader(stream, size0);
475466

476-
// Emit headers and partition #0
477-
this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, iccProfileBytes, hasAlpha, alphaData, alphaDataIsCompressed);
478467
bitWriterPartZero.WriteToStream(stream);
479468

480469
// Write the encoded image to the stream.
@@ -483,16 +472,31 @@ public void WriteEncodedImageToStream(
483472
{
484473
stream.WriteByte(0);
485474
}
475+
}
486476

477+
/// <summary>
478+
/// Write the trunks after data trunk.
479+
/// </summary>
480+
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="BitWriterBase.scratchBuffer"/></remarks>
481+
/// <param name="stream">The stream to write to.</param>
482+
/// <param name="exifProfile">The exif profile.</param>
483+
/// <param name="xmpProfile">The XMP profile.</param>
484+
public void WriteTrunksAfterData(
485+
Stream stream,
486+
ExifProfile? exifProfile,
487+
XmpProfile? xmpProfile)
488+
{
487489
if (exifProfile != null)
488490
{
489-
this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif);
491+
this.WriteMetadataProfile(stream, exifProfile.ToByteArray(), WebpChunkType.Exif);
490492
}
491493

492494
if (xmpProfile != null)
493495
{
494-
this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp);
496+
this.WriteMetadataProfile(stream, xmpProfile.Data, WebpChunkType.Xmp);
495497
}
498+
499+
OverwriteFileSize(stream);
496500
}
497501

498502
private uint GeneratePartition0()
@@ -512,7 +516,7 @@ private uint GeneratePartition0()
512516

513517
this.Finish();
514518

515-
return (uint)this.NumBytes();
519+
return (uint)this.NumBytes;
516520
}
517521

518522
private void WriteSegmentHeader()
@@ -662,43 +666,6 @@ private void CodeIntraModes()
662666
while (it.Next());
663667
}
664668

665-
private void WriteWebpHeaders(
666-
Stream stream,
667-
uint size0,
668-
uint vp8Size,
669-
uint riffSize,
670-
bool isVp8X,
671-
uint width,
672-
uint height,
673-
ExifProfile? exifProfile,
674-
XmpProfile? xmpProfile,
675-
byte[]? iccProfileBytes,
676-
bool hasAlpha,
677-
Span<byte> alphaData,
678-
bool alphaDataIsCompressed)
679-
{
680-
this.WriteRiffHeader(stream, riffSize);
681-
682-
// Write VP8X, header if necessary.
683-
if (isVp8X)
684-
{
685-
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfileBytes, width, height, hasAlpha);
686-
687-
if (iccProfileBytes != null)
688-
{
689-
this.WriteColorProfile(stream, iccProfileBytes);
690-
}
691-
692-
if (hasAlpha)
693-
{
694-
this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed);
695-
}
696-
}
697-
698-
this.WriteVp8Header(stream, vp8Size);
699-
this.WriteFrameHeader(stream, size0);
700-
}
701-
702669
private void WriteVp8Header(Stream stream, uint size)
703670
{
704671
Span<byte> buf = stackalloc byte[WebpConstants.TagSize];

src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public Vp8LBitWriter(int expectedSize)
4747
{
4848
}
4949

50+
/// <inheritdoc/>
51+
public override int NumBytes => this.cur + ((this.used + 7) >> 3);
52+
5053
/// <summary>
5154
/// Initializes a new instance of the <see cref="Vp8LBitWriter"/> class.
5255
/// Used internally for cloning.
@@ -98,9 +101,6 @@ public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, i
98101
this.PutBits((uint)((bits << depth) | symbol), depth + nBits);
99102
}
100103

101-
/// <inheritdoc/>
102-
public override int NumBytes() => this.cur + ((this.used + 7) >> 3);
103-
104104
public Vp8LBitWriter Clone()
105105
{
106106
byte[] clonedBuffer = new byte[this.Buffer.Length];
@@ -166,7 +166,7 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, X
166166
}
167167

168168
this.Finish();
169-
uint size = (uint)this.NumBytes();
169+
uint size = (uint)this.NumBytes;
170170
size++; // One byte extra for the VP8L signature.
171171

172172
// Write RIFF header.
@@ -177,7 +177,7 @@ public void WriteEncodedImageToStream(Stream stream, ExifProfile? exifProfile, X
177177
// Write VP8X, header if necessary.
178178
if (isVp8X)
179179
{
180-
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccBytes, width, height, hasAlpha);
180+
this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfile, width, height, hasAlpha);
181181

182182
if (iccBytes != null)
183183
{

0 commit comments

Comments
 (0)