diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
new file mode 100644
index 0000000000..7069e89823
--- /dev/null
+++ b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// The exception that is thrown when the library tries to load
+ /// an image which contains invalid content.
+ ///
+ public sealed class InvalidImageContentException : ImageFormatException
+ {
+ ///
+ /// Initializes a new instance of the class with the name of the
+ /// parameter that causes this exception.
+ ///
+ /// The error message that explains the reason for this exception.
+ public InvalidImageContentException(string errorMessage)
+ : base(errorMessage)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index dfdbb22fa5..eeb1ae714a 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -462,7 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
- BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap.");
+ BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap.");
}
if (cmd[0] == RleCommand)
@@ -569,7 +569,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
- BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap.");
+ BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap.");
}
if (cmd[0] == RleCommand)
@@ -648,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
- BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap.");
+ BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap.");
}
if (cmd[0] == RleCommand)
@@ -1431,7 +1431,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
{
- BmpThrowHelper.ThrowImageFormatException(
+ BmpThrowHelper.ThrowInvalidImageContentException(
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");
}
@@ -1445,7 +1445,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int skipAmount = this.fileHeader.Offset - (int)this.stream.Position;
if ((skipAmount + (int)this.stream.Position) > this.stream.Length)
{
- BmpThrowHelper.ThrowImageFormatException("Invalid fileheader offset found. Offset is greater than the stream length.");
+ BmpThrowHelper.ThrowInvalidImageContentException("Invalid fileheader offset found. Offset is greater than the stream length.");
}
if (skipAmount > 0)
diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
index 9ede660705..3411de0421 100644
--- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
@@ -393,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
default:
// Compression type 3 (1DHuffman) is not supported.
- BmpThrowHelper.ThrowImageFormatException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24.");
+ BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24.");
break;
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
index 443471404e..c48566f835 100644
--- a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal static class BmpThrowHelper
{
///
- /// Cold path optimization for throwing -s
+ /// Cold path optimization for throwing 's
///
/// The error message for the exception.
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowImageFormatException(string errorMessage)
- {
- throw new ImageFormatException(errorMessage);
- }
+ public static void ThrowInvalidImageContentException(string errorMessage)
+ => throw new InvalidImageContentException(errorMessage);
///
- /// Cold path optimization for throwing -s
+ /// Cold path optimization for throwing 's
///
/// The error message for the exception.
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage)
- {
- throw new NotSupportedException(errorMessage);
- }
+ => throw new NotSupportedException(errorMessage);
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 7919bc1489..de5aa78843 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -318,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (length > GifConstants.MaxCommentSubBlockLength)
{
- throw new ImageFormatException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block");
+ GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block");
}
if (this.IgnoreMetadata)
diff --git a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs
new file mode 100644
index 0000000000..1d81008a01
--- /dev/null
+++ b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Gif
+{
+ internal static class GifThrowHelper
+ {
+ ///
+ /// Cold path optimization for throwing 's
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowInvalidImageContentException(string errorMessage)
+ => throw new InvalidImageContentException(errorMessage);
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
index 09d6a4d1d8..002f79f84c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
// TODO: Run fallback scalar code here
// However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007
- throw new NotImplementedException("Your CPU architecture is too modern!");
+ JpegThrowHelper.ThrowNotImplementedException("Your CPU architecture is too modern!");
}
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
index 7497c8a409..44878bd6c7 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
@@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
if (xDensity <= 0)
{
- JpegThrowHelper.ThrowImageFormatException($"X-Density {xDensity} must be greater than 0.");
+ JpegThrowHelper.ThrowInvalidImageContentException($"X-Density {xDensity} must be greater than 0.");
}
if (yDensity <= 0)
{
- JpegThrowHelper.ThrowImageFormatException($"Y-Density {yDensity} must be greater than 0.");
+ JpegThrowHelper.ThrowInvalidImageContentException($"Y-Density {yDensity} must be greater than 0.");
}
this.MajorVersion = majorVersion;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
index 87b486ea65..325d7780ae 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
@@ -28,6 +28,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
(byte)'I', (byte)'L', (byte)'E', (byte)'\0'
};
+ ///
+ /// Gets the adobe photoshop APP13 marker which can contain IPTC meta data.
+ ///
+ public static ReadOnlySpan AdobePhotoshopApp13Marker => new[]
+ {
+ (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', (byte)'\0'
+ };
+
+ ///
+ /// Gets the 8BIM marker, which signals the start of a adobe specific image resource block.
+ ///
+ public static ReadOnlySpan AdobeImageResourceBlockMarker => new[]
+ {
+ (byte)'8', (byte)'B', (byte)'I', (byte)'M'
+ };
+
+ ///
+ /// Gets a IPTC Image resource ID.
+ ///
+ public static ReadOnlySpan AdobeIptcMarker => new[]
+ {
+ (byte)4, (byte)4
+ };
+
///
/// Gets the EXIF specific markers.
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 951fec1d4c..654610659d 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -14,6 +14,7 @@ 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.Iptc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private readonly byte[] markerBuffer = new byte[2];
///
- /// The DC Huffman tables
+ /// The DC Huffman tables.
///
private HuffmanTable[] dcHuffmanTables;
@@ -56,37 +57,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private HuffmanTable[] acHuffmanTables;
///
- /// The reset interval determined by RST markers
+ /// The reset interval determined by RST markers.
///
private ushort resetInterval;
///
- /// Whether the image has an EXIF marker
+ /// Whether the image has an EXIF marker.
///
private bool isExif;
///
- /// Contains exif data
+ /// Contains exif data.
///
private byte[] exifData;
///
- /// Whether the image has an ICC marker
+ /// Whether the image has an ICC marker.
///
private bool isIcc;
///
- /// Contains ICC data
+ /// Contains ICC data.
///
private byte[] iccData;
///
- /// Contains information about the JFIF marker
+ /// Whether the image has a IPTC data.
+ ///
+ private bool isIptc;
+
+ ///
+ /// Contains IPTC data.
+ ///
+ private byte[] iptcData;
+
+ ///
+ /// Contains information about the JFIF marker.
///
private JFifMarker jFif;
///
- /// Contains information about the Adobe marker
+ /// Contains information about the Adobe marker.
///
private AdobeMarker adobe;
@@ -213,6 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
+ this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage();
}
@@ -226,6 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream, true);
this.InitExifProfile();
this.InitIccProfile();
+ this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata);
@@ -246,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
- JpegThrowHelper.ThrowImageFormatException("Missing SOI marker.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
}
this.InputStream.Read(this.markerBuffer, 0, 2);
@@ -344,10 +357,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
- case JpegConstants.Markers.APP13:
this.InputStream.Skip(remaining);
break;
+ case JpegConstants.Markers.APP13:
+ this.ProcessApp13Marker(remaining);
+ break;
+
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
@@ -407,7 +423,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
: JpegColorSpace.Cmyk;
}
- JpegThrowHelper.ThrowImageFormatException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}");
+ JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}");
return default;
}
@@ -437,6 +453,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ ///
+ /// Initializes the IPTC profile.
+ ///
+ private void InitIptcProfile()
+ {
+ if (this.isIptc)
+ {
+ var profile = new IptcProfile(this.iptcData);
+ this.Metadata.IptcProfile = profile;
+ }
+ }
+
///
/// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header.
///
@@ -582,6 +610,95 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ ///
+ /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop.
+ /// The content of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessApp13Marker(int remaining)
+ {
+ if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata)
+ {
+ this.InputStream.Skip(remaining);
+ return;
+ }
+
+ this.InputStream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length);
+ remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
+ if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
+ {
+ var resourceBlockData = new byte[remaining];
+ this.InputStream.Read(resourceBlockData, 0, remaining);
+ Span blockDataSpan = resourceBlockData.AsSpan();
+
+ while (blockDataSpan.Length > 12)
+ {
+ if (!ProfileResolver.IsProfile(blockDataSpan.Slice(0, 4), ProfileResolver.AdobeImageResourceBlockMarker))
+ {
+ return;
+ }
+
+ blockDataSpan = blockDataSpan.Slice(4);
+ Span imageResourceBlockId = blockDataSpan.Slice(0, 2);
+ if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker))
+ {
+ var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
+ var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
+ int dataStartIdx = 2 + resourceBlockNameLength + 4;
+ if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize)
+ {
+ this.isIptc = true;
+ this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray();
+ break;
+ }
+ }
+ else
+ {
+ var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
+ var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
+ int dataStartIdx = 2 + resourceBlockNameLength + 4;
+ if (blockDataSpan.Length < dataStartIdx + resourceDataSize)
+ {
+ // Not enough data or the resource data size is wrong.
+ break;
+ }
+
+ blockDataSpan = blockDataSpan.Slice(dataStartIdx + resourceDataSize);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads the adobe image resource block name: a Pascal string (padded to make size even).
+ ///
+ /// The span holding the block resource data.
+ /// The length of the name.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int ReadImageResourceNameLength(Span blockDataSpan)
+ {
+ byte nameLength = blockDataSpan[2];
+ var nameDataSize = nameLength == 0 ? 2 : nameLength;
+ if (nameDataSize % 2 != 0)
+ {
+ nameDataSize++;
+ }
+
+ return nameDataSize;
+ }
+
+ ///
+ /// Reads the length of a adobe image resource data block.
+ ///
+ /// The span holding the block resource data.
+ /// The length of the block name.
+ /// The block length.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength)
+ {
+ return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
+ }
+
///
/// Processes the application header containing the Adobe identifier
/// which stores image encoding information for DCT filters.
@@ -704,7 +821,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
if (this.Frame != null)
{
- JpegThrowHelper.ThrowImageFormatException("Multiple SOF markers. Only single frame jpegs supported.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
}
// Read initial marker definitions.
@@ -714,7 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// We only support 8-bit and 12-bit precision.
if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1)
{
- JpegThrowHelper.ThrowImageFormatException("Only 8-Bit and 12-Bit precision supported.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported.");
}
this.Precision = this.temp[0];
@@ -811,13 +928,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Types 0..1 DC..AC
if (tableType > 1)
{
- JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table type.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table type.");
}
// Max tables of each type
if (tableIndex > 3)
{
- JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table index.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index.");
}
this.InputStream.Read(huffmanData.Array, 0, 16);
@@ -836,7 +953,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (codeLengthSum > 256 || codeLengthSum > length)
{
- JpegThrowHelper.ThrowImageFormatException("Huffman table has excessive length.");
+ JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
}
using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
@@ -878,7 +995,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
if (this.Frame is null)
{
- JpegThrowHelper.ThrowImageFormatException("No readable SOFn (Start Of Frame) marker found.");
+ JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
}
int selectorsCount = this.InputStream.ReadByte();
@@ -899,7 +1016,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentIndex < 0)
{
- JpegThrowHelper.ThrowImageFormatException($"Unknown component selector {componentIndex}.");
+ JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}.");
}
ref JpegComponent component = ref this.Frame.Components[componentIndex];
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 32f4d22878..eed95c6b07 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -4,6 +4,7 @@
using System;
using System.Buffers.Binary;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@@ -13,6 +14,7 @@ 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.Iptc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@@ -231,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker.
this.WriteApplicationHeader(metadata);
- // Write Exif and ICC profiles
+ // Write Exif, ICC and IPTC profiles
this.WriteProfiles(metadata);
// Write the quantization tables.
@@ -647,9 +649,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Writes the EXIF profile.
///
/// The exif profile.
- ///
- /// Thrown if the EXIF profile size exceeds the limit
- ///
private void WriteExifProfile(ExifProfile exifProfile)
{
if (exifProfile is null || exifProfile.Values.Count == 0)
@@ -697,16 +696,68 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ ///
+ /// Writes the IPTC metadata.
+ ///
+ /// The iptc metadata to write.
+ ///
+ /// Thrown if the IPTC profile size exceeds the limit of 65533 bytes.
+ ///
+ private void WriteIptcProfile(IptcProfile iptcProfile)
+ {
+ const int Max = 65533;
+ if (iptcProfile is null || !iptcProfile.Values.Any())
+ {
+ return;
+ }
+
+ iptcProfile.UpdateData();
+ byte[] data = iptcProfile.Data;
+ if (data.Length == 0)
+ {
+ return;
+ }
+
+ if (data.Length > Max)
+ {
+ throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes");
+ }
+
+ var app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length +
+ ProfileResolver.AdobeImageResourceBlockMarker.Length +
+ ProfileResolver.AdobeIptcMarker.Length +
+ 2 + 4 + data.Length;
+ this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13);
+ this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker);
+ this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker);
+ this.outputStream.Write(ProfileResolver.AdobeIptcMarker);
+ this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even)
+ this.outputStream.WriteByte(0);
+ BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length);
+ this.outputStream.Write(this.buffer, 0, 4);
+ this.outputStream.Write(data, 0, data.Length);
+ }
+
///
/// Writes the App1 header.
///
- /// The length of the data the app1 marker contains
+ /// The length of the data the app1 marker contains.
private void WriteApp1Header(int app1Length)
+ {
+ this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
+ }
+
+ ///
+ /// Writes a AppX header.
+ ///
+ /// The length of the data the app marker contains.
+ /// The app marker to write.
+ private void WriteAppHeader(int length, byte appMarker)
{
this.buffer[0] = JpegConstants.Markers.XFF;
- this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker
- this.buffer[2] = (byte)((app1Length >> 8) & 0xFF);
- this.buffer[3] = (byte)(app1Length & 0xFF);
+ this.buffer[1] = appMarker;
+ this.buffer[2] = (byte)((length >> 8) & 0xFF);
+ this.buffer[3] = (byte)(length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
}
@@ -805,6 +856,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile);
this.WriteIccProfile(metadata.IccProfile);
+ this.WriteIptcProfile(metadata.IptcProfile);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
index 7e8384dcff..dd44cb2d19 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
@@ -1,6 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg
@@ -8,25 +9,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal static class JpegThrowHelper
{
///
- /// Cold path optimization for throwing 's.
+ /// Cold path optimization for throwing 's.
///
/// The error message for the exception.
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage);
+ public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
+
+ ///
+ /// Cold path optimization for throwing 's
+ ///
+ /// The error message for the exception.
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotImplementedException(string errorMessage)
+ => throw new NotImplementedException(errorMessage);
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowBadMarker(string marker, int length) => throw new ImageFormatException($"Marker {marker} has bad length {length}.");
+ public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowBadQuantizationTable() => throw new ImageFormatException("Bad Quantization Table index.");
+ public static void ThrowBadQuantizationTable() => throw new InvalidImageContentException("Bad Quantization Table index.");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowBadSampling() => throw new ImageFormatException("Bad sampling factor.");
+ public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor.");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new ImageFormatException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}.");
+ public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}.");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowInvalidImageDimensions(int width, int height) => throw new ImageFormatException($"Invalid image dimensions: {width}x{height}.");
+ public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}.");
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index bc7b9d8150..272f93d108 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -1141,24 +1141,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The .
private void ValidateChunk(in PngChunk chunk)
{
- if (!chunk.IsCritical)
- {
- return;
- }
-
- Span chunkType = stackalloc byte[4];
+ uint crc = this.ReadChunkCrc();
- BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
+ if (chunk.IsCritical)
+ {
+ Span chunkType = stackalloc byte[4];
+ BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
- this.crc.Reset();
- this.crc.Update(chunkType);
- this.crc.Update(chunk.Data.GetSpan());
+ this.crc.Reset();
+ this.crc.Update(chunkType);
+ this.crc.Update(chunk.Data.GetSpan());
- uint crc = this.ReadChunkCrc();
- if (this.crc.Value != crc)
- {
- string chunkTypeName = Encoding.ASCII.GetString(chunkType);
- PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
+ if (this.crc.Value != crc)
+ {
+ string chunkTypeName = Encoding.ASCII.GetString(chunkType);
+ PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
+ }
}
}
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
index dd3a054641..dcb643d036 100644
--- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -12,21 +12,21 @@ namespace SixLabors.ImageSharp.Formats.Png
internal static class PngThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
+ public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
+ public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
+ public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data.");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
+ public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
[MethodImpl(InliningOptions.ColdPath)]
- public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
+ public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type.");
}
}
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index 816a472fb2..057ec1bfc7 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -114,12 +114,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
if (this.fileHeader.CMapLength <= 0)
{
- TgaThrowHelper.ThrowImageFormatException("Missing tga color map length");
+ TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map length");
}
if (this.fileHeader.CMapDepth <= 0)
{
- TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth");
+ TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map depth");
}
int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
@@ -898,7 +898,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
var alphaBits = this.fileHeader.ImageDescriptor & 0xf;
if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8)
{
- TgaThrowHelper.ThrowImageFormatException("Invalid alpha channel bits");
+ TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits");
}
this.tgaMetadata.AlphaChannelBits = (byte)alphaBits;
diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
index 845d009227..1714a2025e 100644
--- a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
+++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
@@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Tga
internal static class TgaThrowHelper
{
///
- /// Cold path optimization for throwing -s
+ /// Cold path optimization for throwing 's
///
/// The error message for the exception.
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowImageFormatException(string errorMessage)
- {
- throw new ImageFormatException(errorMessage);
- }
+ public static void ThrowInvalidImageContentException(string errorMessage)
+ => throw new InvalidImageContentException(errorMessage);
///
- /// Cold path optimization for throwing -s
+ /// Cold path optimization for throwing 's
///
/// The error message for the exception.
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage)
- {
- throw new NotSupportedException(errorMessage);
- }
+ => throw new NotSupportedException(errorMessage);
}
}
diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs
index 06b05fe3c4..a0e8097d8e 100644
--- a/src/ImageSharp/Image.FromBytes.cs
+++ b/src/ImageSharp/Image.FromBytes.cs
@@ -17,21 +17,23 @@ namespace SixLabors.ImageSharp
/// By reading the header on the provided byte array this calculates the images format.
///
/// The byte array containing encoded image data to read the header from.
+ /// The data is null.
/// The format or null if none found.
public static IImageFormat DetectFormat(byte[] data)
- {
- return DetectFormat(Configuration.Default, data);
- }
+ => DetectFormat(Configuration.Default, data);
///
/// By reading the header on the provided byte array this calculates the images format.
///
/// The configuration.
/// The byte array containing encoded image data to read the header from.
+ /// The configuration is null.
+ /// The data is null.
/// The mime type or null if none found.
public static IImageFormat DetectFormat(Configuration configuration, byte[] data)
{
- Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(data, nameof(data));
+
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return DetectFormat(configuration, stream);
@@ -42,7 +44,8 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
///
/// The byte array containing encoded image data to read the header from.
- /// Thrown if the stream is not readable.
+ /// The data is null.
+ /// The data is not readable.
///
/// The or null if suitable info detector not found.
///
@@ -53,7 +56,8 @@ namespace SixLabors.ImageSharp
///
/// The byte array containing encoded image data to read the header from.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
+ /// The data is null.
+ /// The data is not readable.
///
/// The or null if suitable info detector not found.
///
@@ -65,13 +69,16 @@ namespace SixLabors.ImageSharp
/// The configuration.
/// The byte array containing encoded image data to read the header from.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
+ /// The configuration is null.
+ /// The data is null.
+ /// The data is not readable.
///
/// The or null if suitable info detector is not found.
///
public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format)
{
- Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(data, nameof(data));
+
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Identify(configuration, stream, out format);
@@ -82,14 +89,20 @@ namespace SixLabors.ImageSharp
/// Load a new instance of from the given encoded byte array.
///
/// The byte array containing image data.
+ /// The configuration is null.
+ /// The data is null.
/// A new .
- public static Image Load(byte[] data) => Load(Configuration.Default, data);
+ public static Image Load(byte[] data)
+ => Load(Configuration.Default, data);
///
/// Load a new instance of from the given encoded byte array.
///
/// The byte array containing encoded image data.
/// The pixel format.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(byte[] data)
where TPixel : unmanaged, IPixel
@@ -101,6 +114,9 @@ namespace SixLabors.ImageSharp
/// The byte array containing image data.
/// The mime type of the decoded image.
/// The pixel format.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel
@@ -112,10 +128,16 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The byte array containing encoded image data.
/// The pixel format.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(Configuration configuration, byte[] data)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(data, nameof(data));
+
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(configuration, stream);
@@ -129,10 +151,16 @@ namespace SixLabors.ImageSharp
/// The byte array containing encoded image data.
/// The of the decoded image.
/// The pixel format.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(Configuration configuration, byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(data, nameof(data));
+
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(configuration, stream, out format);
@@ -145,10 +173,15 @@ namespace SixLabors.ImageSharp
/// The byte array containing encoded image data.
/// The decoder.
/// The pixel format.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(data, nameof(data));
+
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(stream, decoder);
@@ -162,10 +195,16 @@ namespace SixLabors.ImageSharp
/// The byte array containing encoded image data.
/// The decoder.
/// The pixel format.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(data, nameof(data));
+
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load(configuration, stream, decoder);
@@ -173,9 +212,9 @@ namespace SixLabors.ImageSharp
}
///
- /// By reading the header on the provided byte array this calculates the images format.
+ /// By reading the header on the provided byte span this calculates the images format.
///
- /// The byte array containing encoded image data to read the header from.
+ /// The byte span containing encoded image data to read the header from.
/// The format or null if none found.
public static IImageFormat DetectFormat(ReadOnlySpan data)
{
@@ -183,13 +222,16 @@ namespace SixLabors.ImageSharp
}
///
- /// By reading the header on the provided byte array this calculates the images format.
+ /// By reading the header on the provided byte span this calculates the images format.
///
/// The configuration.
- /// The byte array containing encoded image data to read the header from.
+ /// The byte span containing encoded image data to read the header from.
+ /// The configuration is null.
/// The mime type or null if none found.
public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan data)
{
+ Guard.NotNull(configuration, nameof(configuration));
+
int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0)
{
@@ -214,28 +256,34 @@ namespace SixLabors.ImageSharp
///
/// The byte span containing encoded image data.
/// The pixel format.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(ReadOnlySpan data)
where TPixel : unmanaged, IPixel
=> Load(Configuration.Default, data);
///
- /// Load a new instance of from the given encoded byte array.
+ /// Load a new instance of from the given encoded byte span.
///
/// The byte span containing image data.
/// The mime type of the decoded image.
/// The pixel format.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(ReadOnlySpan data, out IImageFormat format)
where TPixel : unmanaged, IPixel
=> Load(Configuration.Default, data, out format);
///
- /// Load a new instance of from the given encoded byte array.
+ /// Load a new instance of from the given encoded byte span.
///
/// The byte span containing encoded image data.
/// The decoder.
/// The pixel format.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(ReadOnlySpan data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel
@@ -247,6 +295,9 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The byte span containing encoded image data.
/// The pixel format.
+ /// The configuration is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static unsafe Image Load(Configuration configuration, ReadOnlySpan data)
where TPixel : unmanaged, IPixel
@@ -267,6 +318,9 @@ namespace SixLabors.ImageSharp
/// The byte span containing image data.
/// The decoder.
/// The pixel format.
+ /// The configuration is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static unsafe Image Load(
Configuration configuration,
@@ -290,6 +344,9 @@ namespace SixLabors.ImageSharp
/// The byte span containing image data.
/// The of the decoded image.
/// The pixel format.
+ /// The configuration is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static unsafe Image Load(
Configuration configuration,
@@ -311,25 +368,38 @@ namespace SixLabors.ImageSharp
///
/// The byte array containing image data.
/// The detected format.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(byte[] data, out IImageFormat format) =>
- Load(Configuration.Default, data, out format);
+ public static Image Load(byte[] data, out IImageFormat format)
+ => Load(Configuration.Default, data, out format);
///
/// Load a new instance of from the given encoded byte array.
///
/// The byte array containing encoded image data.
/// The decoder.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(byte[] data, IImageDecoder decoder) => Load(Configuration.Default, data, decoder);
+ public static Image Load(byte[] data, IImageDecoder decoder)
+ => Load(Configuration.Default, data, decoder);
///
/// Load a new instance of from the given encoded byte array.
///
/// The configuration for the decoder.
/// The byte array containing encoded image data.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(Configuration configuration, byte[] data) => Load(configuration, data, out _);
+ public static Image Load(Configuration configuration, byte[] data)
+ => Load(configuration, data, out _);
///
/// Load a new instance of from the given encoded byte array.
@@ -337,6 +407,10 @@ namespace SixLabors.ImageSharp
/// The configuration for the decoder.
/// The byte array containing image data.
/// The decoder.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder)
{
@@ -352,6 +426,10 @@ namespace SixLabors.ImageSharp
/// The configuration for the decoder.
/// The byte array containing image data.
/// The mime type of the decoded image.
+ /// The configuration is null.
+ /// The data is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
public static Image Load(Configuration configuration, byte[] data, out IImageFormat format)
{
@@ -365,26 +443,36 @@ namespace SixLabors.ImageSharp
/// Load a new instance of from the given encoded byte span.
///
/// The byte span containing image data.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(ReadOnlySpan data) => Load(Configuration.Default, data);
+ public static Image Load(ReadOnlySpan data)
+ => Load(Configuration.Default, data);
///
/// Load a new instance of from the given encoded byte span.
///
/// The byte span containing image data.
/// The decoder.
+ /// The data is null.
+ /// The decoder is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(ReadOnlySpan data, IImageDecoder decoder) =>
- Load(Configuration.Default, data, decoder);
+ public static Image Load(ReadOnlySpan data, IImageDecoder decoder)
+ => Load(Configuration.Default, data, decoder);
///
/// Load a new instance of from the given encoded byte array.
///
/// The byte span containing image data.
/// The detected format.
+ /// The decoder is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(ReadOnlySpan data, out IImageFormat format) =>
- Load(Configuration.Default, data, out format);
+ public static Image Load(ReadOnlySpan data, out IImageFormat format)
+ => Load(Configuration.Default, data, out format);
///
/// Decodes a new instance of from the given encoded byte span.
@@ -392,7 +480,8 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The byte span containing image data.
/// The .
- public static Image Load(Configuration configuration, ReadOnlySpan data) => Load(configuration, data, out _);
+ public static Image Load(Configuration configuration, ReadOnlySpan data)
+ => Load(configuration, data, out _);
///
/// Load a new instance of from the given encoded byte span.
@@ -400,6 +489,11 @@ namespace SixLabors.ImageSharp
/// The Configuration.
/// The byte span containing image data.
/// The decoder.
+ /// The configuration is null.
+ /// The decoder is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
public static unsafe Image Load(
Configuration configuration,
@@ -421,6 +515,9 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The byte span containing image data.
/// The of the decoded image.>
+ /// The configuration is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
public static unsafe Image Load(
Configuration configuration,
diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs
index 1a9fa15462..8546dd2700 100644
--- a/src/ImageSharp/Image.FromFile.cs
+++ b/src/ImageSharp/Image.FromFile.cs
@@ -19,19 +19,19 @@ namespace SixLabors.ImageSharp
/// The image file to open and to read the header from.
/// The mime type or null if none found.
public static IImageFormat DetectFormat(string filePath)
- {
- return DetectFormat(Configuration.Default, filePath);
- }
+ => DetectFormat(Configuration.Default, filePath);
///
/// By reading the header on the provided file this calculates the images mime type.
///
/// The configuration.
/// The image file to open and to read the header from.
+ /// The configuration is null.
/// The mime type or null if none found.
public static IImageFormat DetectFormat(Configuration configuration, string filePath)
{
Guard.NotNull(configuration, nameof(configuration));
+
using (Stream file = configuration.FileSystem.OpenRead(filePath))
{
return DetectFormat(configuration, file);
@@ -42,22 +42,22 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
///
/// The image file to open and to read the header from.
- /// Thrown if the stream is not readable.
///
/// The or null if suitable info detector not found.
///
- public static IImageInfo Identify(string filePath) => Identify(filePath, out IImageFormat _);
+ public static IImageInfo Identify(string filePath)
+ => Identify(filePath, out IImageFormat _);
///
/// Reads the raw image information from the specified stream without fully decoding it.
///
/// The image file to open and to read the header from.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
///
/// The or null if suitable info detector not found.
///
- public static IImageInfo Identify(string filePath, out IImageFormat format) => Identify(Configuration.Default, filePath, out format);
+ public static IImageInfo Identify(string filePath, out IImageFormat format)
+ => Identify(Configuration.Default, filePath, out format);
///
/// Reads the raw image information from the specified stream without fully decoding it.
@@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp
/// The configuration.
/// The image file to open and to read the header from.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
+ /// The configuration is null.
///
/// The or null if suitable info detector is not found.
///
@@ -86,7 +86,8 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is not readable nor seekable.
///
/// The .
- public static Image Load(string path) => Load(Configuration.Default, path);
+ public static Image Load(string path)
+ => Load(Configuration.Default, path);
///
/// Create a new instance of the class from the given file.
@@ -97,18 +98,21 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is not readable nor seekable.
///
/// A new .
- public static Image Load(string path, out IImageFormat format) => Load(Configuration.Default, path, out format);
+ public static Image Load(string path, out IImageFormat format)
+ => Load(Configuration.Default, path, out format);
///
/// Create a new instance of the class from the given file.
///
/// The configuration for the decoder.
/// The file path to the image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The configuration is null.
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(Configuration configuration, string path) => Load(configuration, path, out _);
+ public static Image Load(Configuration configuration, string path)
+ => Load(configuration, path, out _);
///
/// Create a new instance of the class from the given file.
@@ -116,13 +120,17 @@ namespace SixLabors.ImageSharp
/// The Configuration.
/// The file path to the image.
/// The decoder.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The configuration is null.
+ /// The path is null.
+ /// The decoder is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
{
Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(path, nameof(path));
+
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, decoder);
@@ -134,57 +142,58 @@ namespace SixLabors.ImageSharp
///
/// The file path to the image.
/// The decoder.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The path is null.
+ /// The decoder is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(string path, IImageDecoder decoder) => Load(Configuration.Default, path, decoder);
+ public static Image Load(string path, IImageDecoder decoder)
+ => Load(Configuration.Default, path, decoder);
///
/// Create a new instance of the class from the given file.
///
/// The file path to the image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(string path)
where TPixel : unmanaged, IPixel
- {
- return Load(Configuration.Default, path);
- }
+ => Load(Configuration.Default, path);
///
/// Create a new instance of the class from the given file.
///
/// The file path to the image.
/// The mime type of the decoded image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(string path, out IImageFormat format)
where TPixel : unmanaged, IPixel
- {
- return Load(Configuration.Default, path, out format);
- }
+ => Load(Configuration.Default, path, out format);
///
/// Create a new instance of the class from the given file.
///
/// The configuration options.
/// The file path to the image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The configuration is null.
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(Configuration configuration, string path)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(path, nameof(path));
+
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream);
@@ -197,15 +206,18 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The file path to the image.
/// The mime type of the decoded image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The configuration is null.
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(Configuration configuration, string path, out IImageFormat format)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(path, nameof(path));
+
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, out format);
@@ -219,13 +231,16 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The file path to the image.
/// The mime type of the decoded image.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The configuration is null.
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(Configuration configuration, string path, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(path, nameof(path));
+
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, out format);
@@ -237,16 +252,14 @@ namespace SixLabors.ImageSharp
///
/// The file path to the image.
/// The decoder.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The path is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel
- {
- return Load(Configuration.Default, path, decoder);
- }
+ => Load(Configuration.Default, path, decoder);
///
/// Create a new instance of the class from the given file.
@@ -254,15 +267,19 @@ namespace SixLabors.ImageSharp
/// The Configuration.
/// The file path to the image.
/// The decoder.
- ///
- /// Thrown if the stream is not readable nor seekable.
- ///
+ /// The configuration is null.
+ /// The path is null.
+ /// The decoder is null.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel
{
Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(path, nameof(path));
+
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, decoder);
diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs
index 52d71409bb..332ca471e9 100644
--- a/src/ImageSharp/Image.FromStream.cs
+++ b/src/ImageSharp/Image.FromStream.cs
@@ -19,16 +19,20 @@ namespace SixLabors.ImageSharp
/// By reading the header on the provided stream this calculates the images format type.
///
/// The image stream to read the header from.
- /// Thrown if the stream is not readable.
+ /// The stream is null.
+ /// The stream is not readable.
/// The format type or null if none found.
- public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream);
+ public static IImageFormat DetectFormat(Stream stream)
+ => DetectFormat(Configuration.Default, stream);
///
/// By reading the header on the provided stream this calculates the images format type.
///
/// The configuration.
/// The image stream to read the header from.
- /// Thrown if the stream is not readable.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
/// The format type or null if none found.
public static IImageFormat DetectFormat(Configuration configuration, Stream stream)
=> WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration));
@@ -37,22 +41,28 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
///
/// The image stream to read the header from.
- /// Thrown if the stream is not readable.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image contains invalid content.
///
/// The or null if suitable info detector not found.
///
- public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _);
+ public static IImageInfo Identify(Stream stream)
+ => Identify(stream, out IImageFormat _);
///
/// Reads the raw image information from the specified stream without fully decoding it.
///
/// The image stream to read the header from.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image contains invalid content.
///
/// The or null if suitable info detector not found.
///
- public static IImageInfo Identify(Stream stream, out IImageFormat format) => Identify(Configuration.Default, stream, out format);
+ public static IImageInfo Identify(Stream stream, out IImageFormat format)
+ => Identify(Configuration.Default, stream, out format);
///
/// Reads the raw image information from the specified stream without fully decoding it.
@@ -60,7 +70,10 @@ namespace SixLabors.ImageSharp
/// The configuration.
/// The image stream to read the information from.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image contains invalid content.
///
/// The or null if suitable info detector is not found.
///
@@ -78,18 +91,23 @@ namespace SixLabors.ImageSharp
///
/// The stream containing image information.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(Stream stream, out IImageFormat format) => Load(Configuration.Default, stream, out format);
+ public static Image Load(Stream stream, out IImageFormat format)
+ => Load(Configuration.Default, stream, out format);
///
/// Decode a new instance of the class from the given stream.
/// The pixel format is selected by the decoder.
///
/// The stream containing image information.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
public static Image Load(Stream stream) => Load(Configuration.Default, stream);
@@ -99,10 +117,14 @@ namespace SixLabors.ImageSharp
///
/// The stream containing image information.
/// The decoder.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The stream is null.
+ /// The decoder is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The .
- public static Image Load(Stream stream, IImageDecoder decoder) => Load(Configuration.Default, stream, decoder);
+ public static Image Load(Stream stream, IImageDecoder decoder)
+ => Load(Configuration.Default, stream, decoder);
///
/// Decode a new instance of the class from the given stream.
@@ -111,19 +133,29 @@ namespace SixLabors.ImageSharp
/// The configuration for the decoder.
/// The stream containing image information.
/// The decoder.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The decoder is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .>
- public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) =>
- WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
+ public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
+ {
+ Guard.NotNull(decoder, nameof(decoder));
+ return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
+ }
///
/// Decode a new instance of the class from the given stream.
///
/// The configuration for the decoder.
/// The stream containing image information.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .>
public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _);
@@ -131,8 +163,10 @@ namespace SixLabors.ImageSharp
/// Create a new instance of the class from the given stream.
///
/// The stream containing image information.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .>
public static Image Load(Stream stream)
@@ -144,8 +178,10 @@ namespace SixLabors.ImageSharp
///
/// The stream containing image information.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .>
public static Image Load(Stream stream, out IImageFormat format)
@@ -157,8 +193,10 @@ namespace SixLabors.ImageSharp
///
/// The stream containing image information.
/// The decoder.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .>
public static Image Load(Stream stream, IImageDecoder decoder)
@@ -171,8 +209,11 @@ namespace SixLabors.ImageSharp
/// The Configuration.
/// The stream containing image information.
/// The decoder.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .>
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
@@ -184,8 +225,11 @@ namespace SixLabors.ImageSharp
///
/// The configuration options.
/// The stream containing image information.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .>
public static Image Load(Configuration configuration, Stream stream)
@@ -198,14 +242,16 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The stream containing image information.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// The pixel format.
/// A new .
public static Image Load(Configuration configuration, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel
{
- Guard.NotNull(configuration, nameof(configuration));
(Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration));
format = data.format;
@@ -220,7 +266,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders)
{
- sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
+ sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
@@ -233,12 +279,14 @@ namespace SixLabors.ImageSharp
/// The configuration options.
/// The stream containing image information.
/// The format type of the decoded image.
- /// Thrown if the stream is not readable.
- /// Image cannot be loaded.
+ /// The configuration is null.
+ /// The stream is null.
+ /// The stream is not readable.
+ /// Image format not recognised.
+ /// Image contains invalid content.
/// A new .
public static Image Load(Configuration configuration, Stream stream, out IImageFormat format)
{
- Guard.NotNull(configuration, nameof(configuration));
(Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration));
format = data.format;
@@ -253,7 +301,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders)
{
- sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
+ sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new UnknownImageFormatException(sb.ToString());
@@ -261,6 +309,9 @@ namespace SixLabors.ImageSharp
private static T WithSeekableStream(Configuration configuration, Stream stream, Func action)
{
+ Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(stream, nameof(stream));
+
if (!stream.CanRead)
{
throw new NotSupportedException("Cannot read from the stream.");
diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs
index f8f2e84850..f36243cc3e 100644
--- a/src/ImageSharp/Image.LoadPixelData.cs
+++ b/src/ImageSharp/Image.LoadPixelData.cs
@@ -1,9 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The data length is incorrect.
/// A new .
public static Image LoadPixelData(TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel
@@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The data length is incorrect.
/// A new .
public static Image LoadPixelData(ReadOnlySpan data, int width, int height)
where TPixel : unmanaged, IPixel
@@ -45,6 +46,7 @@ namespace SixLabors.ImageSharp
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The data length is incorrect.
/// A new .
public static Image LoadPixelData(byte[] data, int width, int height)
where TPixel : unmanaged, IPixel
@@ -57,6 +59,7 @@ namespace SixLabors.ImageSharp
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The data length is incorrect.
/// A new .
public static Image LoadPixelData(ReadOnlySpan data, int width, int height)
where TPixel : unmanaged, IPixel
@@ -65,60 +68,68 @@ namespace SixLabors.ImageSharp
///
/// Create a new instance of the class from the given byte array in format.
///
- /// The config for the decoder.
+ /// The configuration for the decoder.
/// The byte array containing image data.
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The configuration is null.
+ /// The data length is incorrect.
/// A new .
- public static Image LoadPixelData(Configuration config, byte[] data, int width, int height)
+ public static Image LoadPixelData(Configuration configuration, byte[] data, int width, int height)
where TPixel : unmanaged, IPixel
- => LoadPixelData(config, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height);
+ => LoadPixelData(configuration, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height);
///
/// Create a new instance of the class from the given byte array in format.
///
- /// The config for the decoder.
+ /// The configuration for the decoder.
/// The byte array containing image data.
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The configuration is null.
+ /// The data length is incorrect.
/// A new .
- public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height)
+ public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height)
where TPixel : unmanaged, IPixel
- => LoadPixelData(config, MemoryMarshal.Cast(data), width, height);
+ => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height);
///
/// Create a new instance of the class from the raw data.
///
- /// The config for the decoder.
+ /// The configuration for the decoder.
/// The Span containing the image Pixel data.
/// The width of the final image.
/// The height of the final image.
/// The pixel format.
+ /// The configuration is null.
+ /// The data length is incorrect.
/// A new .
- public static Image LoadPixelData(Configuration config, TPixel[] data, int width, int height)
+ public static Image LoadPixelData(Configuration configuration, TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel
- {
- return LoadPixelData(config, new ReadOnlySpan(data), width, height);
- }
+ => LoadPixelData(configuration, new ReadOnlySpan(data), width, height);
///
/// Create a new instance of the class from the raw data.
///
- /// The config for the decoder.
+ /// The configuration for the decoder.
/// The Span containing the image Pixel data.
/// The width of the final image.
/// The height of the final image.
+ /// The configuration is null.
+ /// The data length is incorrect.
/// The pixel format.
/// A new .
- public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height)
+ public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(configuration, nameof(configuration));
+
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
- var image = new Image(config, width, height);
+ var image = new Image(configuration, width, height);
data = data.Slice(0, count);
data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);
diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs
index e5181a0db3..0dd8c814d0 100644
--- a/src/ImageSharp/Image.WrapMemory.cs
+++ b/src/ImageSharp/Image.WrapMemory.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -20,22 +20,27 @@ namespace SixLabors.ImageSharp
/// allowing to view/manipulate it as an ImageSharp instance.
///
/// The pixel type
- /// The
+ /// The
/// The pixel memory.
/// The width of the memory image.
/// The height of the memory image.
/// The .
+ /// The configuration is null.
+ /// The metadata is null.
/// An instance
public static Image WrapMemory(
- Configuration config,
+ Configuration configuration,
Memory pixelMemory,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(metadata, nameof(metadata));
+
var memorySource = MemoryGroup.Wrap(pixelMemory);
- return new Image(config, memorySource, width, height, metadata);
+ return new Image(configuration, memorySource, width, height, metadata);
}
///
@@ -43,20 +48,19 @@ namespace SixLabors.ImageSharp
/// allowing to view/manipulate it as an ImageSharp instance.
///
/// The pixel type
- /// The
+ /// The
/// The pixel memory.
/// The width of the memory image.
/// The height of the memory image.
+ /// The configuration is null.
/// An instance.
public static Image WrapMemory(
- Configuration config,
+ Configuration configuration,
Memory pixelMemory,
int width,
int height)
where TPixel : unmanaged, IPixel
- {
- return WrapMemory(config, pixelMemory, width, height, new ImageMetadata());
- }
+ => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata());
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@@ -73,9 +77,7 @@ namespace SixLabors.ImageSharp
int width,
int height)
where TPixel : unmanaged, IPixel
- {
- return WrapMemory(Configuration.Default, pixelMemory, width, height);
- }
+ => WrapMemory(Configuration.Default, pixelMemory, width, height);
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@@ -85,22 +87,27 @@ namespace SixLabors.ImageSharp
/// It will be disposed together with the result image.
///
/// The pixel type
- /// The
+ /// The
/// The that is being transferred to the image
/// The width of the memory image.
/// The height of the memory image.
/// The
+ /// The configuration is null.
+ /// The metadata is null.
/// An instance
public static Image WrapMemory(
- Configuration config,
+ Configuration configuration,
IMemoryOwner pixelMemoryOwner,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel
{
+ Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(metadata, nameof(metadata));
+
var memorySource = MemoryGroup.Wrap(pixelMemoryOwner);
- return new Image(config, memorySource, width, height, metadata);
+ return new Image(configuration, memorySource, width, height, metadata);
}
///
@@ -111,20 +118,19 @@ namespace SixLabors.ImageSharp
/// It will be disposed together with the result image.
///
/// The pixel type.
- /// The
+ /// The
/// The that is being transferred to the image.
/// The width of the memory image.
/// The height of the memory image.
+ /// The configuration is null.
/// An instance
public static Image WrapMemory(
- Configuration config,
+ Configuration configuration,
IMemoryOwner pixelMemoryOwner,
int width,
int height)
where TPixel : unmanaged, IPixel
- {
- return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata());
- }
+ => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata());
///
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@@ -143,8 +149,6 @@ namespace SixLabors.ImageSharp
int width,
int height)
where TPixel : unmanaged, IPixel
- {
- return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
- }
+ => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
}
diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs
index 62ac449b72..e5b2a32a99 100644
--- a/src/ImageSharp/ImageExtensions.cs
+++ b/src/ImageSharp/ImageExtensions.cs
@@ -7,12 +7,11 @@ using System.IO;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
///
- /// Extension methods over Image{TPixel}.
+ /// Extension methods for the type.
///
public static partial class ImageExtensions
{
@@ -20,13 +19,13 @@ namespace SixLabors.ImageSharp
/// Writes the image to the given stream using the currently loaded image format.
///
/// The source image.
- /// The file path to save the image to.
- /// Thrown if the stream is null.
- public static void Save(this Image source, string filePath)
+ /// The file path to save the image to.
+ /// The path is null.
+ public static void Save(this Image source, string path)
{
- Guard.NotNullOrWhiteSpace(filePath, nameof(filePath));
+ Guard.NotNull(path, nameof(path));
- string ext = Path.GetExtension(filePath);
+ string ext = Path.GetExtension(path);
IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
if (format is null)
{
@@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp
sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:");
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats)
{
- sb.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}");
+ sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
}
throw new NotSupportedException(sb.ToString());
@@ -48,26 +47,28 @@ namespace SixLabors.ImageSharp
sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
- sb.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}");
+ sb.AppendFormat(" - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
}
throw new NotSupportedException(sb.ToString());
}
- source.Save(filePath, encoder);
+ source.Save(path, encoder);
}
///
/// Writes the image to the given stream using the currently loaded image format.
///
/// The source image.
- /// The file path to save the image to.
+ /// The file path to save the image to.
/// The encoder to save the image with.
- /// Thrown if the encoder is null.
- public static void Save(this Image source, string filePath, IImageEncoder encoder)
+ /// The path is null.
+ /// The encoder is null.
+ public static void Save(this Image source, string path, IImageEncoder encoder)
{
+ Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder));
- using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath))
+ using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
{
source.Save(fs, encoder);
}
@@ -79,10 +80,20 @@ namespace SixLabors.ImageSharp
/// The source image.
/// The stream to save the image to.
/// The format to save the image in.
- /// Thrown if the stream is null.
+ /// The stream is null.
+ /// The format is null.
+ /// The stream is not writable.
+ /// No encoder available for provided format.
public static void Save(this Image source, Stream stream, IImageFormat format)
{
+ Guard.NotNull(stream, nameof(stream));
Guard.NotNull(format, nameof(format));
+
+ if (!stream.CanWrite)
+ {
+ throw new NotSupportedException("Cannot write to the stream.");
+ }
+
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null)
@@ -92,7 +103,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
{
- sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}");
+ sb.AppendFormat(" - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine);
}
throw new NotSupportedException(sb.ToString());
@@ -113,9 +124,12 @@ namespace SixLabors.ImageSharp
///
/// The source image
/// The format.
+ /// The format is null.
/// The
public static string ToBase64String(this Image source, IImageFormat format)
{
+ Guard.NotNull(format, nameof(format));
+
using var stream = new MemoryStream();
source.Save(stream, format);
diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs
index bf8630931a..ada1d29b6d 100644
--- a/src/ImageSharp/Memory/Buffer2D{T}.cs
+++ b/src/ImageSharp/Memory/Buffer2D{T}.cs
@@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Memory
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
- return this.FastMemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width);
+ return this.FastMemoryGroup.View.GetBoundedSlice(y * (long)this.Width, this.Width);
}
///
@@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Memory
}
[MethodImpl(InliningOptions.ColdPath)]
- private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * this.Width, this.Width);
+ private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width);
[MethodImpl(InliningOptions.ColdPath)]
private Memory GetSingleMemorySlow() => this.FastMemoryGroup.Single();
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
index 89aca914d0..3bb6b8d336 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
@@ -33,5 +33,15 @@ namespace SixLabors.ImageSharp.Memory
/// the image buffers internally.
///
bool IsValid { get; }
+
+ ///
+ /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current
+ /// instance. The return type shouldn't be used directly: just use a block on
+ /// the instance in use and the C# compiler will automatically invoke this
+ /// method behind the scenes. This method takes precedence over the
+ /// implementation, which is still available when casting to one of the underlying interfaces.
+ ///
+ /// A new instance mapping the current values in use.
+ new MemoryGroupEnumerator GetEnumerator();
}
}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
new file mode 100644
index 0000000000..1bc44e33e1
--- /dev/null
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ ///
+ /// A value-type enumerator for instances.
+ ///
+ /// The element type.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public ref struct MemoryGroupEnumerator
+ where T : struct
+ {
+ private readonly IMemoryGroup memoryGroup;
+ private readonly int count;
+ private int index;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal MemoryGroupEnumerator(MemoryGroup.Owned memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.count = memoryGroup.Count;
+ this.index = -1;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal MemoryGroupEnumerator(MemoryGroup.Consumed memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.count = memoryGroup.Count;
+ this.index = -1;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal MemoryGroupEnumerator(MemoryGroupView memoryGroup)
+ {
+ this.memoryGroup = memoryGroup;
+ this.count = memoryGroup.Count;
+ this.index = -1;
+ }
+
+ ///
+ public Memory Current
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get => this.memoryGroup[this.index];
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool MoveNext()
+ {
+ int index = this.index + 1;
+
+ if (index < this.count)
+ {
+ this.index = index;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
index 28da49263e..295f9190a9 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -38,6 +38,12 @@ namespace SixLabors.ImageSharp.Memory
Guard.MustBeLessThan(start, group.TotalLength, nameof(start));
int bufferIdx = (int)(start / group.BufferLength);
+
+ if (bufferIdx < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(start));
+ }
+
if (bufferIdx >= group.Count)
{
throw new ArgumentOutOfRangeException(nameof(start));
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
index 3f39ba12f5..1698f08d17 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
@@ -37,6 +38,7 @@ namespace SixLabors.ImageSharp.Memory
public int Count
{
+ [MethodImpl(InliningOptions.ShortMethod)]
get
{
this.EnsureIsValid();
@@ -73,7 +75,15 @@ namespace SixLabors.ImageSharp.Memory
}
}
- public IEnumerator> GetEnumerator()
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public MemoryGroupEnumerator GetEnumerator()
+ {
+ return new MemoryGroupEnumerator(this);
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
{
this.EnsureIsValid();
for (int i = 0; i < this.Count; i++)
@@ -82,7 +92,8 @@ namespace SixLabors.ImageSharp.Memory
}
}
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
internal void Invalidate()
{
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
index f1fe4ed9c5..1dfbaea932 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
@@ -2,16 +2,17 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers;
using System.Collections.Generic;
-using System.Linq;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
internal abstract partial class MemoryGroup
{
- // Analogous to the "consumed" variant of MemorySource
- private sealed class Consumed : MemoryGroup
+ ///
+ /// A implementation that consumes the underlying memory buffers.
+ ///
+ public sealed class Consumed : MemoryGroup, IEnumerable>
{
private readonly Memory[] source;
@@ -22,16 +23,31 @@ namespace SixLabors.ImageSharp.Memory
this.View = new MemoryGroupView(this);
}
- public override int Count => this.source.Length;
+ public override int Count
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get => this.source.Length;
+ }
public override Memory this[int index] => this.source[index];
- public override IEnumerator> GetEnumerator()
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public override MemoryGroupEnumerator GetEnumerator()
+ {
+ return new MemoryGroupEnumerator(this);
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
{
- for (int i = 0; i < this.source.Length; i++)
- {
- yield return this.source[i];
- }
+ /* The runtime sees the Array class as if it implemented the
+ * type-generic collection interfaces explicitly, so here we
+ * can just cast the source array to IList> (or to
+ * an equivalent type), and invoke the generic GetEnumerator
+ * method directly from that interface reference. This saves
+ * having to create our own iterator block here. */
+ return ((IList>)this.source).GetEnumerator();
}
public override void Dispose()
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
index b42b90d286..5a86ac4268 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
@@ -5,13 +5,16 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
- // Analogous to the "owned" variant of MemorySource
internal abstract partial class MemoryGroup
{
- private sealed class Owned : MemoryGroup
+ ///
+ /// A implementation that owns the underlying memory buffers.
+ ///
+ public sealed class Owned : MemoryGroup, IEnumerable>
{
private IMemoryOwner[] memoryOwners;
@@ -29,6 +32,7 @@ namespace SixLabors.ImageSharp.Memory
public override int Count
{
+ [MethodImpl(InliningOptions.ShortMethod)]
get
{
this.EnsureNotDisposed();
@@ -45,7 +49,15 @@ namespace SixLabors.ImageSharp.Memory
}
}
- public override IEnumerator> GetEnumerator()
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public override MemoryGroupEnumerator GetEnumerator()
+ {
+ return new MemoryGroupEnumerator(this);
+ }
+
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator()
{
this.EnsureNotDisposed();
return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator();
@@ -69,14 +81,21 @@ namespace SixLabors.ImageSharp.Memory
this.IsValid = false;
}
+ [MethodImpl(InliningOptions.ShortMethod)]
private void EnsureNotDisposed()
{
- if (this.memoryOwners == null)
+ if (this.memoryOwners is null)
{
- throw new ObjectDisposedException(nameof(MemoryGroup));
+ ThrowObjectDisposedException();
}
}
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryGroup));
+ }
+
internal static void SwapContents(Owned a, Owned b)
{
a.EnsureNotDisposed();
diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
index 38de57b4ac..6fd93f12ea 100644
--- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
+++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
@@ -6,7 +6,6 @@ using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
@@ -48,10 +47,21 @@ namespace SixLabors.ImageSharp.Memory
public abstract void Dispose();
///
- public abstract IEnumerator> GetEnumerator();
+ public abstract MemoryGroupEnumerator GetEnumerator();
///
- IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
+ IEnumerator> IEnumerable>.GetEnumerator()
+ {
+ /* This method is implemented in each derived class.
+ * Implementing the method here as non-abstract and throwing,
+ * then reimplementing it explicitly in each derived class, is
+ * a workaround for the lack of support for abstract explicit
+ * interface method implementations in C#. */
+ throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()");
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
///
/// Creates a new memory group, allocating it's buffers with the provided allocator.
diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs
index b3751bfbdc..716e89e68d 100644
--- a/src/ImageSharp/Metadata/ImageMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageMetadata.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Metadata
{
@@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Metadata
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
+ this.IptcProfile = other.IptcProfile?.DeepClone();
}
///
@@ -122,6 +124,11 @@ namespace SixLabors.ImageSharp.Metadata
///
public IccProfile IccProfile { get; set; }
+ ///
+ /// Gets or sets the iptc profile.
+ ///
+ public IptcProfile IptcProfile { get; set; }
+
///
/// Gets the metadata value associated with the specified key.
///
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
index 11d0bd01b0..29c21d6113 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
@@ -57,8 +57,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// by making a copy from another EXIF profile.
///
/// The other EXIF profile, where the clone should be made from.
+ /// is null.>
private ExifProfile(ExifProfile other)
{
+ Guard.NotNull(other, nameof(other));
+
this.Parts = other.Parts;
this.thumbnailLength = other.thumbnailLength;
this.thumbnailOffset = other.thumbnailOffset;
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf
new file mode 100644
index 0000000000..b00355181c
Binary files /dev/null and b/src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf differ
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
new file mode 100644
index 0000000000..9206e43771
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
@@ -0,0 +1,298 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Text;
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
+{
+ ///
+ /// Represents an IPTC profile providing access to the collection of values.
+ ///
+ public sealed class IptcProfile : IDeepCloneable
+ {
+ private Collection values;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IptcProfile()
+ : this((byte[])null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The byte array to read the iptc profile from.
+ public IptcProfile(byte[] data)
+ {
+ this.Data = data;
+ this.Initialize();
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// by making a copy from another IPTC profile.
+ ///
+ /// The other IPTC profile, from which the clone should be made from.
+ private IptcProfile(IptcProfile other)
+ {
+ Guard.NotNull(other, nameof(other));
+
+ if (other.values != null)
+ {
+ this.values = new Collection();
+
+ foreach (IptcValue value in other.Values)
+ {
+ this.values.Add(value.DeepClone());
+ }
+ }
+
+ if (other.Data != null)
+ {
+ this.Data = new byte[other.Data.Length];
+ other.Data.AsSpan().CopyTo(this.Data);
+ }
+ }
+
+ ///
+ /// Gets the byte data of the IPTC profile.
+ ///
+ public byte[] Data { get; private set; }
+
+ ///
+ /// Gets the values of this iptc profile.
+ ///
+ public IEnumerable Values
+ {
+ get
+ {
+ this.Initialize();
+ return this.values;
+ }
+ }
+
+ ///
+ public IptcProfile DeepClone() => new IptcProfile(this);
+
+ ///
+ /// Returns all value with the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// The values found with the specified tag.
+ public List GetValues(IptcTag tag)
+ {
+ var iptcValues = new List();
+ foreach (IptcValue iptcValue in this.Values)
+ {
+ if (iptcValue.Tag == tag)
+ {
+ iptcValues.Add(iptcValue);
+ }
+ }
+
+ return iptcValues;
+ }
+
+ ///
+ /// Removes all values with the specified tag.
+ ///
+ /// The tag of the iptc value to remove.
+ /// True when the value was found and removed.
+ public bool RemoveValue(IptcTag tag)
+ {
+ this.Initialize();
+
+ bool removed = false;
+ for (int i = this.values.Count - 1; i >= 0; i--)
+ {
+ if (this.values[i].Tag == tag)
+ {
+ this.values.RemoveAt(i);
+ removed = true;
+ }
+ }
+
+ return removed;
+ }
+
+ ///
+ /// Removes values with the specified tag and value.
+ ///
+ /// The tag of the iptc value to remove.
+ /// The value of the iptc item to remove.
+ /// True when the value was found and removed.
+ public bool RemoveValue(IptcTag tag, string value)
+ {
+ this.Initialize();
+
+ bool removed = false;
+ for (int i = this.values.Count - 1; i >= 0; i--)
+ {
+ if (this.values[i].Tag == tag && this.values[i].Value.Equals(value))
+ {
+ this.values.RemoveAt(i);
+ removed = true;
+ }
+ }
+
+ return removed;
+ }
+
+ ///
+ /// Changes the encoding for all the values.
+ ///
+ /// The encoding to use when storing the bytes.
+ public void SetEncoding(Encoding encoding)
+ {
+ Guard.NotNull(encoding, nameof(encoding));
+
+ foreach (IptcValue value in this.Values)
+ {
+ value.Encoding = encoding;
+ }
+ }
+
+ ///
+ /// Sets the value for the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// The encoding to use when storing the bytes.
+ /// The value.
+ ///
+ /// Indicates if length restrictions from the specification should be followed strictly.
+ /// Defaults to true.
+ ///
+ public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true)
+ {
+ Guard.NotNull(encoding, nameof(encoding));
+ Guard.NotNull(value, nameof(value));
+
+ if (!tag.IsRepeatable())
+ {
+ foreach (IptcValue iptcValue in this.Values)
+ {
+ if (iptcValue.Tag == tag)
+ {
+ iptcValue.Strict = strict;
+ iptcValue.Encoding = encoding;
+ iptcValue.Value = value;
+ return;
+ }
+ }
+ }
+
+ this.values.Add(new IptcValue(tag, encoding, value, strict));
+ }
+
+ ///
+ /// Makes sure the datetime is formatted according to the iptc specification.
+ ///
+ /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
+ /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
+ /// two hours ahead of UTC.
+ ///
+ ///
+ /// The tag of the iptc value.
+ /// The datetime.
+ public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset)
+ {
+ if (!tag.IsDate() && !tag.IsTime())
+ {
+ throw new ArgumentException("iptc tag is not a time or date type");
+ }
+
+ var formattedDate = tag.IsDate()
+ ? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture)
+ : dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture)
+ .Replace(":", string.Empty);
+
+ this.SetValue(tag, Encoding.UTF8, formattedDate);
+ }
+
+ ///
+ /// Sets the value of the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// The value.
+ ///
+ /// Indicates if length restrictions from the specification should be followed strictly.
+ /// Defaults to true.
+ ///
+ public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
+
+ ///
+ /// Updates the data of the profile.
+ ///
+ public void UpdateData()
+ {
+ var length = 0;
+ foreach (IptcValue value in this.Values)
+ {
+ length += value.Length + 5;
+ }
+
+ this.Data = new byte[length];
+
+ int i = 0;
+ foreach (IptcValue value in this.Values)
+ {
+ this.Data[i++] = 28;
+ this.Data[i++] = 2;
+ this.Data[i++] = (byte)value.Tag;
+ this.Data[i++] = (byte)(value.Length >> 8);
+ this.Data[i++] = (byte)value.Length;
+ if (value.Length > 0)
+ {
+ Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length);
+ i += value.Length;
+ }
+ }
+ }
+
+ private void Initialize()
+ {
+ if (this.values != null)
+ {
+ return;
+ }
+
+ this.values = new Collection();
+
+ if (this.Data == null || this.Data[0] != 0x1c)
+ {
+ return;
+ }
+
+ int i = 0;
+ while (i + 4 < this.Data.Length)
+ {
+ if (this.Data[i++] != 28)
+ {
+ continue;
+ }
+
+ i++;
+
+ var tag = (IptcTag)this.Data[i++];
+
+ int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2));
+ i += 2;
+
+ var iptcData = new byte[count];
+ if ((count > 0) && (i + count <= this.Data.Length))
+ {
+ Buffer.BlockCopy(this.Data, i, iptcData, 0, count);
+ this.values.Add(new IptcValue(tag, iptcData, false));
+ }
+
+ i += count;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
new file mode 100644
index 0000000000..7258a02917
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
@@ -0,0 +1,397 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
+{
+ ///
+ /// Provides enumeration of all IPTC tags relevant for images.
+ ///
+ public enum IptcTag
+ {
+ ///
+ /// Unknown.
+ ///
+ Unknown = -1,
+
+ ///
+ /// Record version identifying the version of the Information Interchange Model.
+ /// Not repeatable. Max length is 2.
+ ///
+ RecordVersion = 0,
+
+ ///
+ /// Object type, not repeatable. Max Length is 67.
+ ///
+ ObjectType = 3,
+
+ ///
+ /// Object attribute. Max length is 68.
+ ///
+ ObjectAttribute = 4,
+
+ ///
+ /// Object Name, not repeatable. Max length is 64.
+ ///
+ Name = 5,
+
+ ///
+ /// Edit status, not repeatable. Max length is 64.
+ ///
+ EditStatus = 7,
+
+ ///
+ /// Editorial update, not repeatable. Max length is 2.
+ ///
+ EditorialUpdate = 8,
+
+ ///
+ /// Urgency, not repeatable. Max length is 2.
+ ///
+ Urgency = 10,
+
+ ///
+ /// Subject Reference. Max length is 236.
+ ///
+ SubjectReference = 12,
+
+ ///
+ /// Category, not repeatable. Max length is 3.
+ ///
+ Category = 15,
+
+ ///
+ /// Supplemental categories. Max length is 32.
+ ///
+ SupplementalCategories = 20,
+
+ ///
+ /// Fixture identifier, not repeatable. Max length is 32.
+ ///
+ FixtureIdentifier = 22,
+
+ ///
+ /// Keywords. Max length is 64.
+ ///
+ Keywords = 25,
+
+ ///
+ /// Location code. Max length is 3.
+ ///
+ LocationCode = 26,
+
+ ///
+ /// Location name. Max length is 64.
+ ///
+ LocationName = 27,
+
+ ///
+ /// Release date. Format should be CCYYMMDD.
+ /// Not repeatable, max length is 8.
+ ///
+ /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
+ ///
+ ///
+ ReleaseDate = 30,
+
+ ///
+ /// Release time. Format should be HHMMSS±HHMM.
+ /// Not repeatable, max length is 11.
+ ///
+ /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
+ /// two hours ahead of UTC.
+ ///
+ ///
+ ReleaseTime = 35,
+
+ ///
+ /// Expiration date. Format should be CCYYMMDD.
+ /// Not repeatable, max length is 8.
+ ///
+ /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
+ ///
+ ///
+ ExpirationDate = 37,
+
+ ///
+ /// Expiration time. Format should be HHMMSS±HHMM.
+ /// Not repeatable, max length is 11.
+ ///
+ /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
+ /// two hours ahead of UTC.
+ ///
+ ///
+ ExpirationTime = 38,
+
+ ///
+ /// Special instructions, not repeatable. Max length is 256.
+ ///
+ SpecialInstructions = 40,
+
+ ///
+ /// Action advised, not repeatable. Max length is 2.
+ ///
+ ActionAdvised = 42,
+
+ ///
+ /// Reference service. Max length is 10.
+ ///
+ ReferenceService = 45,
+
+ ///
+ /// Reference date. Format should be CCYYMMDD.
+ /// Not repeatable, max length is 8.
+ ///
+ /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
+ ///
+ ///
+ ReferenceDate = 47,
+
+ ///
+ /// ReferenceNumber. Max length is 8.
+ ///
+ ReferenceNumber = 50,
+
+ ///
+ /// Created date. Format should be CCYYMMDD.
+ /// Not repeatable, max length is 8.
+ ///
+ /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
+ ///
+ ///
+ CreatedDate = 55,
+
+ ///
+ /// Created time. Format should be HHMMSS±HHMM.
+ /// Not repeatable, max length is 11.
+ ///
+ /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
+ /// two hours ahead of UTC.
+ ///
+ ///
+ CreatedTime = 60,
+
+ ///
+ /// Digital creation date. Format should be CCYYMMDD.
+ /// Not repeatable, max length is 8.
+ ///
+ /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
+ ///
+ ///
+ DigitalCreationDate = 62,
+
+ ///
+ /// Digital creation time. Format should be HHMMSS±HHMM.
+ /// Not repeatable, max length is 11.
+ ///
+ /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
+ /// two hours ahead of UTC.
+ ///
+ ///
+ DigitalCreationTime = 63,
+
+ ///
+ /// Originating program, not repeatable. Max length is 32.
+ ///
+ OriginatingProgram = 65,
+
+ ///
+ /// Program version, not repeatable. Max length is 10.
+ ///
+ ProgramVersion = 70,
+
+ ///
+ /// Object cycle, not repeatable. Max length is 1.
+ ///
+ ObjectCycle = 75,
+
+ ///
+ /// Byline. Max length is 32.
+ ///
+ Byline = 80,
+
+ ///
+ /// Byline title. Max length is 32.
+ ///
+ BylineTitle = 85,
+
+ ///
+ /// City, not repeatable. Max length is 32.
+ ///
+ City = 90,
+
+ ///
+ /// Sub location, not repeatable. Max length is 32.
+ ///
+ SubLocation = 92,
+
+ ///
+ /// Province/State, not repeatable. Max length is 32.
+ ///
+ ProvinceState = 95,
+
+ ///
+ /// Country code, not repeatable. Max length is 3.
+ ///
+ CountryCode = 100,
+
+ ///
+ /// Country, not repeatable. Max length is 64.
+ ///
+ Country = 101,
+
+ ///
+ /// Original transmission reference, not repeatable. Max length is 32.
+ ///
+ OriginalTransmissionReference = 103,
+
+ ///
+ /// Headline, not repeatable. Max length is 256.
+ ///
+ Headline = 105,
+
+ ///
+ /// Credit, not repeatable. Max length is 32.
+ ///
+ Credit = 110,
+
+ ///
+ /// Source, not repeatable. Max length is 32.
+ ///
+ Source = 115,
+
+ ///
+ /// Copyright notice, not repeatable. Max length is 128.
+ ///
+ CopyrightNotice = 116,
+
+ ///
+ /// Contact. Max length 128.
+ ///
+ Contact = 118,
+
+ ///
+ /// Caption, not repeatable. Max length is 2000.
+ ///
+ Caption = 120,
+
+ ///
+ /// Local caption.
+ ///
+ LocalCaption = 121,
+
+ ///
+ /// Caption writer. Max length is 32.
+ ///
+ CaptionWriter = 122,
+
+ ///
+ /// Image type, not repeatable. Max length is 2.
+ ///
+ ImageType = 130,
+
+ ///
+ /// Image orientation, not repeatable. Max length is 1.
+ ///
+ ImageOrientation = 131,
+
+ ///
+ /// Custom field 1
+ ///
+ CustomField1 = 200,
+
+ ///
+ /// Custom field 2
+ ///
+ CustomField2 = 201,
+
+ ///
+ /// Custom field 3
+ ///
+ CustomField3 = 202,
+
+ ///
+ /// Custom field 4
+ ///
+ CustomField4 = 203,
+
+ ///
+ /// Custom field 5
+ ///
+ CustomField5 = 204,
+
+ ///
+ /// Custom field 6
+ ///
+ CustomField6 = 205,
+
+ ///
+ /// Custom field 7
+ ///
+ CustomField7 = 206,
+
+ ///
+ /// Custom field 8
+ ///
+ CustomField8 = 207,
+
+ ///
+ /// Custom field 9
+ ///
+ CustomField9 = 208,
+
+ ///
+ /// Custom field 10
+ ///
+ CustomField10 = 209,
+
+ ///
+ /// Custom field 11
+ ///
+ CustomField11 = 210,
+
+ ///
+ /// Custom field 12
+ ///
+ CustomField12 = 211,
+
+ ///
+ /// Custom field 13
+ ///
+ CustomField13 = 212,
+
+ ///
+ /// Custom field 14
+ ///
+ CustomField14 = 213,
+
+ ///
+ /// Custom field 15
+ ///
+ CustomField15 = 214,
+
+ ///
+ /// Custom field 16
+ ///
+ CustomField16 = 215,
+
+ ///
+ /// Custom field 17
+ ///
+ CustomField17 = 216,
+
+ ///
+ /// Custom field 18
+ ///
+ CustomField18 = 217,
+
+ ///
+ /// Custom field 19
+ ///
+ CustomField19 = 218,
+
+ ///
+ /// Custom field 20
+ ///
+ CustomField20 = 219,
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
new file mode 100644
index 0000000000..6b39769a7f
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
@@ -0,0 +1,162 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
+{
+ ///
+ /// Extension methods for IPTC tags.
+ ///
+ public static class IptcTagExtensions
+ {
+ ///
+ /// Maximum length of the IPTC value with the given tag according to the specification.
+ ///
+ /// The tag to check the max length for.
+ /// The maximum length.
+ public static int MaxLength(this IptcTag tag)
+ {
+ return tag switch
+ {
+ IptcTag.RecordVersion => 2,
+ IptcTag.ObjectType => 67,
+ IptcTag.ObjectAttribute => 68,
+ IptcTag.Name => 64,
+ IptcTag.EditStatus => 64,
+ IptcTag.EditorialUpdate => 2,
+ IptcTag.Urgency => 1,
+ IptcTag.SubjectReference => 236,
+ IptcTag.Category => 3,
+ IptcTag.SupplementalCategories => 32,
+ IptcTag.FixtureIdentifier => 32,
+ IptcTag.Keywords => 64,
+ IptcTag.LocationCode => 3,
+ IptcTag.LocationName => 64,
+ IptcTag.ReleaseDate => 8,
+ IptcTag.ReleaseTime => 11,
+ IptcTag.ExpirationDate => 8,
+ IptcTag.ExpirationTime => 11,
+ IptcTag.SpecialInstructions => 256,
+ IptcTag.ActionAdvised => 2,
+ IptcTag.ReferenceService => 10,
+ IptcTag.ReferenceDate => 8,
+ IptcTag.ReferenceNumber => 8,
+ IptcTag.CreatedDate => 8,
+ IptcTag.CreatedTime => 11,
+ IptcTag.DigitalCreationDate => 8,
+ IptcTag.DigitalCreationTime => 11,
+ IptcTag.OriginatingProgram => 32,
+ IptcTag.ProgramVersion => 10,
+ IptcTag.ObjectCycle => 1,
+ IptcTag.Byline => 32,
+ IptcTag.BylineTitle => 32,
+ IptcTag.City => 32,
+ IptcTag.SubLocation => 32,
+ IptcTag.ProvinceState => 32,
+ IptcTag.CountryCode => 3,
+ IptcTag.Country => 64,
+ IptcTag.OriginalTransmissionReference => 32,
+ IptcTag.Headline => 256,
+ IptcTag.Credit => 32,
+ IptcTag.Source => 32,
+ IptcTag.CopyrightNotice => 128,
+ IptcTag.Contact => 128,
+ IptcTag.Caption => 2000,
+ IptcTag.CaptionWriter => 32,
+ IptcTag.ImageType => 2,
+ IptcTag.ImageOrientation => 1,
+ _ => 256
+ };
+ }
+
+ ///
+ /// Determines if the given tag can be repeated according to the specification.
+ ///
+ /// The tag to check.
+ /// True, if the tag can occur multiple times.
+ public static bool IsRepeatable(this IptcTag tag)
+ {
+ switch (tag)
+ {
+ case IptcTag.RecordVersion:
+ case IptcTag.ObjectType:
+ case IptcTag.Name:
+ case IptcTag.EditStatus:
+ case IptcTag.EditorialUpdate:
+ case IptcTag.Urgency:
+ case IptcTag.Category:
+ case IptcTag.FixtureIdentifier:
+ case IptcTag.ReleaseDate:
+ case IptcTag.ReleaseTime:
+ case IptcTag.ExpirationDate:
+ case IptcTag.ExpirationTime:
+ case IptcTag.SpecialInstructions:
+ case IptcTag.ActionAdvised:
+ case IptcTag.CreatedDate:
+ case IptcTag.CreatedTime:
+ case IptcTag.DigitalCreationDate:
+ case IptcTag.DigitalCreationTime:
+ case IptcTag.OriginatingProgram:
+ case IptcTag.ProgramVersion:
+ case IptcTag.ObjectCycle:
+ case IptcTag.City:
+ case IptcTag.SubLocation:
+ case IptcTag.ProvinceState:
+ case IptcTag.CountryCode:
+ case IptcTag.Country:
+ case IptcTag.OriginalTransmissionReference:
+ case IptcTag.Headline:
+ case IptcTag.Credit:
+ case IptcTag.Source:
+ case IptcTag.CopyrightNotice:
+ case IptcTag.Caption:
+ case IptcTag.ImageType:
+ case IptcTag.ImageOrientation:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ ///
+ /// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD.
+ ///
+ /// The tag to check.
+ /// True, if its a datetime tag.
+ public static bool IsDate(this IptcTag tag)
+ {
+ switch (tag)
+ {
+ case IptcTag.CreatedDate:
+ case IptcTag.DigitalCreationDate:
+ case IptcTag.ExpirationDate:
+ case IptcTag.ReferenceDate:
+ case IptcTag.ReleaseDate:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM.
+ ///
+ /// The tag to check.
+ /// True, if its a time tag.
+ public static bool IsTime(this IptcTag tag)
+ {
+ switch (tag)
+ {
+ case IptcTag.CreatedTime:
+ case IptcTag.DigitalCreationTime:
+ case IptcTag.ExpirationTime:
+ case IptcTag.ReleaseTime:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
new file mode 100644
index 0000000000..e63781012a
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
@@ -0,0 +1,219 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Text;
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
+{
+ ///
+ /// Represents a single value of the IPTC profile.
+ ///
+ public sealed class IptcValue : IDeepCloneable
+ {
+ private byte[] data = Array.Empty();
+ private Encoding encoding;
+
+ internal IptcValue(IptcValue other)
+ {
+ if (other.data != null)
+ {
+ this.data = new byte[other.data.Length];
+ other.data.AsSpan().CopyTo(this.data);
+ }
+
+ if (other.Encoding != null)
+ {
+ this.Encoding = (Encoding)other.Encoding.Clone();
+ }
+
+ this.Tag = other.Tag;
+ this.Strict = other.Strict;
+ }
+
+ internal IptcValue(IptcTag tag, byte[] value, bool strict)
+ {
+ Guard.NotNull(value, nameof(value));
+
+ this.Strict = strict;
+ this.Tag = tag;
+ this.data = value;
+ this.encoding = Encoding.UTF8;
+ }
+
+ internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict)
+ {
+ this.Strict = strict;
+ this.Tag = tag;
+ this.encoding = encoding;
+ this.Value = value;
+ }
+
+ internal IptcValue(IptcTag tag, string value, bool strict)
+ {
+ this.Strict = strict;
+ this.Tag = tag;
+ this.encoding = Encoding.UTF8;
+ this.Value = value;
+ }
+
+ ///
+ /// Gets or sets the encoding to use for the Value.
+ ///
+ public Encoding Encoding
+ {
+ get => this.encoding;
+ set
+ {
+ if (value != null)
+ {
+ this.encoding = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the tag of the iptc value.
+ ///
+ public IptcTag Tag { get; }
+
+ ///
+ /// Gets or sets a value indicating whether to be enforce value length restrictions according
+ /// to the specification.
+ ///
+ public bool Strict { get; set; }
+
+ ///
+ /// Gets or sets the value.
+ ///
+ public string Value
+ {
+ get => this.encoding.GetString(this.data);
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ this.data = Array.Empty();
+ }
+ else
+ {
+ int maxLength = this.Tag.MaxLength();
+ byte[] valueBytes;
+ if (this.Strict && value.Length > maxLength)
+ {
+ var cappedValue = value.Substring(0, maxLength);
+ valueBytes = this.encoding.GetBytes(cappedValue);
+
+ // It is still possible that the bytes of the string exceed the limit.
+ if (valueBytes.Length > maxLength)
+ {
+ throw new ArgumentException($"The iptc value exceeds the limit of {maxLength} bytes for the tag {this.Tag}");
+ }
+ }
+ else
+ {
+ valueBytes = this.encoding.GetBytes(value);
+ }
+
+ this.data = valueBytes;
+ }
+ }
+ }
+
+ ///
+ /// Gets the length of the value.
+ ///
+ public int Length => this.data.Length;
+
+ ///
+ public IptcValue DeepClone() => new IptcValue(this);
+
+ ///
+ /// Determines whether the specified object is equal to the current .
+ ///
+ /// The object to compare this with.
+ /// True when the specified object is equal to the current .
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ return this.Equals(obj as IptcValue);
+ }
+
+ ///
+ /// Determines whether the specified iptc value is equal to the current .
+ ///
+ /// The iptc value to compare this with.
+ /// True when the specified iptc value is equal to the current .
+ public bool Equals(IptcValue other)
+ {
+ if (other is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ if (this.Tag != other.Tag)
+ {
+ return false;
+ }
+
+ if (this.data.Length != other.data.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.data.Length; i++)
+ {
+ if (this.data[i] != other.data[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Serves as a hash of this type.
+ ///
+ /// A hash code for the current instance.
+ public override int GetHashCode() => HashCode.Combine(this.data, this.Tag);
+
+ ///
+ /// Converts this instance to a byte array.
+ ///
+ /// A array.
+ public byte[] ToByteArray()
+ {
+ var result = new byte[this.data.Length];
+ this.data.CopyTo(result, 0);
+ return result;
+ }
+
+ ///
+ /// Returns a string that represents the current value.
+ ///
+ /// A string that represents the current value.
+ public override string ToString() => this.Value;
+
+ ///
+ /// Returns a string that represents the current value with the specified encoding.
+ ///
+ /// The encoding to use.
+ /// A string that represents the current value with the specified encoding.
+ public string ToString(Encoding encoding)
+ {
+ Guard.NotNull(encoding, nameof(encoding));
+
+ return encoding.GetString(this.data);
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/README.md b/src/ImageSharp/Metadata/Profiles/IPTC/README.md
new file mode 100644
index 0000000000..1217ca0c70
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/README.md
@@ -0,0 +1,11 @@
+IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET)
+
+Information about IPTC can be found here in the following sources:
+
+- [metacpan.org, APP13-segment](https://metacpan.org/pod/Image::MetaData::JPEG::Structures#Structure-of-a-Photoshop-style-APP13-segment)
+
+- [iptc.org](https://www.iptc.org/std/photometadata/documentation/userguide/)
+
+- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf)
+
+- [Tag Overview](https://exiftool.org/TagNames/IPTC.html)
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
index 59be5bf02c..45cff93982 100644
--- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
@@ -19,6 +19,10 @@ namespace SixLabors.ImageSharp.Processing
///
/// The image to mutate.
/// The operation to perform on the source.
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static void Mutate(this Image source, Action operation)
=> Mutate(source, source.GetConfiguration(), operation);
@@ -28,6 +32,11 @@ namespace SixLabors.ImageSharp.Processing
/// The image to mutate.
/// The configuration which allows altering default behaviour or extending the library.
/// The operation to perform on the source.
+ /// The configuration is null.
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static void Mutate(this Image source, Configuration configuration, Action operation)
{
Guard.NotNull(configuration, nameof(configuration));
@@ -44,6 +53,10 @@ namespace SixLabors.ImageSharp.Processing
/// The pixel format.
/// The image to mutate.
/// The operation to perform on the source.
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static void Mutate(this Image source, Action operation)
where TPixel : unmanaged, IPixel
=> Mutate(source, source.GetConfiguration(), operation);
@@ -55,6 +68,11 @@ namespace SixLabors.ImageSharp.Processing
/// The image to mutate.
/// The configuration which allows altering default behaviour or extending the library.
/// The operation to perform on the source.
+ /// The configuration is null.
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static void Mutate(this Image source, Configuration configuration, Action operation)
where TPixel : unmanaged, IPixel
{
@@ -75,6 +93,10 @@ namespace SixLabors.ImageSharp.Processing
/// The pixel format.
/// The image to mutate.
/// The operations to perform on the source.
+ /// The source is null.
+ /// The operations are null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static void Mutate(this Image source, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel
=> Mutate(source, source.GetConfiguration(), operations);
@@ -86,6 +108,11 @@ namespace SixLabors.ImageSharp.Processing
/// The image to mutate.
/// The configuration which allows altering default behaviour or extending the library.
/// The operations to perform on the source.
+ /// The configuration is null.
+ /// The source is null.
+ /// The operations are null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static void Mutate(this Image source, Configuration configuration, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel
{
@@ -104,7 +131,11 @@ namespace SixLabors.ImageSharp.Processing
///
/// The image to clone.
/// The operation to perform on the clone.
- /// The new .
+ /// The new .
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
public static Image Clone(this Image source, Action operation)
=> Clone(source, source.GetConfiguration(), operation);
@@ -114,7 +145,12 @@ namespace SixLabors.ImageSharp.Processing
/// The image to clone.
/// The configuration which allows altering default behaviour or extending the library.
/// The operation to perform on the clone.
- /// The new .
+ /// The configuration is null.
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
+ /// The new .
public static Image Clone(this Image source, Configuration configuration, Action operation)
{
Guard.NotNull(configuration, nameof(configuration));
@@ -133,7 +169,11 @@ namespace SixLabors.ImageSharp.Processing
/// The pixel format.
/// The image to clone.
/// The operation to perform on the clone.
- /// The new
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
+ /// The new .
public static Image Clone(this Image source, Action operation)
where TPixel : unmanaged, IPixel
=> Clone(source, source.GetConfiguration(), operation);
@@ -145,7 +185,12 @@ namespace SixLabors.ImageSharp.Processing
/// The image to clone.
/// The configuration which allows altering default behaviour or extending the library.
/// The operation to perform on the clone.
- /// The new
+ /// The configuration is null.
+ /// The source is null.
+ /// The operation is null.
+ /// The source has been disposed.
+ /// The processing operation failed.
+ /// The new
public static Image Clone(this Image source, Configuration configuration, Action operation)
where TPixel : unmanaged, IPixel
{
@@ -167,7 +212,11 @@ namespace SixLabors.ImageSharp.Processing
/// The pixel format.
/// The image to clone.
/// The operations to perform on the clone.
- /// The new
+ /// The source is null.
+ /// The operations are null.
+ /// The source has been disposed.
+ /// The processing operation failed.
+ /// The new
public static Image Clone(this Image source, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel
=> Clone(source, source.GetConfiguration(), operations);
@@ -179,7 +228,12 @@ namespace SixLabors.ImageSharp.Processing
/// The image to clone.
/// The configuration which allows altering default behaviour or extending the library.
/// The operations to perform on the clone.
- /// The new
+ /// The configuration is null.
+ /// The source is null.
+ /// The operations are null.
+ /// The source has been disposed.
+ /// The processing operation failed.
+ /// The new
public static Image Clone(this Image source, Configuration configuration, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel
{
@@ -200,6 +254,7 @@ namespace SixLabors.ImageSharp.Processing
///
/// The image processing context.
/// The operations to perform on the source.
+ /// The processing operation failed.
/// The to allow chaining of operations.
public static IImageProcessingContext ApplyProcessors(
this IImageProcessingContext source,
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index a8376f51b9..85cdf6d11a 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
@@ -394,10 +394,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[Theory]
[WithFile(InvalidPaletteSize, PixelTypes.Rgba32)]
- public void BmpDecoder_ThrowsImageFormatException_OnInvalidPaletteSize(TestImageProvider provider)
+ public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
using (provider.GetImage(BmpDecoder))
{
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
index cf2e5c81b3..57051a9d7b 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
@@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)]
- public void UnrecoverableImage_Throws_ImageFormatException(TestImageProvider provider)
- where TPixel : unmanaged, IPixel => Assert.Throws(provider.GetImage);
+ public void UnrecoverableImage_Throws_InvalidImageContentException(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel => Assert.Throws(provider.GetImage);
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
index ee4001c203..297512fa6a 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
@@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
ImageFormatException exception =
- Assert.Throws(() => decoder.Decode(null, memStream));
+ Assert.Throws(() => decoder.Decode(null, memStream));
Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message);
}
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 46112bdd81..b08025f532 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -89,6 +89,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6
};
+ public static readonly string[] TestImagesIssue1177 =
+ {
+ TestImages.Png.Issue1177_1,
+ TestImages.Png.Issue1177_2
+ };
+
[Theory]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
public void Decode(TestImageProvider provider)
@@ -243,6 +249,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Null(ex);
}
+ [Theory]
+ [WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)]
+ public void Issue1177(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ System.Exception ex = Record.Exception(
+ () =>
+ {
+ using (Image image = provider.GetImage(PngDecoder))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, ImageComparer.Exact);
+ }
+ });
+ Assert.Null(ex);
+ }
+
[Theory]
[WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)]
public void Issue1127(TestImageProvider provider)
diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
index 694c4d32f6..15b07265f6 100644
--- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
+++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
@@ -1,8 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@@ -150,6 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
public static TheoryData GetBoundedSlice_ErrorData = new TheoryData()
{
+ { 300, 100, -1, 91 },
{ 300, 100, 110, 91 },
{ 42, 7, 0, 8 },
{ 42, 7, 1, 7 },
@@ -165,7 +168,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
}
[Fact]
- public void Fill()
+ public void FillWithFastEnumerator()
{
using MemoryGroup group = this.CreateTestGroup(100, 10, true);
group.Fill(42);
@@ -177,6 +180,34 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
}
}
+ [Fact]
+ public void FillWithSlowGenericEnumerator()
+ {
+ using MemoryGroup group = this.CreateTestGroup(100, 10, true);
+ group.Fill(42);
+
+ int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
+ IReadOnlyList> groupAsList = group;
+ foreach (Memory memory in groupAsList)
+ {
+ Assert.True(memory.Span.SequenceEqual(expectedRow));
+ }
+ }
+
+ [Fact]
+ public void FillWithSlowEnumerator()
+ {
+ using MemoryGroup group = this.CreateTestGroup(100, 10, true);
+ group.Fill(42);
+
+ int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
+ IEnumerable groupAsList = group;
+ foreach (Memory memory in groupAsList)
+ {
+ Assert.True(memory.Span.SequenceEqual(expectedRow));
+ }
+ }
+
[Fact]
public void Clear()
{
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
new file mode 100644
index 0000000000..d9f44cef9c
--- /dev/null
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
@@ -0,0 +1,359 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using SixLabors.ImageSharp.Formats.Jpeg;
+using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
+using SixLabors.ImageSharp.PixelFormats;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
+{
+ public class IptcProfileTests
+ {
+ private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false };
+
+ public static IEnumerable