diff --git a/Directory.Build.props b/Directory.Build.props
index 12a4a5c2a3..50c09fbb3c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -31,21 +31,21 @@
- $(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE
+ $(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE;SUPPORTS_HOTPATH
$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_CODECOVERAGE
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/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs
index 069a426d75..895b6250f6 100644
--- a/src/ImageSharp/Common/Helpers/InliningOptions.cs
+++ b/src/ImageSharp/Common/Helpers/InliningOptions.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.
// Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN!
@@ -13,10 +13,16 @@ namespace SixLabors.ImageSharp
internal static class InliningOptions
{
#if PROFILING
+ public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;
#else
+#if SUPPORTS_HOTPATH
+ public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization;
+#else
+ public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining;
+#endif
public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining;
#endif
public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining;
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 47c7c54ea0..17ac8c3fd8 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Net.Http;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
@@ -73,6 +74,12 @@ namespace SixLabors.ImageSharp
}
}
+ ///
+ /// Gets a set of properties for the Congiguration.
+ ///
+ /// This can be used for storing global settings and defaults to be accessable to processors.
+ public IDictionary Properties { get; } = new Dictionary();
+
///
/// Gets the currently registered s.
///
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 102e80b0a7..2db1fa2c95 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -13,6 +13,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
@@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private readonly byte[] markerBuffer = new byte[2];
///
- /// The DC Huffman tables
+ /// The DC Huffman tables.
///
private HuffmanTable[] dcHuffmanTables;
@@ -55,37 +56,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;
@@ -207,6 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
+ this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage();
}
@@ -220,6 +232,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);
@@ -239,7 +252,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.");
}
stream.Read(this.markerBuffer, 0, 2);
@@ -337,10 +350,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
- case JpegConstants.Markers.APP13:
stream.Skip(remaining);
break;
+ case JpegConstants.Markers.APP13:
+ this.ProcessApp13Marker(stream, remaining);
+ break;
+
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(stream, remaining);
break;
@@ -398,7 +414,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;
}
@@ -428,6 +444,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.
///
@@ -576,6 +604,96 @@ 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 input stream.
+ /// The remaining bytes in the segment block.
+ private void ProcessApp13Marker(Stream stream, int remaining)
+ {
+ if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata)
+ {
+ stream.Skip(remaining);
+ return;
+ }
+
+ stream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length);
+ remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
+ if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
+ {
+ var resourceBlockData = new byte[remaining];
+ stream.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.
@@ -701,7 +819,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.
@@ -711,7 +829,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];
@@ -809,13 +927,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.");
}
stream.Read(huffmanData.Array, 0, 16);
@@ -834,7 +952,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 +996,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 = stream.ReadByte();
@@ -899,7 +1017,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 4d7de4161b..272f93d108 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -380,7 +380,12 @@ namespace SixLabors.ImageSharp.Formats.Png
private void InitializeImage(ImageMetadata metadata, out Image image)
where TPixel : unmanaged, IPixel
{
- image = new Image(this.configuration, this.header.Width, this.header.Height, metadata);
+ image = Image.CreateUninitialized(
+ this.configuration,
+ this.header.Width,
+ this.header.Height,
+ metadata);
+
this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
this.bytesPerSample = 1;
@@ -1136,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/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 45e1ffd2d7..fcbbc66974 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -5,10 +5,8 @@ using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
-using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
@@ -16,7 +14,6 @@ using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png
{
@@ -633,10 +630,21 @@ namespace SixLabors.ImageSharp.Formats.Png
private void WriteTextChunks(Stream stream, PngMetadata meta)
{
const int MaxLatinCode = 255;
- foreach (PngTextData textData in meta.TextData)
+ for (int i = 0; i < meta.TextData.Count; i++)
{
- bool hasUnicodeCharacters = textData.Value.Any(c => c > MaxLatinCode);
- if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
+ PngTextData textData = meta.TextData[i];
+ bool hasUnicodeCharacters = false;
+ foreach (var c in textData.Value)
+ {
+ if (c > MaxLatinCode)
+ {
+ hasUnicodeCharacters = true;
+ break;
+ }
+ }
+
+ if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) ||
+ !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)))
{
// Write iTXt chunk.
byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword);
@@ -647,7 +655,8 @@ namespace SixLabors.ImageSharp.Formats.Png
byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword);
byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag);
- Span outputBytes = new byte[keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5];
+ Span outputBytes = new byte[keywordBytes.Length + textBytes.Length +
+ translatedKeyword.Length + languageTag.Length + 5];
keywordBytes.CopyTo(outputBytes);
if (textData.Value.Length > this.options.TextCompressionThreshold)
{
@@ -667,7 +676,8 @@ namespace SixLabors.ImageSharp.Formats.Png
if (textData.Value.Length > this.options.TextCompressionThreshold)
{
// Write zTXt chunk.
- byte[] compressedData = this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value));
+ byte[] compressedData =
+ this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value));
Span outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2];
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2));
@@ -678,7 +688,8 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write tEXt chunk.
Span outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1];
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
- PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes.Slice(textData.Keyword.Length + 1));
+ PngConstants.Encoding.GetBytes(textData.Value)
+ .CopyTo(outputBytes.Slice(textData.Keyword.Length + 1));
this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray());
}
}
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/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
index 7398b089bb..2083edab1c 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
@@ -288,8 +288,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.engine = null;
this.isDisposed = true;
}
-
- GC.SuppressFinalize(this);
}
}
}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
index 1a8bb4ab04..c1c86a98be 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
@@ -362,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
more = this.inputEnd - this.inputOff;
}
- Array.Copy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
+ Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
this.inputOff += more;
this.lookahead += more;
@@ -397,8 +397,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true;
}
-
- GC.SuppressFinalize(this);
}
[MethodImpl(InliningOptions.ShortMethod)]
@@ -464,6 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
///
/// The current match.
/// True if a match greater than the minimum length is found
+ [MethodImpl(InliningOptions.HotPath)]
private bool FindLongestMatch(int curMatch)
{
int match;
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
index 543a1fe302..022d3d4d0d 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
@@ -36,11 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private const int EofSymbol = 256;
- private static readonly short[] StaticLCodes;
- private static readonly byte[] StaticLLength;
- private static readonly short[] StaticDCodes;
- private static readonly byte[] StaticDLength;
-
private Tree literalTree;
private Tree distTree;
private Tree blTree;
@@ -58,49 +53,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private int extraBits;
private bool isDisposed;
- // TODO: These should be pre-generated array/readonlyspans.
- static DeflaterHuffman()
- {
- // See RFC 1951 3.2.6
- // Literal codes
- StaticLCodes = new short[LiteralNumber];
- StaticLLength = new byte[LiteralNumber];
-
- int i = 0;
- while (i < 144)
- {
- StaticLCodes[i] = BitReverse((0x030 + i) << 8);
- StaticLLength[i++] = 8;
- }
-
- while (i < 256)
- {
- StaticLCodes[i] = BitReverse((0x190 - 144 + i) << 7);
- StaticLLength[i++] = 9;
- }
-
- while (i < 280)
- {
- StaticLCodes[i] = BitReverse((0x000 - 256 + i) << 9);
- StaticLLength[i++] = 7;
- }
-
- while (i < LiteralNumber)
- {
- StaticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8);
- StaticLLength[i++] = 8;
- }
-
- // Distance codes
- StaticDCodes = new short[DistanceNumber];
- StaticDLength = new byte[DistanceNumber];
- for (i = 0; i < DistanceNumber; i++)
- {
- StaticDCodes[i] = BitReverse(i << 11);
- StaticDLength[i] = 5;
- }
- }
-
///
/// Initializes a new instance of the class.
///
@@ -122,12 +74,80 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
}
+#pragma warning disable SA1201 // Elements should appear in the correct order
+
+ // See RFC 1951 3.2.6
+ // Literal codes
+ private static readonly short[] StaticLCodes = new short[]
+ {
+ 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
+ 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
+ 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
+ 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
+ 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
+ 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9,
+ 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5,
+ 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13,
+ 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19,
+ 275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499,
+ 11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491,
+ 27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507,
+ 7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487,
+ 23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503,
+ 15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495,
+ 31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511,
+ 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36,
+ 100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163
+ };
+
+ private static ReadOnlySpan StaticLLength => new byte[]
+ {
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8
+ };
+
+ // Distance codes and lengths.
+ private static readonly short[] StaticDCodes = new short[]
+ {
+ 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14,
+ 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23
+ };
+
+ private static ReadOnlySpan StaticDLength => new byte[]
+ {
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+ };
+#pragma warning restore SA1201 // Elements should appear in the correct order
+
///
/// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes.
///
- private static ReadOnlySpan BitLengthOrder => new byte[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+ private static ReadOnlySpan BitLengthOrder => new byte[]
+ {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+ };
- private static ReadOnlySpan Bit4Reverse => new byte[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
+ private static ReadOnlySpan Bit4Reverse => new byte[]
+ {
+ 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
+ };
///
/// Gets the pending buffer to use.
@@ -413,8 +433,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.distTree = null;
this.isDisposed = true;
}
-
- GC.SuppressFinalize(this);
}
[MethodImpl(InliningOptions.ShortMethod)]
@@ -553,6 +571,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
}
+ [MethodImpl(InliningOptions.HotPath)]
public void BuildTree()
{
int numSymbols = this.elementCount;
@@ -964,8 +983,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true;
}
-
- GC.SuppressFinalize(this);
}
}
}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
index 731c9e80f0..0414ca2f87 100644
--- a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
@@ -172,8 +172,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.bufferMemoryOwner = null;
this.isDisposed = true;
}
-
- GC.SuppressFinalize(this);
}
}
}
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/GraphicOptionsDefaultsExtensions.cs b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs
new file mode 100644
index 0000000000..c8beea8e85
--- /dev/null
+++ b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using SixLabors.ImageSharp.Processing;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Adds extensions that allow the processing of images to the type.
+ ///
+ public static class GraphicOptionsDefaultsExtensions
+ {
+ ///
+ /// Sets the default options against the image processing context.
+ ///
+ /// The image processing context to store default against.
+ /// The action to update instance of the default options used.
+ /// The passed in to allow chaining.
+ public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, Action optionsBuilder)
+ {
+ var cloned = context.GetGraphicsOptions().DeepClone();
+ optionsBuilder(cloned);
+ context.Properties[typeof(GraphicsOptions)] = cloned;
+ return context;
+ }
+
+ ///
+ /// Sets the default options against the configuration.
+ ///
+ /// The configuration to store default against.
+ /// The default options to use.
+ public static void SetGraphicsOptions(this Configuration configuration, Action optionsBuilder)
+ {
+ var cloned = configuration.GetGraphicsOptions().DeepClone();
+ optionsBuilder(cloned);
+ configuration.Properties[typeof(GraphicsOptions)] = cloned;
+ }
+
+ ///
+ /// Sets the default options against the image processing context.
+ ///
+ /// The image processing context to store default against.
+ /// The default options to use.
+ /// The passed in to allow chaining.
+ public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, GraphicsOptions options)
+ {
+ context.Properties[typeof(GraphicsOptions)] = options;
+ return context;
+ }
+
+ ///
+ /// Sets the default options against the configuration.
+ ///
+ /// The configuration to store default against.
+ /// The default options to use.
+ public static void SetGraphicsOptions(this Configuration configuration, GraphicsOptions options)
+ {
+ configuration.Properties[typeof(GraphicsOptions)] = options;
+ }
+
+ ///
+ /// Gets the default options against the image processing context.
+ ///
+ /// The image processing context to retrieve defaults from.
+ /// The globaly configued default options.
+ public static GraphicsOptions GetGraphicsOptions(this IImageProcessingContext context)
+ {
+ if (context.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go)
+ {
+ return go;
+ }
+
+ var configOptions = context.Configuration.GetGraphicsOptions();
+
+ // do not cache the fall back to config into the the processing context
+ // in case someone want to change the value on the config and expects it re trflow thru
+ return configOptions;
+ }
+
+ ///
+ /// Gets the default options against the image processing context.
+ ///
+ /// The configuration to retrieve defaults from.
+ /// The globaly configued default options.
+ public static GraphicsOptions GetGraphicsOptions(this Configuration configuration)
+ {
+ if (configuration.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go)
+ {
+ return go;
+ }
+
+ var configOptions = new GraphicsOptions();
+
+ // capture the fallback so the same instance will always be returned in case its mutated
+ configuration.Properties[typeof(GraphicsOptions)] = configOptions;
+ return configOptions;
+ }
+ }
+}
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 7ef9038c53..b3fd9c879d 100644
--- a/src/ImageSharp/Image.FromStream.cs
+++ b/src/ImageSharp/Image.FromStream.cs
@@ -20,16 +20,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, false, s => InternalDetectFormat(s, configuration));
@@ -38,22 +42,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.
@@ -61,7 +71,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.
///
@@ -79,18 +92,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);
@@ -100,10 +118,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.
@@ -112,28 +134,38 @@ 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, true, s => decoder.Decode(configuration, s));
+ public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
+ => WithSeekableStream(configuration, stream, true, 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 _);
+ public static Image Load(Configuration configuration, Stream stream)
+ => Load(configuration, stream, out _);
///
/// 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)
@@ -145,8 +177,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)
@@ -158,8 +192,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)
@@ -172,8 +208,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)
@@ -185,8 +224,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)
@@ -199,14 +241,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, true, s => Decode(s, configuration));
format = data.format;
@@ -221,7 +265,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());
@@ -234,12 +278,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, true, s => Decode(s, configuration));
format = data.format;
@@ -254,7 +300,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());
@@ -262,6 +308,9 @@ namespace SixLabors.ImageSharp
private static T WithSeekableStream(Configuration configuration, Stream stream, bool buffer, 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/DefaultImageProcessorContext{TPixel}.cs b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
index 2e5919d1e3..714a45f5f0 100644
--- a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
+++ b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
@@ -39,6 +40,9 @@ namespace SixLabors.ImageSharp.Processing
///
public Configuration Configuration { get; }
+ ///
+ public IDictionary Properties { get; } = new Dictionary();
+
///
public Image GetResultImage()
{
diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
index 4717c09eaf..3c25bb7c40 100644
--- a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
@@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing
Image image,
float opacity)
{
- var options = new GraphicsOptions();
+ var options = source.GetGraphicsOptions();
return source.ApplyProcessor(
new DrawImageProcessor(
image,
@@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing
image,
Point.Empty,
colorBlending,
- new GraphicsOptions().AlphaCompositionMode,
+ source.GetGraphicsOptions().AlphaCompositionMode,
opacity));
///
@@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Processing
Point location,
float opacity)
{
- var options = new GraphicsOptions();
+ var options = source.GetGraphicsOptions();
return source.ApplyProcessor(
new DrawImageProcessor(
image,
@@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing
image,
location,
colorBlending,
- new GraphicsOptions().AlphaCompositionMode,
+ source.GetGraphicsOptions().AlphaCompositionMode,
opacity));
///
diff --git a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs
index 84b11c5e71..3f8a67feb3 100644
--- a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.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 SixLabors.ImageSharp.Processing.Processors.Filters;
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The to allow chaining of operations.
public static IImageProcessingContext Lomograph(this IImageProcessingContext source)
- => source.ApplyProcessor(new LomographProcessor());
+ => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()));
///
/// Alters the colors of the image recreating an old Lomograph camera effect.
@@ -28,6 +28,6 @@ namespace SixLabors.ImageSharp.Processing
///
/// The to allow chaining of operations.
public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle)
- => source.ApplyProcessor(new LomographProcessor(), rectangle);
+ => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()), rectangle);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs
index 94ced7108d..ab75ea56b5 100644
--- a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.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 SixLabors.ImageSharp.Processing.Processors.Filters;
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The to allow chaining of operations.
public static IImageProcessingContext Polaroid(this IImageProcessingContext source)
- => source.ApplyProcessor(new PolaroidProcessor());
+ => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()));
///
/// Alters the colors of the image recreating an old Polaroid camera effect.
@@ -28,6 +28,6 @@ namespace SixLabors.ImageSharp.Processing
///
/// The to allow chaining of operations.
public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle)
- => source.ApplyProcessor(new PolaroidProcessor(), rectangle);
+ => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()), rectangle);
}
}
diff --git a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs
index d068ba10b5..21e244f0a3 100644
--- a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing
/// The color to set as the background.
/// The to allow chaining of operations.
public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) =>
- BackgroundColor(source, new GraphicsOptions(), color);
+ BackgroundColor(source, source.GetGraphicsOptions(), color);
///
/// Replaces the background color of image with the given one.
@@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
Color color,
Rectangle rectangle) =>
- BackgroundColor(source, new GraphicsOptions(), color, rectangle);
+ BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle);
///
/// Replaces the background color of image with the given one.
diff --git a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs
index d5114e30ab..c3ce32e636 100644
--- a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source) =>
- Glow(source, new GraphicsOptions());
+ Glow(source, source.GetGraphicsOptions());
///
/// Applies a radial glow effect to an image.
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color)
{
- return Glow(source, new GraphicsOptions(), color);
+ return Glow(source, source.GetGraphicsOptions(), color);
}
///
@@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing
/// The the radius.
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) =>
- Glow(source, new GraphicsOptions(), radius);
+ Glow(source, source.GetGraphicsOptions(), radius);
///
/// Applies a radial glow effect to an image.
@@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing
///
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) =>
- source.Glow(new GraphicsOptions(), rectangle);
+ source.Glow(source.GetGraphicsOptions(), rectangle);
///
/// Applies a radial glow effect to an image.
@@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing
Color color,
float radius,
Rectangle rectangle) =>
- source.Glow(new GraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
+ source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
///
/// Applies a radial glow effect to an image.
diff --git a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs
index 799b30e01e..b53880fc12 100644
--- a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The to allow chaining of operations.
public static IImageProcessingContext Vignette(this IImageProcessingContext source) =>
- Vignette(source, new GraphicsOptions());
+ Vignette(source, source.GetGraphicsOptions());
///
/// Applies a radial vignette effect to an image.
@@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing
/// The color to set as the vignette.
/// The to allow chaining of operations.
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) =>
- Vignette(source, new GraphicsOptions(), color);
+ Vignette(source, source.GetGraphicsOptions(), color);
///
/// Applies a radial vignette effect to an image.
@@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
float radiusX,
float radiusY) =>
- Vignette(source, new GraphicsOptions(), radiusX, radiusY);
+ Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY);
///
/// Applies a radial vignette effect to an image.
@@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing
///
/// The to allow chaining of operations.
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) =>
- Vignette(source, new GraphicsOptions(), rectangle);
+ Vignette(source, source.GetGraphicsOptions(), rectangle);
///
/// Applies a radial vignette effect to an image.
@@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing
float radiusX,
float radiusY,
Rectangle rectangle) =>
- source.Vignette(new GraphicsOptions(), color, radiusX, radiusY, rectangle);
+ source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle);
///
/// Applies a radial vignette effect to an image.
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/src/ImageSharp/Processing/IImageProcessingContext.cs b/src/ImageSharp/Processing/IImageProcessingContext.cs
index 8b57a289d4..cb39766a94 100644
--- a/src/ImageSharp/Processing/IImageProcessingContext.cs
+++ b/src/ImageSharp/Processing/IImageProcessingContext.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System.Collections.Generic;
using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp.Processing
@@ -15,6 +16,12 @@ namespace SixLabors.ImageSharp.Processing
///
Configuration Configuration { get; }
+ ///
+ /// Gets a set of properties for the Image Processing Context.
+ ///
+ /// This can be used for storing global settings and defaults to be accessable to processors.
+ IDictionary Properties { get; }
+
///
/// Gets the image dimensions at the current point in the processing pipeline.
///
diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs
index 3c150d7ebf..bb6ea51c12 100644
--- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs
@@ -11,11 +11,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
///
/// Initializes a new instance of the class.
///
- public LomographProcessor()
+ /// Graphics options to use within the processor.
+ public LomographProcessor(GraphicsOptions graphicsOptions)
: base(KnownFilterMatrices.LomographFilter)
{
+ this.GraphicsOptions = graphicsOptions;
}
+ ///
+ /// Gets the options effecting blending and composition
+ ///
+ public GraphicsOptions GraphicsOptions { get; }
+
///
public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) =>
new LomographProcessor(configuration, this, source, sourceRectangle);
diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
index 5a19b78516..0706e9fc8d 100644
--- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
@@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
where TPixel : unmanaged, IPixel
{
private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255);
+ private readonly LomographProcessor definition;
///
/// Initializes a new instance of the class.
@@ -24,12 +25,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
public LomographProcessor(Configuration configuration, LomographProcessor definition, Image source, Rectangle sourceRectangle)
: base(configuration, definition, source, sourceRectangle)
{
+ this.definition = definition;
}
///
protected override void AfterImageApply()
{
- new VignetteProcessor(VeryDarkGreen).Execute(this.Configuration, this.Source, this.SourceRectangle);
+ new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkGreen).Execute(this.Configuration, this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}
diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs
index a5cf268625..965a35be15 100644
--- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs
@@ -11,11 +11,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
///
/// Initializes a new instance of the class.
///
- public PolaroidProcessor()
+ /// Graphics options to use within the processor.
+ public PolaroidProcessor(GraphicsOptions graphicsOptions)
: base(KnownFilterMatrices.PolaroidFilter)
{
+ this.GraphicsOptions = graphicsOptions;
}
+ ///
+ /// Gets the options effecting blending and composition
+ ///
+ public GraphicsOptions GraphicsOptions { get; }
+
///
public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) =>
new PolaroidProcessor(configuration, this, source, sourceRectangle);
diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
index 9f547be1c9..470d553c2c 100644
--- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
@@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
private static readonly Color LightOrange = Color.FromRgba(255, 153, 102, 128);
private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0);
+ private readonly PolaroidProcessor definition;
///
/// Initializes a new instance of the class.
@@ -25,13 +26,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image source, Rectangle sourceRectangle)
: base(configuration, definition, source, sourceRectangle)
{
+ this.definition = definition;
}
///
protected override void AfterImageApply()
{
- new VignetteProcessor(VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle);
- new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle);
+ new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle);
+ new GlowProcessor(this.definition.GraphicsOptions, LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}
diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
index 87e93ca215..5e0d1cbf7e 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
@@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
public sealed class GlowProcessor : IImageProcessor
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The color or the glow.
- public GlowProcessor(Color color)
- : this(color, 0)
- {
- }
-
///
/// Initializes a new instance of the class.
///
@@ -29,16 +20,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The color or the glow.
- /// The radius of the glow.
- internal GlowProcessor(Color color, ValueSize radius)
- : this(new GraphicsOptions(), color, radius)
- {
- }
-
///
/// Initializes a new instance of the class.
///
diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
index 5654eccfa4..3b16f8bc85 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
@@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
public sealed class VignetteProcessor : IImageProcessor
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The color of the vignette.
- public VignetteProcessor(Color color)
- : this(new GraphicsOptions(), color)
- {
- }
-
///
/// Initializes a new instance of the class.
///
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
new file mode 100644
index 0000000000..3f90412ae9
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Linq;
+using Moq;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Processing.Processors.Drawing;
+using SixLabors.ImageSharp.Tests.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Drawing
+{
+ public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
+ {
+
+ [Fact]
+ public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext()
+ {
+ // non-default values as we cant easly defect usage otherwise
+ this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
+ this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
+
+ this.operations.DrawImage(null, 0.5f);
+ var dip = this.Verify();
+
+ Assert.Equal(0.5, dip.Opacity);
+ Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
+ Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode);
+ }
+
+ [Fact]
+ public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
+ {
+ // non-default values as we cant easly defect usage otherwise
+ this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
+ this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
+
+ this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f);
+ var dip = this.Verify();
+
+ Assert.Equal(0.5, dip.Opacity);
+ Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
+ Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode);
+ }
+
+ [Fact]
+ public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext()
+ {
+ // non-default values as we cant easly defect usage otherwise
+ this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
+ this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
+
+ this.operations.DrawImage(null, Point.Empty, 0.5f);
+ var dip = this.Verify();
+
+ Assert.Equal(0.5, dip.Opacity);
+ Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
+ Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode);
+ }
+
+ [Fact]
+ public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
+ {
+ // non-default values as we cant easly defect usage otherwise
+ this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor;
+ this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;
+
+ this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f);
+ var dip = this.Verify();
+
+ Assert.Equal(0.5, dip.Opacity);
+ Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
+ Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode);
+ }
+ }
+}
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