Browse Source

Merge branch 'main' into js/premultiply

pull/2369/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
aed484a72c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 68
      src/ImageSharp/Formats/Webp/AlphaDecoder.cs
  2. 26
      src/ImageSharp/Formats/Webp/AlphaEncoder.cs
  3. 21
      src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs
  4. 4
      src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs
  5. 7
      src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs
  6. 4
      src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs
  7. 38
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  8. 13
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  9. 47
      src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs
  10. 31
      src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs
  11. 5
      src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs
  12. 19
      src/ImageSharp/Formats/Webp/Lossless/CostManager.cs
  13. 3
      src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs
  14. 10
      src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs
  15. 25
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  16. 1
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  17. 220
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  18. 2
      src/ImageSharp/Formats/Webp/WebpEncoder.cs
  19. 12
      src/ImageSharp/Formats/Webp/WebpEncoderCore.cs
  20. 9
      src/ImageSharp/Formats/Webp/WebpImageInfo.cs
  21. 6
      src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

68
src/ImageSharp/Formats/Webp/AlphaDecoder.cs

@ -1,8 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
@ -38,7 +38,7 @@ internal class AlphaDecoder : IDisposable
this.LastRow = 0;
int totalPixels = width * height;
var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03);
WebpAlphaCompressionMethod compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03);
if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression)
{
WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found");
@ -59,7 +59,7 @@ internal class AlphaDecoder : IDisposable
if (this.Compressed)
{
var bitReader = new Vp8LBitReader(data);
Vp8LBitReader bitReader = new(data);
this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration);
this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true);
@ -110,6 +110,7 @@ internal class AlphaDecoder : IDisposable
/// <summary>
/// Gets a value indicating whether the alpha channel uses compression.
/// </summary>
[MemberNotNullWhen(true, nameof(LosslessDecoder))]
private bool Compressed { get; }
/// <summary>
@ -120,7 +121,7 @@ internal class AlphaDecoder : IDisposable
/// <summary>
/// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
/// </summary>
private WebpLosslessDecoder LosslessDecoder { get; }
private WebpLosslessDecoder? LosslessDecoder { get; }
/// <summary>
/// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding.
@ -173,17 +174,14 @@ internal class AlphaDecoder : IDisposable
dst = dst[this.Width..];
}
}
else if (this.Use8BDecode)
{
this.LosslessDecoder.DecodeAlphaData(this);
}
else
{
if (this.Use8BDecode)
{
this.LosslessDecoder.DecodeAlphaData(this);
}
else
{
this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
this.ExtractAlphaRows(this.Vp8LDec);
}
this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span);
this.ExtractAlphaRows(this.Vp8LDec);
}
}
@ -261,8 +259,7 @@ internal class AlphaDecoder : IDisposable
{
int numRowsToProcess = dec.Height;
int width = dec.Width;
Span<uint> pixels = dec.Pixels.Memory.Span;
Span<uint> input = pixels;
Span<uint> input = dec.Pixels.Memory.Span;
Span<byte> output = this.Alpha.Memory.Span;
// Extract alpha (which is stored in the green plane).
@ -327,7 +324,7 @@ internal class AlphaDecoder : IDisposable
ref byte srcRef = ref MemoryMarshal.GetReference(input);
for (i = 1; i + 8 <= width; i += 8)
{
var a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<long> a0 = Vector128.Create(Unsafe.As<byte, long>(ref Unsafe.Add(ref srcRef, i)), 0);
Vector128<byte> a1 = Sse2.Add(a0.AsByte(), last.AsByte());
Vector128<byte> a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1);
Vector128<byte> a3 = Sse2.Add(a1, a2);
@ -365,32 +362,29 @@ internal class AlphaDecoder : IDisposable
{
HorizontalUnfilter(null, input, dst, width);
}
else
else if (Avx2.IsSupported)
{
if (Avx2.IsSupported)
nint i;
int maxPos = width & ~31;
for (i = 0; i < maxPos; i += 32)
{
nint i;
int maxPos = width & ~31;
for (i = 0; i < maxPos; i += 32)
{
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i));
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i));
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
}
Vector256<int> a0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i));
Vector256<int> b0 = Unsafe.As<byte, Vector256<int>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i));
Vector256<byte> c0 = Avx2.Add(a0.AsByte(), b0.AsByte());
ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i);
Unsafe.As<byte, Vector256<byte>>(ref outputRef) = c0;
}
for (; i < width; i++)
{
dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]);
}
for (; i < width; i++)
{
dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]);
}
else
}
else
{
for (int i = 0; i < width; i++)
{
for (int i = 0; i < width; i++)
{
dst[i] = (byte)(prev[i] + input[i]);
}
dst[i] = (byte)(prev[i] + input[i]);
}
}
}

