diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 86489cd36..a23705413 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/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 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(); + bool ignoreMetadata = this.skipMetadata; + SegmentIntegrityHandling segmentIntegrityHandling = this.segmentIntegrityHandling; Span 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: diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index dc95ca044..26ae28fd4 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/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 /// The stream to read from. /// The buffer to store the read data into. /// A unsigned 24 bit integer. + /// + /// Thrown if the input stream is not valid. + /// public static uint ReadUInt24LittleEndian(Stream stream, Span buffer) { if (stream.Read(buffer, 0, 3) == 3) @@ -272,8 +276,11 @@ internal static class WebpChunkParsingUtils /// /// Writes a unsigned 24 bit integer. /// - /// The stream to read from. + /// The stream to write to. /// The uint24 data to write. + /// + /// Thrown if the data is not a valid unsigned 24 bit integer. + /// public static unsafe void WriteUInt24LittleEndian(Stream stream, uint data) { if (data >= 1 << 24) @@ -296,18 +303,24 @@ internal static class WebpChunkParsingUtils /// /// The stream to read the data from. /// Buffer to store the data read from the stream. + /// If true, the chunk size is required to be read, otherwise it can be skipped. /// The chunk size in bytes. - public static uint ReadChunkSize(Stream stream, Span buffer) + /// Thrown if the input stream is not valid. + public static uint ReadChunkSize(Stream stream, Span 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); } /// @@ -320,14 +333,13 @@ internal static class WebpChunkParsingUtils /// public static WebpChunkType ReadChunkType(BufferedReadStream stream, Span 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. /// + /// The stream to read the data from. + /// The chunk type to parse. + /// The image metadata to write to. + /// If true, metadata will be ignored. + /// Indicates how to handle segment integrity issues. + /// Buffer to store the data read from the stream. public static void ParseOptionalChunks( BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, - bool ignoreMetaData, + bool ignoreMetadata, SegmentIntegrityHandling segmentIntegrityHandling, Span 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; } } } + /// + /// Reads the ICCP chunk from the stream. + /// + /// The stream to decode from. + /// The image metadata. + /// If true, metadata will be ignored. + /// Indicates how to handle segment integrity issues. + /// Temporary buffer. + public static void ReadIccProfile( + BufferedReadStream stream, + ImageMetadata metadata, + bool ignoreMetadata, + SegmentIntegrityHandling segmentIntegrityHandling, + Span 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; + } + } + } + + /// + /// Reads the EXIF profile from the stream. + /// + /// The stream to decode from. + /// The image metadata. + /// If true, metadata will be ignored. + /// Indicates how to handle segment integrity issues. + /// Temporary buffer. + public static void ReadExifProfile( + BufferedReadStream stream, + ImageMetadata metadata, + bool ignoreMetadata, + SegmentIntegrityHandling segmentIntegrityHandling, + Span 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; + } + } + + /// + /// Reads the XMP profile the stream. + /// + /// The stream to decode from. + /// The image metadata. + /// If true, metadata will be ignored. + /// Indicates how to handle segment integrity issues. + /// Temporary buffer. + public static void ReadXmpProfile( + BufferedReadStream stream, + ImageMetadata metadata, + bool ignoreMetadata, + SegmentIntegrityHandling segmentIntegrityHandling, + Span 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 tag) { if (exifProfile.TryGetValue(tag, out IExifValue? resolution)) diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 0e9888adb..fd31a7fad 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/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 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 /// Temporary buffer. private void ParseOptionalChunks(BufferedReadStream stream, ImageMetadata metadata, WebpFeatures features, Span 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); } } } - /// - /// Reads the EXIF profile from the stream. - /// - /// The stream to decode from. - /// The image metadata. - /// Temporary buffer. - private void ReadExifProfile(BufferedReadStream stream, ImageMetadata metadata, Span 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 tag) - { - if (exifProfile.TryGetValue(tag, out IExifValue? resolution)) - { - return resolution.Value.ToDouble(); - } - - return 0; - } - - /// - /// Reads the XMP profile the stream. - /// - /// The stream to decode from. - /// The image metadata. - /// Temporary buffer. - private void ReadXmpProfile(BufferedReadStream stream, ImageMetadata metadata, Span 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); - } - } - - /// - /// Reads the ICCP chunk from the stream. - /// - /// The stream to decode from. - /// The image metadata. - /// Temporary buffer. - private void ReadIccProfile(BufferedReadStream stream, ImageMetadata metadata, Span 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; - } - } - } - /// /// Reads the animation parameters chunk from the stream. /// @@ -512,50 +399,6 @@ internal sealed class WebpDecoderCore : ImageDecoderCore, IDisposable } } - /// - /// Identifies the chunk type from the chunk. - /// - /// The stream to decode from. - /// Temporary buffer. - /// - /// Thrown if the input stream is not valid. - /// - private static WebpChunkType ReadChunkType(BufferedReadStream stream, Span buffer) - { - if (stream.Read(buffer, 0, 4) == 4) - { - return (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); - } - - throw new ImageFormatException("Invalid Webp data."); - } - - /// - /// 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. - /// - /// The stream to decode from. - /// Temporary buffer. - /// If true, the chunk size is required to be read, otherwise it can be skipped. - /// The chunk size in bytes. - /// Invalid data. - private static uint ReadChunkSize(BufferedReadStream stream, Span 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); - } - /// public void Dispose() => this.alphaData?.Dispose(); } diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 96ef84510..d48065d01 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -34,7 +34,12 @@ public abstract partial class Image /// The encoded image format is unknown. public static unsafe IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan 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 /// The encoded image format is unknown. public static unsafe ImageInfo Identify(DecoderOptions options, ReadOnlySpan 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 /// The encoded image format is unknown. public static unsafe Image Load(DecoderOptions options, ReadOnlySpan 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 Load(DecoderOptions options, ReadOnlySpan data) where TPixel : unmanaged, IPixel { + 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); diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index 53b672b7d..efe1b6e2f 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/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)); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index a1966e2bb..178f5375f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/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(() => Image.DetectFormat([])); + [Fact] public void FromFileSystemPath_GlobalConfiguration() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 490fa3b0b..433a1e101 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/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(() => Image.Identify([])); + [Fact] public void FromBytes_CustomConfiguration() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs index 3a47a0ea7..600bed010 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs +++ b/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(() => Image.Load(options, [])); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs index 00ec985ac..a0ce1e5d8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -45,5 +45,9 @@ public partial class ImageTests VerifyDecodedImage(img); Assert.IsType(img.Metadata.DecodedImageFormat); } + + [Fact] + public void FromBytes_EmptySpan_Throws() + => Assert.ThrowsAny(() => Image.Load([])); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs index a064b6472..c2609545d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs +++ b/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(() => Image.Load(DecoderOptions.Default, ms)); + } + + public void Dispose() + { + this.Stream?.Dispose(); + GC.SuppressFinalize(this); + } } }