Browse Source

Merge branch 'main' into sn/arm_tests

pull/3018/head
James Jackson-South 3 months ago
committed by GitHub
parent
commit
468cea246c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 21
      src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs
  2. 236
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  3. 187
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  4. 28
      src/ImageSharp/Image.FromBytes.cs
  5. 5
      src/ImageSharp/Image.LoadPixelData.cs
  6. 4
      tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs
  7. 4
      tests/ImageSharp.Tests/Image/ImageTests.Identify.cs
  8. 11
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs
  9. 4
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs
  10. 13
      tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs

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

@ -112,12 +112,12 @@ internal class WebpAnimationDecoder : IDisposable
this.webpMetadata = this.metadata.GetWebpMetadata();
this.webpMetadata.RepeatCount = features.AnimationLoopCount;
Color backgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
this.webpMetadata.BackgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore
? Color.Transparent
: features.AnimationBackgroundColor!.Value;
this.webpMetadata.BackgroundColor = backgroundColor;
bool ignoreMetadata = this.skipMetadata;
SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling;
Span<byte> buffer = stackalloc byte[4];
uint frameCount = 0;
int remainingBytes = (int)completeDataSize;
@ -135,9 +135,16 @@ internal class WebpAnimationDecoder : IDisposable
remainingBytes -= (int)dataSize;
break;
case WebpChunkType.Iccp:
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, this.metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer);
WebpChunkParsingUtils.ParseOptionalChunks(
stream,
chunkType,
this.metadata,
ignoreMetadata,
segmentIntegrityHandling,
buffer);
break;
default:
@ -187,9 +194,12 @@ internal class WebpAnimationDecoder : IDisposable
this.webpMetadata.BackgroundColor = backgroundColor;
TPixel backgroundPixel = backgroundColor.ToPixel<TPixel>();
bool ignoreMetadata = this.skipMetadata;
SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling;
Span<byte> buffer = stackalloc byte[4];
uint frameCount = 0;
int remainingBytes = (int)completeDataSize;
while (remainingBytes > 0)
{
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
@ -209,9 +219,10 @@ internal class WebpAnimationDecoder : IDisposable
remainingBytes -= (int)dataSize;
break;
case WebpChunkType.Iccp:
case WebpChunkType.Xmp:
case WebpChunkType.Exif:
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, this.skipMetadata, this.segmentIntegrityHandling, buffer);
WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, ignoreMetadata, segmentIntegrityHandling, buffer);
break;
default:

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