26
src/ImageSharp/Formats/Webp/AlphaEncoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
@ -13,10 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Webp;
/// <summary>
/// Methods for encoding the alpha data of a VP8 image.
/// </summary>
internal class AlphaEncoder : IDisposable
internal static class AlphaEncoder
{
private IMemoryOwner<byte> alphaData;
/// <summary>
/// Encodes the alpha channel data.
/// Data is either compressed as lossless webp image or uncompressed.
@ -29,12 +26,18 @@ internal class AlphaEncoder : IDisposable
/// <param name="compress">Indicates, if the data should be compressed with the lossless webp compression.</param>
/// <param name="size">The size in bytes of the alpha data.</param>
/// <returns>The encoded alpha data.</returns>
public IMemoryOwner<byte> EncodeAlpha<TPixel>(Image<TPixel> image, Configuration configuration, MemoryAllocator memoryAllocator, bool skipMetadata, bool compress, out int size)
public static IMemoryOwner<byte> EncodeAlpha<TPixel>(
Image<TPixel> image,
Configuration configuration,
MemoryAllocator memoryAllocator,
bool skipMetadata,
bool compress,
out int size)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = image.Width;
int height = image.Height;
this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
IMemoryOwner<byte> alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator);
if (compress)
{
@ -55,15 +58,15 @@ internal class AlphaEncoder : IDisposable
// The transparency information will be stored in the green channel of the ARGB quadruplet.
// The green channel is allowed extra transformation steps in the specification -- unlike the other channels,
// that can improve compression.
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan());
using Image<Rgba32> alphaAsImage = DispatchAlphaToGreen(image, alphaData.GetSpan());
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData);
size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, alphaData);
return this.alphaData;
return alphaData;
}
size = width * height;
return this.alphaData;
return alphaData;
}
/// <summary>
@ -128,7 +131,4 @@ internal class AlphaEncoder : IDisposable
return alphaDataBuffer;
}
/// <inheritdoc/>
public void Dispose() => this.alphaData?.Dispose();
}

