Browse Source

(Vp8) Write total size after writing. Separate the writes of each block

pull/2569/head
Poker 3 years ago
parent
commit
95d36af396
No known key found for this signature in database GPG Key ID: C65A6AD457D5C8F8
  1. 47
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  2. 135
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  3. 10
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  4. 8
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  5. 27
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  6. 11
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  7. 4
      src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs

47
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;
/// <summary>
/// Gets the number of bytes of the encoded image data.
/// </summary>
/// <returns>The number of bytes of the image data.</returns>
public abstract int NumBytes { get; }
/// <summary>
/// Writes the encoded bytes of the image to the stream. Call Finish() before this.
/// </summary>
/// <param name="stream">The stream to write to.</param>
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));
/// <summary>
/// Writes the encoded bytes of the image to the given buffer. Call Finish() before this.
/// </summary>
/// <param name="dest">The destination buffer.</param>
public void WriteToBuffer(Span<byte> dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest);
public void WriteToBuffer(Span<byte> dest) => this.Buffer.AsSpan(0, this.NumBytes).CopyTo(dest);
/// <summary>
/// Resizes the buffer to write to.
@ -59,12 +66,6 @@ internal abstract class BitWriterBase
/// <param name="extraSize">The extra size in bytes needed.</param>
public abstract void BitWriterResize(int extraSize);
/// <summary>
/// Returns the number of bytes of the encoded image data.
/// </summary>
/// <returns>The number of bytes of the image data.</returns>
public abstract int NumBytes();
/// <summary>
/// Flush leftover bits.
/// </summary>
@ -86,6 +87,7 @@ internal abstract class BitWriterBase
/// <summary>
/// Writes the RIFF header to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="riffSize">The block length.</param>
protected void WriteRiffHeader(Stream stream, uint riffSize)
@ -99,6 +101,7 @@ internal abstract class BitWriterBase
/// <summary>
/// Calculates the chunk size of EXIF, XMP or ICCP metadata.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="metadataBytes">The metadata profile bytes.</param>
/// <returns>The metadata chunk size in bytes.</returns>
protected static uint MetadataChunkSize(byte[] metadataBytes)
@ -118,9 +121,26 @@ internal abstract class BitWriterBase
return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1);
}
/// <summary>
/// Overwrites ides the write file size.
/// </summary>
/// <param name="stream">The stream to write to.</param>
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;
}
/// <summary>
/// Writes a metadata profile (EXIF or XMP) to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="metadataBytes">The metadata profile's bytes.</param>
/// <param name="chunkType">The chuck type to write.</param>
@ -146,6 +166,7 @@ internal abstract class BitWriterBase
/// <summary>
/// Writes the color profile(<see cref="WebpChunkType.Iccp"/>) to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="iccProfileBytes">The color profile bytes.</param>
protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) => this.WriteMetadataProfile(stream, iccProfileBytes, WebpChunkType.Iccp);
@ -153,6 +174,7 @@ internal abstract class BitWriterBase
/// <summary>
/// Writes the animation parameter(<see cref="WebpChunkType.AnimationParameter"/>) to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="background">
/// The default background color of the canvas in [Blue, Green, Red, Alpha] byte order.
@ -177,6 +199,7 @@ internal abstract class BitWriterBase
/// <summary>
/// Writes the animation frame(<see cref="WebpChunkType.Animation"/>) to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="animation">Animation frame data.</param>
/// <param name="data">Frame data.</param>
@ -201,6 +224,7 @@ internal abstract class BitWriterBase
/// <summary>
/// Writes the alpha chunk to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="dataBytes">The alpha channel data bytes.</param>
/// <param name="alphaDataIsCompressed">Indicates, if the alpha channel data is compressed.</param>
@ -232,14 +256,15 @@ internal abstract class BitWriterBase
/// <summary>
/// Writes a VP8X header to the stream.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="exifProfile">A exif profile or null, if it does not exist.</param>
/// <param name="xmpProfile">A XMP profile or null, if it does not exist.</param>
/// <param name="iccProfileBytes">The color profile bytes.</param>
/// <param name="iccProfile">The color profile.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
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;

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

@ -72,7 +72,7 @@ internal class Vp8BitWriter : BitWriterBase
}
/// <inheritdoc/>
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
}
/// <summary>
/// Writes the encoded image to the stream.
/// Write the trunks before data trunk.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="BitWriterBase.scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="xmpProfile">The XMP profile.</param>
/// <param name="iccProfile">The color profile.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="hasAlpha">Flag indicating, if a alpha channel is present.</param>
/// <param name="alphaData">The alpha channel data.</param>
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
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<byte> 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;
}
/// <summary>
/// Writes the encoded image to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
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);
}
}
/// <summary>
/// Write the trunks after data trunk.
/// </summary>
/// <remarks>Think of it as a static method — none of the other members are called except for <see cref="BitWriterBase.scratchBuffer"/></remarks>
/// <param name="stream">The stream to write to.</param>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="xmpProfile">The XMP profile.</param>
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<byte> 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<byte> buf = stackalloc byte[WebpConstants.TagSize];

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

@ -47,6 +47,9 @@ internal class Vp8LBitWriter : BitWriterBase
{
}
/// <inheritdoc/>
public override int NumBytes => this.cur + ((this.used + 7) >> 3);
/// <summary>
/// Initializes a new instance of the <see cref="Vp8LBitWriter"/> class.
/// Used internally for cloning.
@ -98,9 +101,6 @@ internal class Vp8LBitWriter : BitWriterBase
this.PutBits((uint)((bits << depth) | symbol), depth + nBits);
}
/// <inheritdoc/>
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)
{

8
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;

27
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;

11
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()
{

4
src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs

@ -158,8 +158,7 @@ public sealed class IccProfile : IDeepCloneable<IccProfile>
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;
}
/// <summary>
@ -175,7 +174,6 @@ public sealed class IccProfile : IDeepCloneable<IccProfile>
return copy;
}
IccWriter writer = new();
return IccWriter.Write(this);
}

Loading…
Cancel
Save