@ -9,6 +9,7 @@ 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;
namespace SixLabors.ImageSharp.Formats.Webp;
@ -258,6 +259,9 @@ internal static class WebpChunkParsingUtils
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the read data into.</param>
/// <returns>A unsigned 24 bit integer.</returns>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
public static uint ReadUInt24LittleEndian(Stream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 3) == 3)
@ -272,8 +276,11 @@ internal static class WebpChunkParsingUtils
/// <summary>
/// Writes a unsigned 24 bit integer.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="data">The uint24 data to write.</param>
/// <exception cref="InvalidDataException">
/// Thrown if the data is not a valid unsigned 24 bit integer.
/// </exception>
public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data)
{
if (data >= 1 << 24)
@ -296,18 +303,24 @@ internal static class WebpChunkParsingUtils
/// </summary>
/// <param name="stream">The stream to read the data from.</param>
/// <param name="buffer">Buffer to store the data read from the stream.</param>
/// <param name="required">If true, the chunk size is required to be read, otherwise it can be skipped.</param>
/// <returns>The chunk size in bytes.</returns>
public static uint ReadChunkSize(Stream stream, Span<byte> buffer)
/// <exception cref="ImageFormatException">Thrown if the input stream is not valid.</exception>
public static uint ReadChunkSize(Stream stream, Span<byte> buffer, bool required = true)
{
DebugGuard.IsTrue(buffer.Length is 4, "buffer has wrong length");
if (stream.Read(buffer) is 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return chunkSize % 2 is 0 ? chunkSize : chunkSize + 1;
}
throw new ImageFormatException("Invalid Webp data, could not read chunk size.");
if (required)
{
throw new ImageFormatException("Invalid Webp data, could not read chunk size.");
}
// Return the size of the remaining data in the stream.
return (uint)(stream.Length - stream.Position);
}
/// <summary>
@ -320,14 +333,13 @@ internal static class WebpChunkParsingUtils
/// </exception>
public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer)
{
DebugGuard.IsTrue(buffer.Length == 4, "buffer has wrong length");
if (stream.Read(buffer) == 4)
{
WebpChunkType chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
return chunkType;
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
// While we ignore unknown chunks we still need a to be a ble to read a chunk type
// known or otherwise from the stream.
throw new ImageFormatException("Invalid Webp data, could not read chunk type.");
}
@ -336,82 +348,182 @@ internal static class WebpChunkParsingUtils
/// If there are more such chunks, readers MAY ignore all except the first one.
/// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
/// </summary>
/// <param name="stream">The stream to read the data from.</param>
/// <param name="chunkType">The chunk type to parse.</param>
/// <param name="metadata">The image metadata to write to.</param>
/// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
/// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
/// <param name="buffer">Buffer to store the data read from the stream.</param>
public static void ParseOptionalChunks(
BufferedReadStream stream,
WebpChunkType chunkType,
ImageMetadata metadata,
bool ignoreMetaData,
bool ignoreMetadata,
SegmentIntegrityHandling segmentIntegrityHandling,
Span<byte> buffer)
{
long streamLength = stream.Length;
while (stream.Position < streamLength)
{
uint chunkLength = ReadChunkSize(stream, buffer);
if (ignoreMetaData)
{
stream.Skip((int)chunkLength);
}
int bytesRead;
switch (chunkType)
{
case WebpChunkType.Exif:
byte[] exifData = new byte[chunkLength];
bytesRead = stream.Read(exifData, 0, (int)chunkLength);
if (bytesRead != chunkLength)
{
if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
}
return;
}
if (metadata.ExifProfile == null)
{
ExifProfile exifProfile = new(exifData);
// Set the resolution from the metadata.
double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution);
double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution);
if (horizontalValue > 0 && verticalValue > 0)
{
metadata.HorizontalResolution = horizontalValue;
metadata.VerticalResolution = verticalValue;
metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile);
}
metadata.ExifProfile = exifProfile;
}
case WebpChunkType.Iccp:
ReadIccProfile(stream, metadata, ignoreMetadata, segmentIntegrityHandling, buffer);
break;
case WebpChunkType.Exif:
ReadExifProfile(stream, metadata, ignoreMetadata, segmentIntegrityHandling, buffer);
break;
case WebpChunkType.Xmp:
byte[] xmpData = new byte[chunkLength];
bytesRead = stream.Read(xmpData, 0, (int)chunkLength);
if (bytesRead != chunkLength)
{
if (segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
}
return;
}
metadata.XmpProfile ??= new XmpProfile(xmpData);
ReadXmpProfile(stream, metadata, ignoreMetadata, segmentIntegrityHandling, buffer);
break;
default:
// Ignore unknown chunks.
// These must always fall after the image data so we are safe to always skip them.
uint chunkLength = ReadChunkSize(stream, buffer, false);
stream.Skip((int)chunkLength);
break;
}
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
/// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
/// <param name="buffer">Temporary buffer.</param>
public static void ReadIccProfile(
BufferedReadStream stream,
ImageMetadata metadata,
bool ignoreMetadata,
SegmentIntegrityHandling segmentIntegrityHandling,
Span<byte> buffer)
{
// While ICC profiles are optional, an invalid ICC profile cannot be ignored as it must precede the image data
// and since we canot determine its size to allow skipping without reading the chunk size, we have to throw if it's invalid.
// Hence we do not consider segment integrity handling here.
uint iccpChunkSize = ReadChunkSize(stream, buffer);
if (ignoreMetadata || metadata.IccProfile != null)
{
stream.Skip((int)iccpChunkSize);
}
else
{
bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone;
byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize);
// We have the size but the profile is invalid if we cannot read enough data.
// Use the segment integrity handling to determine if we throw.
if (bytesRead != iccpChunkSize && ignoreNone)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
IccProfile profile = new(iccpData);
if (profile.CheckIsValid())
{
metadata.IccProfile = profile;
}
}
}
/// <summary>
/// Reads the EXIF profile from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
/// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
/// <param name="buffer">Temporary buffer.</param>
public static void ReadExifProfile(
BufferedReadStream stream,
ImageMetadata metadata,
bool ignoreMetadata,
SegmentIntegrityHandling segmentIntegrityHandling,
Span<byte> buffer)
{
bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone;
uint exifChunkSize = ReadChunkSize(stream, buffer, ignoreNone);
if (ignoreMetadata || metadata.ExifProfile != null)
{
stream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
if (ignoreNone)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
}
return;
}
ExifProfile exifProfile = new(exifData);
// Set the resolution from the metadata.
double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution);
double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution);
if (horizontalValue > 0 && verticalValue > 0)
{
metadata.HorizontalResolution = horizontalValue;
metadata.VerticalResolution = verticalValue;
metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile);
}
metadata.ExifProfile = exifProfile;
}
}
/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="ignoreMetadata">If true, metadata will be ignored.</param>
/// <param name="segmentIntegrityHandling">Indicates how to handle segment integrity issues.</param>
/// <param name="buffer">Temporary buffer.</param>
public static void ReadXmpProfile(
BufferedReadStream stream,
ImageMetadata metadata,
bool ignoreMetadata,
SegmentIntegrityHandling segmentIntegrityHandling,
Span<byte> buffer)
{
bool ignoreNone = segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone;
uint xmpChunkSize = ReadChunkSize(stream, buffer, ignoreNone);
if (ignoreMetadata || metadata.XmpProfile != null)
{
stream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
if (ignoreNone)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
}
return;
}
metadata.XmpProfile = new XmpProfile(xmpData);
}
}
private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag<Rational> tag)
{
if (exifProfile.TryGetValue(tag, out IExifValue<Rational>? resolution))

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

@ -3,15 +3,11 @@
using System.Buffers;
using System.Buffers.Binary;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Webp.Lossless;
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;
@ -248,7 +244,8 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
else
{
// Ignore unknown chunks.
uint chunkSize = ReadChunkSize(stream, buffer, false);
// These must always fall after the image data so we are safe to always skip them.
uint chunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, buffer, false);
stream.Skip((int)chunkSize);
}
}
@ -279,18 +276,20 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
bool ignoreAlpha,
Span<byte> buffer)
{
bool ignoreMetadata = this.skipMetadata;
SegmentIntegrityHandling integrityHandling = this.segmentIntegrityHandling;
switch (chunkType)
{
case WebpChunkType.Iccp:
this.ReadIccProfile(stream, metadata, buffer);
WebpChunkParsingUtils.ReadIccProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer);
break;
case WebpChunkType.Exif:
this.ReadExifProfile(stream, metadata, buffer);
WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer);
break;
case WebpChunkType.Xmp:
this.ReadXmpProfile(stream, metadata, buffer);
WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer);
break;
case WebpChunkType.AnimationParameter:
@ -319,7 +318,10 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
/// <param name="buffer">Temporary buffer.</param>
private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span<byte> buffer)
{
if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData))
bool ignoreMetadata = this.skipMetadata;
SegmentIntegrityHandling integrityHandling = this.segmentIntegrityHandling;
if (ignoreMetadata || (!features.ExifProfile && !features.XmpMetaData))
{
return;
}
@ -328,139 +330,24 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
while (stream.Position < streamLength)
{
// Read chunk header.
WebpChunkType chunkType = ReadChunkType(stream, buffer);
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer);
if (chunkType == WebpChunkType.Exif && metadata.ExifProfile == null)
{
this.ReadExifProfile(stream, metadata, buffer);
WebpChunkParsingUtils.ReadExifProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer);
}
else if (chunkType == WebpChunkType.Xmp && metadata.XmpProfile == null)
{
this.ReadXmpProfile(stream, metadata, buffer);
WebpChunkParsingUtils.ReadXmpProfile(stream, metadata, ignoreMetadata, integrityHandling, buffer);
}
else
{
// Skip duplicate XMP or EXIF chunk.
uint chunkLength = ReadChunkSize(stream, buffer);
uint chunkLength = WebpChunkParsingUtils.ReadChunkSize(stream, buffer, false);
stream.Skip((int)chunkLength);
}
}
}
/// <summary>
/// Reads the EXIF profile from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="buffer">Temporary buffer.</param>
private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
{
uint exifChunkSize = ReadChunkSize(stream, buffer);
if (this.skipMetadata)
{
stream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
int bytesRead = stream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
}
return;
}
ExifProfile exifProfile = new(exifData);
// Set the resolution from the metadata.
double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution);
double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution);
if (horizontalValue > 0 && verticalValue > 0)
{
metadata.HorizontalResolution = horizontalValue;
metadata.VerticalResolution = verticalValue;
metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile);
}
metadata.ExifProfile = exifProfile;
}
}
private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag<Rational> tag)
{
if (exifProfile.TryGetValue(tag, out IExifValue<Rational>? resolution))
{
return resolution.Value.ToDouble();
}
return 0;
}
/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="buffer">Temporary buffer.</param>
private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
{
uint xmpChunkSize = ReadChunkSize(stream, buffer);
if (this.skipMetadata)
{
stream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = stream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
if (this.segmentIntegrityHandling == SegmentIntegrityHandling.IgnoreNone)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
}
return;
}
metadata.XmpProfile = new XmpProfile(xmpData);
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="buffer">Temporary buffer.</param>
private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span<byte> buffer)
{
uint iccpChunkSize = ReadChunkSize(stream, buffer);
if (this.skipMetadata)
{
stream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = stream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
IccProfile profile = new(iccpData);
if (profile.CheckIsValid())
{
metadata.IccProfile = profile;
}
}
}
/// <summary>
/// Reads the animation parameters chunk from the stream.
/// </summary>
@ -512,50 +399,6 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable
}
}
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <param name="stream">The stream to decode from.</param>
/// <param name="buffer">Temporary buffer.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span<byte> buffer)
{
if (stream.Read(buffer, 0, 4) == 4)
{
return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
throw new ImageFormatException("Invalid Webp data.");
}
/// <summary>
/// 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>
/// <param name="buffer">Temporary buffer.</param>
/// <param name="required">If true, the chunk size is required to be read, otherwise it can be skipped.</param>
/// <returns>The chunk size in bytes.</returns>
/// <exception cref="ImageFormatException">Invalid data.</exception>
private static uint ReadChunkSize(BufferedReadStream stream, Span<byte> buffer, bool required = true)
{
if (stream.Read(buffer, 0, 4) == 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
}
if (required)
{
throw new ImageFormatException("Invalid Webp data.");
}
// Return the size of the remaining data in the stream.
return (uint)(stream.Length - stream.Position);
}
/// <inheritdoc/>
public void Dispose() => this.alphaData?.Dispose();
}