21
src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -14,10 +13,16 @@ internal abstract class BitReaderBase : IDisposable
{
private bool isDisposed;
protected BitReaderBase(IMemoryOwner<byte> data)
=> this.Data = data;
protected BitReaderBase(Stream inputStream, int imageDataSize, MemoryAllocator memoryAllocator)
=> this.Data = ReadImageDataFromStream(inputStream, imageDataSize, memoryAllocator);
/// <summary>
/// Gets or sets the raw encoded image data.
/// Gets the raw encoded image data.
/// </summary>
public IMemoryOwner<byte> Data { get; set; }
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Copies the raw encoded image data from the stream into a byte array.
@ -25,11 +30,13 @@ internal abstract class BitReaderBase : IDisposable
/// <param name="input">The input stream.</param>
/// <param name="bytesToRead">Number of bytes to read as indicated from the chunk size.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
protected static IMemoryOwner<byte> ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
{
this.Data = memoryAllocator.Allocate<byte>(bytesToRead);
Span<byte> dataSpan = this.Data.Memory.Span;
IMemoryOwner<byte> data = memoryAllocator.Allocate<byte>(bytesToRead);
Span<byte> dataSpan = data.Memory.Span;
input.Read(dataSpan[..bytesToRead], 0, bytesToRead);
return data;
}
protected virtual void Dispose(bool disposing)
@ -41,7 +48,7 @@ internal abstract class BitReaderBase : IDisposable
if (disposing)
{
this.Data?.Dispose();
this.Data.Dispose();
}
this.isDisposed = true;

4
src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs

@ -57,12 +57,12 @@ internal class Vp8BitReader : BitReaderBase
/// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0)
: base(inputStream, (int)imageDataSize, memoryAllocator)
{
Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize));
this.ImageDataSize = imageDataSize;
this.PartitionLength = partitionLength;
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.InitBitreader(partitionLength, startPos);
}
@ -73,8 +73,8 @@ internal class Vp8BitReader : BitReaderBase
/// <param name="partitionLength">The partition length.</param>
/// <param name="startPos">Start index in the data array. Defaults to 0.</param>
public Vp8BitReader(IMemoryOwner<byte> imageData, uint partitionLength, int startPos = 0)
: base(imageData)
{
this.Data = imageData;
this.ImageDataSize = (uint)imageData.Memory.Length;
this.PartitionLength = partitionLength;
this.InitBitreader(partitionLength, startPos);

7
src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs

@ -63,8 +63,8 @@ internal class Vp8LBitReader : BitReaderBase
/// </summary>
/// <param name="data">Lossless compressed image data.</param>
public Vp8LBitReader(IMemoryOwner<byte> data)
: base(data)
{
this.Data = data;
this.len = data.Memory.Length;
this.value = 0;
this.bitPos = 0;
@ -88,11 +88,10 @@ internal class Vp8LBitReader : BitReaderBase
/// <param name="imageDataSize">The raw image data size in bytes.</param>
/// <param name="memoryAllocator">Used for allocating memory during reading data from the stream.</param>
public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
: base(inputStream, (int)imageDataSize, memoryAllocator)
{
long length = imageDataSize;
this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
this.len = length;
this.value = 0;
this.bitPos = 0;
@ -193,7 +192,7 @@ internal class Vp8LBitReader : BitReaderBase
[MethodImpl(InliningOptions.ShortMethod)]
private void ShiftBytes()
{
System.Span<byte> dataSpan = this.Data.Memory.Span;
System.Span<byte> dataSpan = this.Data!.Memory.Span;
while (this.bitPos >= 8 && this.pos < this.len)
{
this.value >>= 8;

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

@ -123,7 +123,7 @@ internal abstract class BitWriterBase
/// <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>
protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChunkType chunkType)
protected void WriteMetadataProfile(Stream stream, byte[]? metadataBytes, WebpChunkType chunkType)
{
DebugGuard.NotNull(metadataBytes, nameof(metadataBytes));
@ -207,7 +207,7 @@ internal abstract class BitWriterBase
/// <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, byte[]? iccProfileBytes, uint width, uint height, bool hasAlpha)
{
if (width > MaxDimension || height > MaxDimension)
{

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -58,7 +57,8 @@ internal class Vp8BitWriter : BitWriterBase
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary>
/// <param name="expectedSize">The expected size in bytes.</param>
public Vp8BitWriter(int expectedSize)
/// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: base(expectedSize)
{
this.range = 255 - 1;
@ -67,15 +67,9 @@ internal class Vp8BitWriter : BitWriterBase
this.nbBits = -8;
this.pos = 0;
this.maxPos = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="Vp8BitWriter"/> class.
/// </summary>
/// <param name="expectedSize">The expected size in bytes.</param>
/// <param name="enc">The Vp8Encoder.</param>
public Vp8BitWriter(int expectedSize, Vp8Encoder enc)
: this(expectedSize) => this.enc = enc;
this.enc = enc;
}
/// <inheritdoc/>
public override int NumBytes() => (int)this.pos;
@ -414,9 +408,9 @@ internal class Vp8BitWriter : BitWriterBase
/// <param name="alphaDataIsCompressed">Indicates, if the alpha data is compressed.</param>
public void WriteEncodedImageToStream(
Stream stream,
ExifProfile exifProfile,
XmpProfile xmpProfile,
IccProfile iccProfile,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
IccProfile? iccProfile,
uint width,
uint height,
bool hasAlpha,
@ -424,22 +418,22 @@ internal class Vp8BitWriter : BitWriterBase
bool alphaDataIsCompressed)
{
bool isVp8X = false;
byte[] exifBytes = null;
byte[] xmpBytes = null;
byte[] iccProfileBytes = null;
byte[]? exifBytes = null;
byte[]? xmpBytes = null;
byte[]? iccProfileBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes);
riffSize += MetadataChunkSize(exifBytes!);
}
if (xmpProfile != null)
{
isVp8X = true;
xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes);
riffSize += MetadataChunkSize(xmpBytes!);
}
if (iccProfile != null)
@ -465,7 +459,7 @@ internal class Vp8BitWriter : BitWriterBase
int mbSize = this.enc.Mbw * this.enc.Mbh;
int expectedSize = mbSize * 7 / 8;
var bitWriterPartZero = new Vp8BitWriter(expectedSize);
Vp8BitWriter bitWriterPartZero = new(expectedSize, this.enc);
// Partition #0 with header and partition sizes.
uint size0 = this.GeneratePartition0(bitWriterPartZero);
@ -676,9 +670,9 @@ internal class Vp8BitWriter : BitWriterBase
bool isVp8X,
uint width,
uint height,
ExifProfile exifProfile,
XmpProfile xmpProfile,
byte[] iccProfileBytes,
ExifProfile? exifProfile,
XmpProfile? xmpProfile,
byte[]? iccProfileBytes,
bool hasAlpha,
Span<byte> alphaData,
bool alphaDataIsCompressed)

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

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
@ -138,25 +137,25 @@ internal class Vp8LBitWriter : BitWriterBase
/// <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>
public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, IccProfile iccProfile, 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;
byte[]? exifBytes = null;
byte[]? xmpBytes = null;
byte[]? iccBytes = null;
uint riffSize = 0;
if (exifProfile != null)
{
isVp8X = true;
exifBytes = exifProfile.ToByteArray();
riffSize += MetadataChunkSize(exifBytes);
riffSize += MetadataChunkSize(exifBytes!);
}
if (xmpProfile != null)
{
isVp8X = true;
xmpBytes = xmpProfile.Data;
riffSize += MetadataChunkSize(xmpBytes);
riffSize += MetadataChunkSize(xmpBytes!);
}
if (iccProfile != null)

47
src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -50,7 +49,7 @@ internal static class BackwardReferenceEncoder
int lz77TypeBest = 0;
double bitCostBest = -1;
int cacheBitsInitial = cacheBits;
Vp8LHashChain hashChainBox = null;
Vp8LHashChain? hashChainBox = null;
var stats = new Vp8LStreaks();
var bitsEntropy = new Vp8LBitEntropy();
for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1)
@ -101,7 +100,7 @@ internal static class BackwardReferenceEncoder
// Improve on simple LZ77 but only for high quality (TraceBackwards is costly).
if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25)
{
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox;
Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox!;
BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst);
var histo = new Vp8LHistogram(worst, cacheBits);
double bitCostTrace = histo.EstimateBits(stats, bitsEntropy);
@ -140,8 +139,7 @@ internal static class BackwardReferenceEncoder
for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++)
{
histos[i] = new Vp8LHistogram(paletteCodeBits: i);
colorCache[i] = new ColorCache();
colorCache[i].Init(i);
colorCache[i] = new ColorCache(i);
}
// Find the cacheBits giving the lowest entropy.
@ -274,11 +272,11 @@ internal static class BackwardReferenceEncoder
double offsetCost = -1;
int firstOffsetIsConstant = -1; // initialized with 'impossible' value.
int reach = 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
costModel.Build(xSize, cacheBits, refs);
@ -375,12 +373,12 @@ internal static class BackwardReferenceEncoder
private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan<uint> bgra, int cacheBits, Span<ushort> chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs)
{
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
int i = 0;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
backwardRefs.Refs.Clear();
@ -396,7 +394,7 @@ internal static class BackwardReferenceEncoder
{
for (int k = 0; k < len; k++)
{
colorCache.Insert(bgra[i + k]);
colorCache!.Insert(bgra[i + k]);
}
}
@ -405,7 +403,7 @@ internal static class BackwardReferenceEncoder
else
{
PixOrCopy v;
int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1;
int idx = useColorCache ? colorCache!.Contains(bgra[i]) : -1;
if (idx >= 0)
{
// useColorCache is true and color cache contains bgra[i]
@ -416,7 +414,7 @@ internal static class BackwardReferenceEncoder
{
if (useColorCache)
{
colorCache.Insert(bgra[i]);
colorCache!.Insert(bgra[i]);
}
v = PixOrCopy.CreateLiteral(bgra[i]);
@ -430,7 +428,7 @@ internal static class BackwardReferenceEncoder
private static void AddSingleLiteralWithCostModel(
ReadOnlySpan<uint> bgra,
ColorCache colorCache,
ColorCache? colorCache,
CostModel costModel,
int idx,
bool useColorCache,
@ -440,7 +438,7 @@ internal static class BackwardReferenceEncoder
{
double costVal = prevCost;
uint color = bgra[idx];
int ix = useColorCache ? colorCache.Contains(color) : -1;
int ix = useColorCache ? colorCache!.Contains(color) : -1;
if (ix >= 0)
{
double mul0 = 0.68;
@ -451,7 +449,7 @@ internal static class BackwardReferenceEncoder
double mul1 = 0.82;
if (useColorCache)
{
colorCache.Insert(color);
colorCache!.Insert(color);
}
costVal += costModel.GetLiteralCost(color) * mul1;
@ -469,10 +467,10 @@ internal static class BackwardReferenceEncoder
int iLastCheck = -1;
bool useColorCache = cacheBits > 0;
int pixCount = xSize * ySize;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
refs.Refs.Clear();
@ -529,7 +527,7 @@ internal static class BackwardReferenceEncoder
{
for (j = i; j < i + len; j++)
{
colorCache.Insert(bgra[j]);
colorCache!.Insert(bgra[j]);
}
}
}
@ -725,11 +723,11 @@ internal static class BackwardReferenceEncoder
{
int pixelCount = xSize * ySize;
bool useColorCache = cacheBits > 0;
var colorCache = new ColorCache();
ColorCache? colorCache = null;
if (useColorCache)
{
colorCache.Init(cacheBits);
colorCache = new ColorCache(cacheBits);
}
refs.Refs.Clear();
@ -757,7 +755,7 @@ internal static class BackwardReferenceEncoder
{
for (int k = 0; k < prevRowLen; ++k)
{
colorCache.Insert(bgra[i + k]);
colorCache!.Insert(bgra[i + k]);
}
}
@ -777,8 +775,7 @@ internal static class BackwardReferenceEncoder
private static void BackwardRefsWithLocalCache(ReadOnlySpan<uint> bgra, int cacheBits, Vp8LBackwardRefs refs)
{
int pixelIndex = 0;
var colorCache = new ColorCache();
colorCache.Init(cacheBits);
ColorCache colorCache = new(cacheBits);
for (int idx = 0; idx < refs.Refs.Count; idx++)
{
PixOrCopy v = refs.Refs[idx];
@ -825,12 +822,12 @@ internal static class BackwardReferenceEncoder
}
}
private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs)
private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache? colorCache, Vp8LBackwardRefs refs)
{
PixOrCopy v;
if (useColorCache)
{
int key = colorCache.GetIndex(pixel);
int key = colorCache!.GetIndex(pixel);
if (colorCache.Lookup(key) == pixel)
{
v = PixOrCopy.CreateCacheIdx(key);

31
src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Runtime.CompilerServices;
@ -14,31 +13,31 @@ internal class ColorCache
private const uint HashMul = 0x1e35a7bdu;
/// <summary>
/// Gets the color entries.
/// Initializes a new instance of the <see cref="ColorCache"/> class.
/// </summary>
public uint[] Colors { get; private set; }
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
public ColorCache(int hashBits)
{
int hashSize = 1 << hashBits;
this.Colors = new uint[hashSize];
this.HashBits = hashBits;
this.HashShift = 32 - hashBits;
}
/// <summary>
/// Gets the hash shift: 32 - hashBits.
/// Gets the color entries.
/// </summary>
public int HashShift { get; private set; }
public uint[] Colors { get; }
/// <summary>
/// Gets the hash bits.
/// Gets the hash shift: 32 - hashBits.
/// </summary>
public int HashBits { get; private set; }
public int HashShift { get; }
/// <summary>
/// Initializes a new color cache.
/// Gets the hash bits.
/// </summary>
/// <param name="hashBits">The hashBits determine the size of cache. It will be 1 left shifted by hashBits.</param>
public void Init(int hashBits)
{
int hashSize = 1 << hashBits;
this.Colors = new uint[hashSize];
this.HashBits = hashBits;
this.HashShift = 32 - hashBits;
}
public int HashBits { get; }
/// <summary>
/// Inserts a new color into the cache.

5
src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Diagnostics;
@ -33,7 +32,7 @@ internal class CostInterval
public int Index { get; set; }
public CostInterval Previous { get; set; }
public CostInterval? Previous { get; set; }
public CostInterval Next { get; set; }
public CostInterval? Next { get; set; }
}

19
src/ImageSharp/Formats/Webp/Lossless/CostManager.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using SixLabors.ImageSharp.Memory;
@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless;
/// </summary>
internal sealed class CostManager : IDisposable
{
private CostInterval head;
private CostInterval? head;
private const int FreeIntervalsStartCount = 25;
@ -103,10 +102,10 @@ internal sealed class CostManager : IDisposable
/// <param name="doCleanIntervals">If 'doCleanIntervals' is true, intervals that end before 'i' will be popped.</param>
public void UpdateCostAtIndex(int i, bool doCleanIntervals)
{
CostInterval current = this.head;
CostInterval? current = this.head;
while (current != null && current.Start <= i)
{
CostInterval next = current.Next;
CostInterval? next = current.Next;
if (current.End <= i)
{
if (doCleanIntervals)
@ -155,7 +154,7 @@ internal sealed class CostManager : IDisposable
return;
}
CostInterval interval = this.head;
CostInterval? interval = this.head;
for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++)
{
// Define the intersection of the ith interval with the new one.
@ -163,7 +162,7 @@ internal sealed class CostManager : IDisposable
int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End);
float cost = (float)(distanceCost + this.CacheIntervals[i].Cost);
CostInterval intervalNext;
CostInterval? intervalNext;
for (; interval != null && interval.Start < end; interval = intervalNext)
{
intervalNext = interval.Next;
@ -225,7 +224,7 @@ internal sealed class CostManager : IDisposable
/// Pop an interval from the manager.
/// </summary>
/// <param name="interval">The interval to remove.</param>
private void PopInterval(CostInterval interval)
private void PopInterval(CostInterval? interval)
{
if (interval == null)
{
@ -240,7 +239,7 @@ internal sealed class CostManager : IDisposable
this.freeIntervals.Push(interval);
}
private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end)
private void InsertInterval(CostInterval? intervalIn, float cost, int position, int start, int end)
{
if (start >= end)
{
@ -271,7 +270,7 @@ internal sealed class CostManager : IDisposable
/// it was orphaned (which can be NULL), set it at the right place in the list
/// of intervals using the start_ ordering and the previous interval as a hint.
/// </summary>
private void PositionOrphanInterval(CostInterval current, CostInterval previous)
private void PositionOrphanInterval(CostInterval current, CostInterval? previous)
{
previous ??= this.head;
@ -292,7 +291,7 @@ internal sealed class CostManager : IDisposable
/// <summary>
/// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'.
/// </summary>
private void ConnectIntervals(CostInterval prev, CostInterval next)
private void ConnectIntervals(CostInterval? prev, CostInterval? next)
{
if (prev != null)
{

3
src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs

@ -158,10 +158,9 @@ internal sealed class WebpLosslessDecoder
// Finish setting up the color-cache.
if (isColorCachePresent)
{
decoder.Metadata.ColorCache = new ColorCache();
decoder.Metadata.ColorCache = new ColorCache(colorCacheBits);
colorCacheSize = 1 << colorCacheBits;
decoder.Metadata.ColorCacheSize = colorCacheSize;
decoder.Metadata.ColorCache.Init(colorCacheBits);
}
else
{

10
src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs

@ -348,12 +348,18 @@ internal class Vp8Encoder : IDisposable
// Extract and encode alpha channel data, if present.
int alphaDataSize = 0;
bool alphaCompressionSucceeded = false;
using AlphaEncoder alphaEncoder = new();
Span<byte> alphaData = Span<byte>.Empty;
if (hasAlpha)
{
// TODO: This can potentially run in an separate task.
IMemoryOwner<byte> encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.skipMetadata, this.alphaCompression, out alphaDataSize);
using IMemoryOwner<byte> encodedAlphaData = AlphaEncoder.EncodeAlpha(
image,
this.configuration,
this.memoryAllocator,
this.skipMetadata,
this.alphaCompression,
out alphaDataSize);
alphaData = encodedAlphaData.GetSpan();
if (alphaDataSize < pixelCount)
{

25
src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Runtime.CompilerServices;
@ -46,17 +45,17 @@ internal class WebpAnimationDecoder : IDisposable
/// <summary>
/// The abstract metadata.
/// </summary>
private ImageMetadata metadata;
private ImageMetadata? metadata;
/// <summary>
/// The gif specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
private WebpMetadata? webpMetadata;
/// <summary>
/// The alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// Initializes a new instance of the <see cref="WebpAnimationDecoder"/> class.
@ -83,8 +82,8 @@ internal class WebpAnimationDecoder : IDisposable
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
ImageFrame<TPixel> previousFrame = null;
Image<TPixel>? image = null;
ImageFrame<TPixel>? previousFrame = null;
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetWebpMetadata();
@ -99,12 +98,12 @@ internal class WebpAnimationDecoder : IDisposable
switch (chunkType)
{
case WebpChunkType.Animation:
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor.Value);
uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor!.Value);
remainingBytes -= (int)dataSize;
break;
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image.Metadata, false, this.buffer);
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, this.buffer);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data");
@ -117,7 +116,7 @@ internal class WebpAnimationDecoder : IDisposable
}
}
return image;
return image!;
}
/// <summary>
@ -130,7 +129,7 @@ internal class WebpAnimationDecoder : IDisposable
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="backgroundColor">The default background color of the canvas in.</param>
private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, uint width, uint height, Color backgroundColor)
private uint ReadFrame<TPixel>(BufferedReadStream stream, ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, uint width, uint height, Color backgroundColor)
where TPixel : unmanaged, IPixel<TPixel>
{
AnimationFrameData frameData = this.ReadFrameHeader(stream);
@ -146,7 +145,7 @@ internal class WebpAnimationDecoder : IDisposable
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
}
WebpImageInfo webpInfo = null;
WebpImageInfo? webpInfo = null;
WebpFeatures features = new();
switch (chunkType)
{
@ -163,7 +162,7 @@ internal class WebpAnimationDecoder : IDisposable
break;
}
ImageFrame<TPixel> currentFrame = null;
ImageFrame<TPixel>? currentFrame = null;
ImageFrame<TPixel> imageFrame;
if (previousFrame is null)
{
@ -175,7 +174,7 @@ internal class WebpAnimationDecoder : IDisposable
}
else
{
currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection.
currentFrame = image!.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection.
SetFrameMetadata(currentFrame.Metadata, frameData.Duration);

1
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Webp.BitReader;

220
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using System.Buffers;
using System.Buffers.Binary;
@ -9,9 +8,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.IO;
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;
namespace SixLabors.ImageSharp.Formats.Webp;
@ -41,35 +38,20 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// </summary>
private readonly uint maxFrames;
/// <summary>
/// Gets the <see cref="ImageMetadata"/> decoded by this decoder instance.
/// </summary>
private ImageMetadata metadata;
/// <summary>
/// Gets or sets the alpha data, if an ALPH chunk is present.
/// </summary>
private IMemoryOwner<byte> alphaData;
private IMemoryOwner<byte>? alphaData;
/// <summary>
/// Used for allocating memory during the decoding operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// The stream to decode from.
/// </summary>
private BufferedReadStream currentStream;
/// <summary>
/// The webp specific metadata.
/// </summary>
private WebpMetadata webpMetadata;
/// <summary>
/// Information about the webp image.
/// </summary>
private WebpImageInfo webImageInfo;
private WebpImageInfo? webImageInfo;
/// <summary>
/// Initializes a new instance of the <see cref="WebpDecoderCore"/> class.
@ -88,25 +70,24 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
public DecoderOptions Options { get; }
/// <inheritdoc/>
public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height);
public Size Dimensions => new((int)this.webImageInfo!.Width, (int)this.webImageInfo.Height);
/// <inheritdoc />
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
Image<TPixel>? image = null;
try
{
this.metadata = new ImageMetadata();
this.currentStream = stream;
ImageMetadata metadata = new();
uint fileSize = this.ReadImageHeader();
uint fileSize = this.ReadImageHeader(stream);
using (this.webImageInfo = this.ReadVp8Info())
using (this.webImageInfo = this.ReadVp8Info(stream, metadata))
{
if (this.webImageInfo.Features is { Animation: true })
{
using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames);
using WebpAnimationDecoder animationDecoder = new(this.memoryAllocator, this.configuration, this.maxFrames);
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
@ -115,23 +96,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata);
image = new Image<TPixel>(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)
{
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
WebpLosslessDecoder losslessDecoder = new(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
{
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
WebpLossyDecoder lossyDecoder = new(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData);
}
// There can be optional chunks after the image data, like EXIF and XMP.
if (this.webImageInfo.Features != null)
{
this.ParseOptionalChunks(this.webImageInfo.Features);
this.ParseOptionalChunks(stream, metadata, this.webImageInfo.Features);
}
return image;
@ -147,31 +128,32 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.currentStream = stream;
this.ReadImageHeader(stream);
this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(true))
ImageMetadata metadata = new();
using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true))
{
return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata);
return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, metadata);
}
}
/// <summary>
/// Reads and skips over the image header.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <returns>The file size in bytes.</returns>
private uint ReadImageHeader()
private uint ReadImageHeader(BufferedReadStream stream)
{
// Skip FourCC header, we already know its a RIFF file at this point.
this.currentStream.Skip(4);
stream.Skip(4);
// 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 fileSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
uint fileSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
// Skip 'WEBP' from the header.
this.currentStream.Skip(4);
stream.Skip(4);
return fileSize;
}
@ -179,42 +161,43 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads information present in the image header, about the image content and how to decode the image.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>Information about the webp image.</returns>
private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false)
private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metadata, bool ignoreAlpha = false)
{
this.metadata = new ImageMetadata();
this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance);
WebpMetadata webpMetadata = metadata.GetFormatMetadata(WebpFormat.Instance);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
var features = new WebpFeatures();
WebpFeatures features = new();
switch (chunkType)
{
case WebpChunkType.Vp8:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
case WebpChunkType.Vp8L:
this.webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
case WebpChunkType.Vp8X:
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(this.currentStream, this.buffer, features);
while (this.currentStream.Position < this.currentStream.Length)
WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(stream, this.buffer, features);
while (stream.Position < stream.Length)
{
chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer);
if (chunkType == WebpChunkType.Vp8)
{
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossy;
webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features);
}
else if (chunkType == WebpChunkType.Vp8L)
{
this.webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features);
webpMetadata.FileFormat = WebpFileFormatType.Lossless;
webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features);
}
else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType))
{
bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha);
bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha);
if (isAnimationChunk)
{
return webpInfos;
@ -223,8 +206,8 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
else
{
// Ignore unknown chunks.
uint chunkSize = this.ReadChunkSize();
this.currentStream.Skip((int)chunkSize);
uint chunkSize = this.ReadChunkSize(stream);
stream.Skip((int)chunkSize);
}
}
@ -239,32 +222,39 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>true, if its a alpha chunk.</returns>
private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha)
private bool ParseOptionalExtendedChunks(
BufferedReadStream stream,
ImageMetadata metadata,
WebpChunkType chunkType,
WebpFeatures features,
bool ignoreAlpha)
{
switch (chunkType)
{
case WebpChunkType.Iccp:
this.ReadIccProfile();
this.ReadIccProfile(stream, metadata);
break;
case WebpChunkType.Exif:
this.ReadExifProfile();
this.ReadExifProfile(stream, metadata);
break;
case WebpChunkType.Xmp:
this.ReadXmpProfile();
this.ReadXmpProfile(stream, metadata);
break;
case WebpChunkType.AnimationParameter:
this.ReadAnimationParameters(features);
this.ReadAnimationParameters(stream, features);
return true;
case WebpChunkType.Alpha:
this.ReadAlphaData(features, ignoreAlpha);
this.ReadAlphaData(stream, features, ignoreAlpha);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -277,32 +267,34 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features)
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features)
{
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
{
return;
}
long streamLength = this.currentStream.Length;
while (this.currentStream.Position < streamLength)
long streamLength = stream.Length;
while (stream.Position < streamLength)
{
// Read chunk header.
WebpChunkType chunkType = this.ReadChunkType();
if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null)
WebpChunkType chunkType = this.ReadChunkType(stream);
if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null)
{
this.ReadExifProfile();
this.ReadExifProfile(stream, metadata);
}
else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null)
else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null)
{
this.ReadXmpProfile();
this.ReadXmpProfile(stream, metadata);
}
else
{
// Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize();
this.currentStream.Skip((int)chunkLength);
uint chunkLength = this.ReadChunkSize(stream);
stream.Skip((int)chunkLength);
}
}
}
@ -310,76 +302,80 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the EXIF profile from the stream.
/// </summary>
private void ReadExifProfile()
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata)
{
uint exifChunkSize = this.ReadChunkSize();
uint exifChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
stream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize);
int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new ExifProfile(exifData);
this.metadata.ExifProfile = profile;
metadata.ExifProfile = new(exifData);
}
}
/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
private void ReadXmpProfile()
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata)
{
uint xmpChunkSize = this.ReadChunkSize();
uint xmpChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
stream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new XmpProfile(xmpData);
this.metadata.XmpProfile = profile;
metadata.XmpProfile = new(xmpData);
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </summary>
private void ReadIccProfile()
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata)
{
uint iccpChunkSize = this.ReadChunkSize();
uint iccpChunkSize = this.ReadChunkSize(stream);
if (this.skipMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
stream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
var profile = new IccProfile(iccpData);
IccProfile profile = new(iccpData);
if (profile.CheckIsValid())
{
this.metadata.IccProfile = profile;
metadata.IccProfile = profile;
}
}
}
@ -387,17 +383,18 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the animation parameters chunk from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The webp features.</param>
private void ReadAnimationParameters(WebpFeatures features)
private void ReadAnimationParameters(BufferedReadStream stream, WebpFeatures features)
{
features.Animation = true;
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
byte blue = (byte)this.currentStream.ReadByte();
byte green = (byte)this.currentStream.ReadByte();
byte red = (byte)this.currentStream.ReadByte();
byte alpha = (byte)this.currentStream.ReadByte();
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
byte blue = (byte)stream.ReadByte();
byte green = (byte)stream.ReadByte();
byte red = (byte)stream.ReadByte();
byte alpha = (byte)stream.ReadByte();
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha));
int bytesRead = this.currentStream.Read(this.buffer, 0, 2);
int bytesRead = stream.Read(this.buffer, 0, 2);
if (bytesRead != 2)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count");
@ -409,22 +406,23 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Reads the alpha data chunk data from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="features">The features.</param>
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha)
private void ReadAlphaData(BufferedReadStream stream, WebpFeatures features, bool ignoreAlpha)
{
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer);
if (ignoreAlpha)
{
this.currentStream.Skip((int)alphaChunkSize);
stream.Skip((int)alphaChunkSize);
return;
}
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
features.AlphaChunkHeader = (byte)stream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1);
this.alphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.alphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize);
int bytesRead = stream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream");
@ -434,15 +432,15 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private WebpChunkType ReadChunkType()
private WebpChunkType ReadChunkType(BufferedReadStream stream)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (stream.Read(this.buffer, 0, 4) == 4)
{
var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return chunkType;
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
}
throw new ImageFormatException("Invalid Webp data.");
@ -452,10 +450,12 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
/// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
/// so the chunk size will be increased by 1 in those cases.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <returns>The chunk size in bytes.</returns>
private uint ReadChunkSize()
/// <exception cref="ImageFormatException">Invalid data.</exception>
private uint ReadChunkSize(BufferedReadStream stream)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
if (stream.Read(this.buffer, 0, 4) == 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;

2
src/ImageSharp/Formats/Webp/WebpEncoder.cs

@ -82,7 +82,7 @@ public sealed class WebpEncoder : ImageEncoder
/// <inheritdoc/>
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
{
WebpEncoderCore encoder = new(this, image.GetMemoryAllocator());
WebpEncoderCore encoder = new(this, image.GetConfiguration());
encoder.Encode(image, stream, cancellationToken);
}
}

12
src/ImageSharp/Formats/Webp/WebpEncoderCore.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
using SixLabors.ImageSharp.Memory;
@ -81,16 +79,17 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
/// <summary>
/// The global configuration.
/// </summary>
private Configuration configuration;
private readonly Configuration configuration;
/// <summary>
/// Initializes a new instance of the <see cref="WebpEncoderCore"/> class.
/// </summary>
/// <param name="encoder">The encoder with options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
public WebpEncoderCore(WebpEncoder encoder, MemoryAllocator memoryAllocator)
/// <param name="configuration">The global configuration.</param>
public WebpEncoderCore(WebpEncoder encoder, Configuration configuration)
{
this.memoryAllocator = memoryAllocator;
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.alphaCompression = encoder.UseAlphaCompression;
this.fileFormat = encoder.FileFormat;
this.quality = encoder.Quality;
@ -117,7 +116,6 @@ internal sealed class WebpEncoderCore : IImageEncoderInternals
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
this.configuration = image.GetConfiguration();
bool lossless;
if (this.fileFormat is not null)
{

9
src/ImageSharp/Formats/Webp/WebpImageInfo.cs

@ -1,6 +1,5 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
using SixLabors.ImageSharp.Formats.Webp.BitReader;
using SixLabors.ImageSharp.Formats.Webp.Lossy;
@ -36,7 +35,7 @@ internal class WebpImageInfo : IDisposable
/// <summary>
/// Gets or sets additional features present in a VP8X image.
/// </summary>
public WebpFeatures Features { get; set; }
public WebpFeatures? Features { get; set; }
/// <summary>
/// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1.
@ -46,17 +45,17 @@ internal class WebpImageInfo : IDisposable
/// <summary>
/// Gets or sets the VP8 frame header.
/// </summary>
public Vp8FrameHeader Vp8FrameHeader { get; set; }
public Vp8FrameHeader? Vp8FrameHeader { get; set; }
/// <summary>
/// Gets or sets the VP8L bitreader. Will be <see langword="null"/>, if its not a lossless image.
/// </summary>
public Vp8LBitReader Vp8LBitReader { get; set; }
public Vp8LBitReader? Vp8LBitReader { get; set; }
/// <summary>
/// Gets or sets the VP8 bitreader. Will be <see langword="null"/>, if its not a lossy image.
/// </summary>
public Vp8BitReader Vp8BitReader { get; set; }
public Vp8BitReader? Vp8BitReader { get; set; }
/// <inheritdoc/>
public void Dispose()

6
src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

@ -1,15 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Webp;
internal static class WebpThrowHelper
{
[DoesNotReturn]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[DoesNotReturn]
public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
[DoesNotReturn]
public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage);
[DoesNotReturn]
public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage);
}

Loading…
Cancel
Save