28
src/ImageSharp/Image.FromBytes.cs

@ -34,7 +34,12 @@ public abstract partial class Image
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static unsafe IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan<byte> buffer)
{
Guard.NotNull(options, nameof(options.Configuration));
Guard.NotNull(options, nameof(options));
if (buffer.IsEmpty)
{
throw new UnknownImageFormatException("Cannot detect image format from empty data.");
}
fixed (byte* ptr = buffer)
{
@ -66,6 +71,13 @@ public abstract partial class Image
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static unsafe ImageInfo Identify(DecoderOptions options, ReadOnlySpan<byte> buffer)
{
Guard.NotNull(options, nameof(options));
if (buffer.IsEmpty)
{
throw new UnknownImageFormatException("Cannot identify image format from empty data.");
}
fixed (byte* ptr = buffer)
{
using UnmanagedMemoryStream stream = new(ptr, buffer.Length);
@ -99,6 +111,13 @@ public abstract partial class Image
/// <exception cref="UnknownImageFormatException">The encoded image format is unknown.</exception>
public static unsafe Image Load(DecoderOptions options, ReadOnlySpan<byte> buffer)
{
Guard.NotNull(options, nameof(options));
if (buffer.IsEmpty)
{
throw new UnknownImageFormatException("Cannot load image from empty data.");
}
fixed (byte* ptr = buffer)
{
using UnmanagedMemoryStream stream = new(ptr, buffer.Length);
@ -133,6 +152,13 @@ public abstract partial class Image
public static unsafe Image<TPixel> Load<TPixel>(DecoderOptions options, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(options, nameof(options));
if (data.IsEmpty)
{
throw new UnknownImageFormatException("Cannot load image from empty data.");
}
fixed (byte* ptr = data)
{
using UnmanagedMemoryStream stream = new(ptr, data.Length);

5
src/ImageSharp/Image.LoadPixelData.cs

@ -69,6 +69,11 @@ public abstract partial class Image
{
Guard.NotNull(configuration, nameof(configuration));
if (data.IsEmpty)
{
throw new ArgumentException("Pixel data cannot be empty.", nameof(data));
}
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));

4
tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs

@ -50,6 +50,10 @@ public partial class ImageTests
Assert.Equal(this.LocalImageFormat, format);
}
[Fact]
public void FromBytes_EmptySpan_Throws()
=> Assert.Throws<UnknownImageFormatException>(() => Image.DetectFormat([]));
[Fact]
public void FromFileSystemPath_GlobalConfiguration()
{

4
tests/ImageSharp.Tests/Image/ImageTests.Identify.cs

@ -38,6 +38,10 @@ public partial class ImageTests
Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat);
}
[Fact]
public void FromBytes_EmptySpan_Throws()
=> Assert.Throws<UnknownImageFormatException>(() => Image.Identify([]));
[Fact]
public void FromBytes_CustomConfiguration()
{

11
tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs

@ -79,5 +79,16 @@ public partial class ImageTests
this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration);
}
[Fact]
public void FromBytes_EmptySpan_Throws()
{
DecoderOptions options = new()
{
Configuration = this.TopLevelConfiguration
};
Assert.Throws<UnknownImageFormatException>(() => Image.Load(options, []));
}
}
}

4
tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs

@ -45,5 +45,9 @@ public partial class ImageTests
VerifyDecodedImage(img);
Assert.IsType<BmpFormat>(img.Metadata.DecodedImageFormat);
}
[Fact]
public void FromBytes_EmptySpan_Throws()
=> Assert.ThrowsAny<UnknownImageFormatException>(() => Image.Load<Rgba32>([]));
}
}

13
tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs

@ -32,6 +32,17 @@ public partial class ImageTests
}
});
public void Dispose() => this.Stream?.Dispose();
[Fact]
public void FromStream_Empty_Throws()
{
using MemoryStream ms = new();
Assert.Throws<UnknownImageFormatException>(() => Image.Load(DecoderOptions.Default, ms));
}
public void Dispose()
{
this.Stream?.Dispose();
GC.SuppressFinalize(this);
}
}
}

Loading…
Cancel
Save