Browse Source

Merge branch 'master' into js/buffered-read

pull/1269/head
James Jackson-South 6 years ago
parent
commit
48e1e8d5eb
  1. 24
      Directory.Build.props
  2. 22
      src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
  3. 10
      src/ImageSharp/Common/Helpers/InliningOptions.cs
  4. 7
      src/ImageSharp/Configuration.cs
  5. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  6. 2
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  7. 16
      src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
  8. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  9. 18
      src/ImageSharp/Formats/Gif/GifThrowHelper.cs
  10. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  11. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
  12. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  13. 154
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  14. 68
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  15. 27
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  16. 35
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  17. 29
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  18. 10
      src/ImageSharp/Formats/Png/PngThrowHelper.cs
  19. 2
      src/ImageSharp/Formats/Png/Zlib/Deflater.cs
  20. 5
      src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
  21. 125
      src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
  22. 2
      src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
  23. 6
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  24. 14
      src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
  25. 100
      src/ImageSharp/GraphicOptionsDefaultsExtensions.cs
  26. 147
      src/ImageSharp/Image.FromBytes.cs
  27. 119
      src/ImageSharp/Image.FromFile.cs
  28. 131
      src/ImageSharp/Image.FromStream.cs
  29. 43
      src/ImageSharp/Image.LoadPixelData.cs
  30. 50
      src/ImageSharp/Image.WrapMemory.cs
  31. 46
      src/ImageSharp/ImageExtensions.cs
  32. 4
      src/ImageSharp/Memory/Buffer2D{T}.cs
  33. 10
      src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
  34. 69
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
  35. 8
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
  36. 15
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
  37. 36
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
  38. 29
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  39. 16
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  40. 7
      src/ImageSharp/Metadata/ImageMetadata.cs
  41. 3
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  42. BIN
      src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf
  43. 298
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  44. 397
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
  45. 162
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
  46. 219
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  47. 11
      src/ImageSharp/Metadata/Profiles/IPTC/README.md
  48. 4
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  49. 8
      src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
  50. 8
      src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs
  51. 6
      src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs
  52. 4
      src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs
  53. 10
      src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs
  54. 10
      src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs
  55. 67
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  56. 7
      src/ImageSharp/Processing/IImageProcessingContext.cs
  57. 9
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs
  58. 4
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
  59. 9
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs
  60. 6
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
  61. 19
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  62. 9
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  63. 83
      tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
  64. 4
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  65. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  66. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  67. 23
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  68. 172
      tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs
  69. 35
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
  70. 359
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
  71. 2
      tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
  72. 10
      tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs
  73. 2
      tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs
  74. 4
      tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs
  75. 6
      tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs
  76. 6
      tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs
  77. 10
      tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs
  78. 10
      tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs
  79. 6
      tests/ImageSharp.Tests/TestImages.cs
  80. BIN
      tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg
  81. BIN
      tests/Images/Input/Jpg/baseline/iptc.jpg
  82. BIN
      tests/Images/Input/Png/issues/Issue_1177_1.png
  83. BIN
      tests/Images/Input/Png/issues/Issue_1177_2.png

24
Directory.Build.props

@ -31,21 +31,21 @@
<!-- <!--
https://apisof.net/ https://apisof.net/
+===================+=======+==========+=====================+=============+=================+====================+==============+ +===================+=======+==========+=====================+=============+=================+====================+==============+=========+
| SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | | SUPPORTS | MATHF | HASHCODE | EXTENDED_INTRINSICS | SPAN_STREAM | ENCODING_STRING | RUNTIME_INTRINSICS | CODECOVERAGE | HOTPATH |
+===================+=======+==========+=====================+=============+=================+====================+==============+ +===================+=======+==========+=====================+=============+=================+====================+==============+=========|
| netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | | netcoreapp3.1 | Y | Y | Y | Y | Y | Y | Y | Y |
| netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | | netcoreapp2.1 | Y | Y | Y | Y | Y | N | Y | N |
| netcoreapp2.0 | Y | N | N | N | N | N | Y | | netcoreapp2.0 | Y | N | N | N | N | N | Y | N |
| netstandard2.1 | Y | Y | N | Y | Y | N | Y | | netstandard2.1 | Y | Y | N | Y | Y | N | Y | N |
| netstandard2.0 | N | N | N | N | N | N | Y | | netstandard2.0 | N | N | N | N | N | N | Y | N |
| netstandard1.3 | N | N | N | N | N | N | N | | netstandard1.3 | N | N | N | N | N | N | N | N |
| net472 | N | N | Y | N | N | N | Y | | net472 | N | N | Y | N | N | N | Y | N |
+===================+=======+==========+=====================+=============+=================+====================+==============+ +===================+=======+==========+=====================+=============+=================+====================+==============+=========|
--> -->
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'"> <PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_RUNTIME_INTRINSICS;SUPPORTS_CODECOVERAGE;SUPPORTS_HOTPATH</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'"> <PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_CODECOVERAGE</DefineConstants> <DefineConstants>$(DefineConstants);SUPPORTS_MATHF;SUPPORTS_HASHCODE;SUPPORTS_EXTENDED_INTRINSICS;SUPPORTS_SPAN_STREAM;SUPPORTS_ENCODING_STRING;SUPPORTS_CODECOVERAGE</DefineConstants>

22
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
{
/// <summary>
/// The exception that is thrown when the library tries to load
/// an image which contains invalid content.
/// </summary>
public sealed class InvalidImageContentException : ImageFormatException
{
/// <summary>
/// Initializes a new instance of the <see cref="InvalidImageContentException"/> class with the name of the
/// parameter that causes this exception.
/// </summary>
/// <param name="errorMessage">The error message that explains the reason for this exception.</param>
public InvalidImageContentException(string errorMessage)
: base(errorMessage)
{
}
}
}

10
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. // Licensed under the Apache License, Version 2.0.
// Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN! // Uncomment this for verbose profiler results. DO NOT PUSH TO MAIN!
@ -13,10 +13,16 @@ namespace SixLabors.ImageSharp
internal static class InliningOptions internal static class InliningOptions
{ {
#if PROFILING #if PROFILING
public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;
#else #else
#if SUPPORTS_HOTPATH
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization;
#else
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining;
#endif
public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining;
#endif #endif
public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining;
} }
} }

7
src/ImageSharp/Configuration.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
@ -73,6 +74,12 @@ namespace SixLabors.ImageSharp
} }
} }
/// <summary>
/// Gets a set of properties for the Congiguration.
/// </summary>
/// <remarks>This can be used for storing global settings and defaults to be accessable to processors.</remarks>
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
/// <summary> /// <summary>
/// Gets the currently registered <see cref="IImageFormat"/>s. /// Gets the currently registered <see cref="IImageFormat"/>s.
/// </summary> /// </summary>

10
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -462,7 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
if (this.stream.Read(cmd, 0, cmd.Length) != 2) 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) if (cmd[0] == RleCommand)
@ -569,7 +569,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
if (this.stream.Read(cmd, 0, cmd.Length) != 2) 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) if (cmd[0] == RleCommand)
@ -648,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
if (this.stream.Read(cmd, 0, cmd.Length) != 2) 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) 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). // Make sure, that we will not read pass the bitmap offset (starting position of image data).
if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) 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."); $"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; int skipAmount = this.fileHeader.Offset - (int)this.stream.Position;
if ((skipAmount + (int)this.stream.Position) > this.stream.Length) 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) if (skipAmount > 0)

2
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -393,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break; break;
default: default:
// Compression type 3 (1DHuffman) is not supported. // 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; break;
} }

16
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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal static class BmpThrowHelper internal static class BmpThrowHelper
{ {
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s /// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s
/// </summary> /// </summary>
/// <param name="errorMessage">The error message for the exception.</param> /// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowImageFormatException(string errorMessage) public static void ThrowInvalidImageContentException(string errorMessage)
{ => throw new InvalidImageContentException(errorMessage);
throw new ImageFormatException(errorMessage);
}
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s /// Cold path optimization for throwing <see cref="NotSupportedException"/>'s
/// </summary> /// </summary>
/// <param name="errorMessage">The error message for the exception.</param> /// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage) public static void ThrowNotSupportedException(string errorMessage)
{ => throw new NotSupportedException(errorMessage);
throw new NotSupportedException(errorMessage);
}
} }
} }

2
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -318,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
if (length > GifConstants.MaxCommentSubBlockLength) 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) if (this.IgnoreMetadata)

18
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
{
/// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidImageContentException(string errorMessage)
=> throw new InvalidImageContentException(errorMessage);
}
}

4
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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
// TODO: Run fallback scalar code here // TODO: Run fallback scalar code here
// However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007 // 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: // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:

4
src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs

@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
if (xDensity <= 0) 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) 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; this.MajorVersion = majorVersion;

24
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' (byte)'I', (byte)'L', (byte)'E', (byte)'\0'
}; };
/// <summary>
/// Gets the adobe photoshop APP13 marker which can contain IPTC meta data.
/// </summary>
public static ReadOnlySpan<byte> 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'
};
/// <summary>
/// Gets the 8BIM marker, which signals the start of a adobe specific image resource block.
/// </summary>
public static ReadOnlySpan<byte> AdobeImageResourceBlockMarker => new[]
{
(byte)'8', (byte)'B', (byte)'I', (byte)'M'
};
/// <summary>
/// Gets a IPTC Image resource ID.
/// </summary>
public static ReadOnlySpan<byte> AdobeIptcMarker => new[]
{
(byte)4, (byte)4
};
/// <summary> /// <summary>
/// Gets the EXIF specific markers. /// Gets the EXIF specific markers.
/// </summary> /// </summary>

154
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private readonly byte[] markerBuffer = new byte[2]; private readonly byte[] markerBuffer = new byte[2];
/// <summary> /// <summary>
/// The DC Huffman tables /// The DC Huffman tables.
/// </summary> /// </summary>
private HuffmanTable[] dcHuffmanTables; private HuffmanTable[] dcHuffmanTables;
@ -55,37 +56,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private HuffmanTable[] acHuffmanTables; private HuffmanTable[] acHuffmanTables;
/// <summary> /// <summary>
/// The reset interval determined by RST markers /// The reset interval determined by RST markers.
/// </summary> /// </summary>
private ushort resetInterval; private ushort resetInterval;
/// <summary> /// <summary>
/// Whether the image has an EXIF marker /// Whether the image has an EXIF marker.
/// </summary> /// </summary>
private bool isExif; private bool isExif;
/// <summary> /// <summary>
/// Contains exif data /// Contains exif data.
/// </summary> /// </summary>
private byte[] exifData; private byte[] exifData;
/// <summary> /// <summary>
/// Whether the image has an ICC marker /// Whether the image has an ICC marker.
/// </summary> /// </summary>
private bool isIcc; private bool isIcc;
/// <summary> /// <summary>
/// Contains ICC data /// Contains ICC data.
/// </summary> /// </summary>
private byte[] iccData; private byte[] iccData;
/// <summary> /// <summary>
/// Contains information about the JFIF marker /// Whether the image has a IPTC data.
/// </summary>
private bool isIptc;
/// <summary>
/// Contains IPTC data.
/// </summary>
private byte[] iptcData;
/// <summary>
/// Contains information about the JFIF marker.
/// </summary> /// </summary>
private JFifMarker jFif; private JFifMarker jFif;
/// <summary> /// <summary>
/// Contains information about the Adobe marker /// Contains information about the Adobe marker.
/// </summary> /// </summary>
private AdobeMarker adobe; private AdobeMarker adobe;
@ -207,6 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream); this.ParseStream(stream);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage<TPixel>(); return this.PostProcessIntoImage<TPixel>();
} }
@ -220,6 +232,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream, true); this.ParseStream(stream, true);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); 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); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI) if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
JpegThrowHelper.ThrowImageFormatException("Missing SOI marker."); JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
} }
stream.Read(this.markerBuffer, 0, 2); stream.Read(this.markerBuffer, 0, 2);
@ -337,10 +350,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12: case JpegConstants.Markers.APP12:
case JpegConstants.Markers.APP13:
stream.Skip(remaining); stream.Skip(remaining);
break; break;
case JpegConstants.Markers.APP13:
this.ProcessApp13Marker(stream, remaining);
break;
case JpegConstants.Markers.APP14: case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(stream, remaining); this.ProcessApp14Marker(stream, remaining);
break; break;
@ -398,7 +414,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
: JpegColorSpace.Cmyk; : 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; return default;
} }
@ -428,6 +444,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
} }
/// <summary>
/// Initializes the IPTC profile.
/// </summary>
private void InitIptcProfile()
{
if (this.isIptc)
{
var profile = new IptcProfile(this.iptcData);
this.Metadata.IptcProfile = profile;
}
}
/// <summary> /// <summary>
/// Assigns derived metadata properties to <see cref="Metadata"/>, eg. horizontal and vertical resolution if it has a JFIF header. /// Assigns derived metadata properties to <see cref="Metadata"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary> /// </summary>
@ -576,6 +604,96 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
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<byte> blockDataSpan = resourceBlockData.AsSpan();
while (blockDataSpan.Length > 12)
{
if (!ProfileResolver.IsProfile(blockDataSpan.Slice(0, 4), ProfileResolver.AdobeImageResourceBlockMarker))
{
return;
}
blockDataSpan = blockDataSpan.Slice(4);
Span<byte> 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);
}
}
}
}
/// <summary>
/// Reads the adobe image resource block name: a Pascal string (padded to make size even).
/// </summary>
/// <param name="blockDataSpan">The span holding the block resource data.</param>
/// <returns>The length of the name.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int ReadImageResourceNameLength(Span<byte> blockDataSpan)
{
byte nameLength = blockDataSpan[2];
var nameDataSize = nameLength == 0 ? 2 : nameLength;
if (nameDataSize % 2 != 0)
{
nameDataSize++;
}
return nameDataSize;
}
/// <summary>
/// Reads the length of a adobe image resource data block.
/// </summary>
/// <param name="blockDataSpan">The span holding the block resource data.</param>
/// <param name="resourceBlockNameLength">The length of the block name.</param>
/// <returns>The block length.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int ReadResourceDataLength(Span<byte> blockDataSpan, int resourceBlockNameLength)
{
return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
}
/// <summary> /// <summary>
/// Processes the application header containing the Adobe identifier /// Processes the application header containing the Adobe identifier
/// which stores image encoding information for DCT filters. /// which stores image encoding information for DCT filters.
@ -701,7 +819,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
if (this.Frame != null) 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. // Read initial marker definitions.
@ -711,7 +829,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// We only support 8-bit and 12-bit precision. // We only support 8-bit and 12-bit precision.
if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) 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]; this.Precision = this.temp[0];
@ -809,13 +927,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Types 0..1 DC..AC // Types 0..1 DC..AC
if (tableType > 1) if (tableType > 1)
{ {
JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table type."); JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table type.");
} }
// Max tables of each type // Max tables of each type
if (tableIndex > 3) if (tableIndex > 3)
{ {
JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table index."); JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index.");
} }
stream.Read(huffmanData.Array, 0, 16); stream.Read(huffmanData.Array, 0, 16);
@ -834,7 +952,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (codeLengthSum > 256 || codeLengthSum > length) 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)) using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
@ -878,7 +996,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
if (this.Frame is null) 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(); int selectorsCount = stream.ReadByte();
@ -899,7 +1017,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentIndex < 0) if (componentIndex < 0)
{ {
JpegThrowHelper.ThrowImageFormatException($"Unknown component selector {componentIndex}."); JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}.");
} }
ref JpegComponent component = ref this.Frame.Components[componentIndex]; ref JpegComponent component = ref this.Frame.Components[componentIndex];

68
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -13,6 +14,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -231,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteApplicationHeader(metadata); this.WriteApplicationHeader(metadata);
// Write Exif and ICC profiles // Write Exif, ICC and IPTC profiles
this.WriteProfiles(metadata); this.WriteProfiles(metadata);
// Write the quantization tables. // Write the quantization tables.
@ -647,9 +649,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Writes the EXIF profile. /// Writes the EXIF profile.
/// </summary> /// </summary>
/// <param name="exifProfile">The exif profile.</param> /// <param name="exifProfile">The exif profile.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the EXIF profile size exceeds the limit
/// </exception>
private void WriteExifProfile(ExifProfile exifProfile) private void WriteExifProfile(ExifProfile exifProfile)
{ {
if (exifProfile is null || exifProfile.Values.Count == 0) if (exifProfile is null || exifProfile.Values.Count == 0)
@ -697,16 +696,68 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
} }
/// <summary>
/// Writes the IPTC metadata.
/// </summary>
/// <param name="iptcProfile">The iptc metadata to write.</param>
/// <exception cref="ImageFormatException">
/// Thrown if the IPTC profile size exceeds the limit of 65533 bytes.
/// </exception>
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);
}
/// <summary> /// <summary>
/// Writes the App1 header. /// Writes the App1 header.
/// </summary> /// </summary>
/// <param name="app1Length">The length of the data the app1 marker contains</param> /// <param name="app1Length">The length of the data the app1 marker contains.</param>
private void WriteApp1Header(int app1Length) private void WriteApp1Header(int app1Length)
{
this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1);
}
/// <summary>
/// Writes a AppX header.
/// </summary>
/// <param name="length">The length of the data the app marker contains.</param>
/// <param name="appMarker">The app marker to write.</param>
private void WriteAppHeader(int length, byte appMarker)
{ {
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker this.buffer[1] = appMarker;
this.buffer[2] = (byte)((app1Length >> 8) & 0xFF); this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(app1Length & 0xFF); this.buffer[3] = (byte)(length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4); this.outputStream.Write(this.buffer, 0, 4);
} }
@ -805,6 +856,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
metadata.SyncProfiles(); metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile); this.WriteExifProfile(metadata.ExifProfile);
this.WriteIccProfile(metadata.IccProfile); this.WriteIccProfile(metadata.IccProfile);
this.WriteIptcProfile(metadata.IptcProfile);
} }
/// <summary> /// <summary>

27
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. // Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -8,25 +9,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal static class JpegThrowHelper internal static class JpegThrowHelper
{ {
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>'s. /// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary> /// </summary>
/// <param name="errorMessage">The error message for the exception.</param> /// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
/// <summary>
/// Cold path optimization for throwing <see cref="NotImplementedException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotImplementedException(string errorMessage)
=> throw new NotImplementedException(errorMessage);
[MethodImpl(InliningOptions.ColdPath)] [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)] [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)] [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)] [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)] [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}.");
} }
} }

35
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -380,7 +380,12 @@ namespace SixLabors.ImageSharp.Formats.Png
private void InitializeImage<TPixel>(ImageMetadata metadata, out Image<TPixel> image) private void InitializeImage<TPixel>(ImageMetadata metadata, out Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata); image = Image.CreateUninitialized<TPixel>(
this.configuration,
this.header.Width,
this.header.Height,
metadata);
this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
this.bytesPerSample = 1; this.bytesPerSample = 1;
@ -1136,24 +1141,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param> /// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk) private void ValidateChunk(in PngChunk chunk)
{ {
if (!chunk.IsCritical) uint crc = this.ReadChunkCrc();
{
return;
}
Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); if (chunk.IsCritical)
{
Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
this.crc.Reset(); this.crc.Reset();
this.crc.Update(chunkType); this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan()); this.crc.Update(chunk.Data.GetSpan());
uint crc = this.ReadChunkCrc(); if (this.crc.Value != crc)
if (this.crc.Value != crc) {
{ string chunkTypeName = Encoding.ASCII.GetString(chunkType);
string chunkTypeName = Encoding.ASCII.GetString(chunkType); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); }
} }
} }

29
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -5,10 +5,8 @@ using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Filters;
@ -16,7 +14,6 @@ using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Png namespace SixLabors.ImageSharp.Formats.Png
{ {
@ -633,10 +630,21 @@ namespace SixLabors.ImageSharp.Formats.Png
private void WriteTextChunks(Stream stream, PngMetadata meta) private void WriteTextChunks(Stream stream, PngMetadata meta)
{ {
const int MaxLatinCode = 255; 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); PngTextData textData = meta.TextData[i];
if (hasUnicodeCharacters || (!string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword))) 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. // Write iTXt chunk.
byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword); byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword);
@ -647,7 +655,8 @@ namespace SixLabors.ImageSharp.Formats.Png
byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword);
byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag);
Span<byte> outputBytes = new byte[keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5]; Span<byte> outputBytes = new byte[keywordBytes.Length + textBytes.Length +
translatedKeyword.Length + languageTag.Length + 5];
keywordBytes.CopyTo(outputBytes); keywordBytes.CopyTo(outputBytes);
if (textData.Value.Length > this.options.TextCompressionThreshold) if (textData.Value.Length > this.options.TextCompressionThreshold)
{ {
@ -667,7 +676,8 @@ namespace SixLabors.ImageSharp.Formats.Png
if (textData.Value.Length > this.options.TextCompressionThreshold) if (textData.Value.Length > this.options.TextCompressionThreshold)
{ {
// Write zTXt chunk. // Write zTXt chunk.
byte[] compressedData = this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value)); byte[] compressedData =
this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value));
Span<byte> outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2]; Span<byte> outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2];
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes);
compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2)); compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2));
@ -678,7 +688,8 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write tEXt chunk. // Write tEXt chunk.
Span<byte> outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1]; Span<byte> outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1];
PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); 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()); this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray());
} }
} }

10
src/ImageSharp/Formats/Png/PngThrowHelper.cs

@ -12,21 +12,21 @@ namespace SixLabors.ImageSharp.Formats.Png
internal static class PngThrowHelper internal static class PngThrowHelper
{ {
[MethodImpl(InliningOptions.ColdPath)] [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)] [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)] [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)] [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)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type"); public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type."); public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type.");
} }
} }

2
src/ImageSharp/Formats/Png/Zlib/Deflater.cs

@ -288,8 +288,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.engine = null; this.engine = null;
this.isDisposed = true; this.isDisposed = true;
} }
GC.SuppressFinalize(this);
} }
} }
} }

5
src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs

@ -362,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
more = this.inputEnd - this.inputOff; 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.inputOff += more;
this.lookahead += more; this.lookahead += more;
@ -397,8 +397,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true; this.isDisposed = true;
} }
GC.SuppressFinalize(this);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -464,6 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </summary> /// </summary>
/// <param name="curMatch">The current match.</param> /// <param name="curMatch">The current match.</param>
/// <returns>True if a match greater than the minimum length is found</returns> /// <returns>True if a match greater than the minimum length is found</returns>
[MethodImpl(InliningOptions.HotPath)]
private bool FindLongestMatch(int curMatch) private bool FindLongestMatch(int curMatch)
{ {
int match; int match;

125
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs

@ -36,11 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private const int EofSymbol = 256; 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 literalTree;
private Tree distTree; private Tree distTree;
private Tree blTree; private Tree blTree;
@ -58,49 +53,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private int extraBits; private int extraBits;
private bool isDisposed; 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;
}
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeflaterHuffman"/> class. /// Initializes a new instance of the <see cref="DeflaterHuffman"/> class.
/// </summary> /// </summary>
@ -122,12 +74,80 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; 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<byte> 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<byte> 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
/// <summary> /// <summary>
/// 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. /// 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.
/// </summary> /// </summary>
private static ReadOnlySpan<byte> 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<byte> 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<byte> Bit4Reverse => new byte[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; private static ReadOnlySpan<byte> Bit4Reverse => new byte[]
{
0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
};
/// <summary> /// <summary>
/// Gets the pending buffer to use. /// Gets the pending buffer to use.
@ -413,8 +433,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.distTree = null; this.distTree = null;
this.isDisposed = true; this.isDisposed = true;
} }
GC.SuppressFinalize(this);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -553,6 +571,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
} }
} }
[MethodImpl(InliningOptions.HotPath)]
public void BuildTree() public void BuildTree()
{ {
int numSymbols = this.elementCount; int numSymbols = this.elementCount;
@ -964,8 +983,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true; this.isDisposed = true;
} }
GC.SuppressFinalize(this);
} }
} }
} }

2
src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs

@ -172,8 +172,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.bufferMemoryOwner = null; this.bufferMemoryOwner = null;
this.isDisposed = true; this.isDisposed = true;
} }
GC.SuppressFinalize(this);
} }
} }
} }

6
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -114,12 +114,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
{ {
if (this.fileHeader.CMapLength <= 0) if (this.fileHeader.CMapLength <= 0)
{ {
TgaThrowHelper.ThrowImageFormatException("Missing tga color map length"); TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map length");
} }
if (this.fileHeader.CMapDepth <= 0) 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; int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
@ -898,7 +898,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
var alphaBits = this.fileHeader.ImageDescriptor & 0xf; var alphaBits = this.fileHeader.ImageDescriptor & 0xf;
if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8) if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8)
{ {
TgaThrowHelper.ThrowImageFormatException("Invalid alpha channel bits"); TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits");
} }
this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; this.tgaMetadata.AlphaChannelBits = (byte)alphaBits;

14
src/ImageSharp/Formats/Tga/TgaThrowHelper.cs

@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Tga
internal static class TgaThrowHelper internal static class TgaThrowHelper
{ {
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s /// Cold path optimization for throwing <see cref="ImageFormatException"/>'s
/// </summary> /// </summary>
/// <param name="errorMessage">The error message for the exception.</param> /// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowImageFormatException(string errorMessage) public static void ThrowInvalidImageContentException(string errorMessage)
{ => throw new InvalidImageContentException(errorMessage);
throw new ImageFormatException(errorMessage);
}
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s /// Cold path optimization for throwing <see cref="NotSupportedException"/>'s
/// </summary> /// </summary>
/// <param name="errorMessage">The error message for the exception.</param> /// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage) public static void ThrowNotSupportedException(string errorMessage)
{ => throw new NotSupportedException(errorMessage);
throw new NotSupportedException(errorMessage);
}
} }
} }

100
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
{
/// <summary>
/// Adds extensions that allow the processing of images to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class GraphicOptionsDefaultsExtensions
{
/// <summary>
/// Sets the default options against the image processing context.
/// </summary>
/// <param name="context">The image processing context to store default against.</param>
/// <param name="optionsBuilder">The action to update instance of the default options used.</param>
/// <returns>The passed in <paramref name="context"/> to allow chaining.</returns>
public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, Action<GraphicsOptions> optionsBuilder)
{
var cloned = context.GetGraphicsOptions().DeepClone();
optionsBuilder(cloned);
context.Properties[typeof(GraphicsOptions)] = cloned;
return context;
}
/// <summary>
/// Sets the default options against the configuration.
/// </summary>
/// <param name="configuration">The configuration to store default against.</param>
/// <param name="optionsBuilder">The default options to use.</param>
public static void SetGraphicsOptions(this Configuration configuration, Action<GraphicsOptions> optionsBuilder)
{
var cloned = configuration.GetGraphicsOptions().DeepClone();
optionsBuilder(cloned);
configuration.Properties[typeof(GraphicsOptions)] = cloned;
}
/// <summary>
/// Sets the default options against the image processing context.
/// </summary>
/// <param name="context">The image processing context to store default against.</param>
/// <param name="options">The default options to use.</param>
/// <returns>The passed in <paramref name="context"/> to allow chaining.</returns>
public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, GraphicsOptions options)
{
context.Properties[typeof(GraphicsOptions)] = options;
return context;
}
/// <summary>
/// Sets the default options against the configuration.
/// </summary>
/// <param name="configuration">The configuration to store default against.</param>
/// <param name="options">The default options to use.</param>
public static void SetGraphicsOptions(this Configuration configuration, GraphicsOptions options)
{
configuration.Properties[typeof(GraphicsOptions)] = options;
}
/// <summary>
/// Gets the default options against the image processing context.
/// </summary>
/// <param name="context">The image processing context to retrieve defaults from.</param>
/// <returns>The globaly configued default options.</returns>
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;
}
/// <summary>
/// Gets the default options against the image processing context.
/// </summary>
/// <param name="configuration">The configuration to retrieve defaults from.</param>
/// <returns>The globaly configued default options.</returns>
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;
}
}
}

147
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. /// By reading the header on the provided byte array this calculates the images format.
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <returns>The format or null if none found.</returns> /// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(byte[] data) public static IImageFormat DetectFormat(byte[] data)
{ => DetectFormat(Configuration.Default, data);
return DetectFormat(Configuration.Default, data);
}
/// <summary> /// <summary>
/// By reading the header on the provided byte array this calculates the images format. /// By reading the header on the provided byte array this calculates the images format.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, byte[] data) 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)) using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
return DetectFormat(configuration, stream); return DetectFormat(configuration, stream);
@ -42,7 +44,8 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
@ -53,7 +56,8 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
@ -65,13 +69,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte array containing encoded image data to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="NotSupportedException">The data is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns> /// </returns>
public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format) 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)) using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
return Identify(configuration, stream, out format); return Identify(configuration, stream, out format);
@ -82,14 +89,20 @@ namespace SixLabors.ImageSharp
/// Load a new instance of <see cref="Image{Rgba32}"/> from the given encoded byte array. /// Load a new instance of <see cref="Image{Rgba32}"/> from the given encoded byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
public static Image<Rgba32> Load(byte[] data) => Load<Rgba32>(Configuration.Default, data); public static Image<Rgba32> Load(byte[] data)
=> Load<Rgba32>(Configuration.Default, data);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data) public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -101,6 +114,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -112,10 +128,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data) public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true)) using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
return Load<TPixel>(configuration, stream); return Load<TPixel>(configuration, stream);
@ -129,10 +151,16 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param> /// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true)) using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
return Load<TPixel>(configuration, stream, out format); return Load<TPixel>(configuration, stream, out format);
@ -145,10 +173,15 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true)) using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
return Load<TPixel>(stream, decoder); return Load<TPixel>(stream, decoder);
@ -162,10 +195,16 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true)) using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{ {
return Load<TPixel>(configuration, stream, decoder); return Load<TPixel>(configuration, stream, decoder);
@ -173,9 +212,9 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <returns>The format or null if none found.</returns> /// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data) public static IImageFormat DetectFormat(ReadOnlySpan<byte> data)
{ {
@ -183,13 +222,16 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="data">The byte array containing encoded image data to read the header from.</param> /// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan<byte> data) public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan<byte> data)
{ {
Guard.NotNull(configuration, nameof(configuration));
int maxHeaderSize = configuration.MaxHeaderSize; int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0) if (maxHeaderSize <= 0)
{ {
@ -214,28 +256,34 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="data">The byte span containing encoded image data.</param> /// <param name="data">The byte span containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data); => Load<TPixel>(Configuration.Default, data);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format); => Load<TPixel>(Configuration.Default, data, out format);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="data">The byte span containing encoded image data.</param> /// <param name="data">The byte span containing encoded image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -247,6 +295,9 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing encoded image data.</param> /// <param name="data">The byte span containing encoded image data.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data) public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -267,6 +318,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>( public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration, Configuration configuration,
@ -290,6 +344,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param> /// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>( public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration, Configuration configuration,
@ -311,25 +368,38 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="format">The detected format.</param> /// <param name="format">The detected format.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(byte[] data, out IImageFormat format) => public static Image Load(byte[] data, out IImageFormat format)
Load(Configuration.Default, data, out format); => Load(Configuration.Default, data, out format);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array. /// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary> /// </summary>
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
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);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array. /// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing encoded image data.</param> /// <param name="data">The byte array containing encoded image data.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data) => Load(configuration, data, out _); public static Image Load(Configuration configuration, byte[] data)
=> Load(configuration, data, out _);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array. /// Load a new instance of <see cref="Image"/> from the given encoded byte array.
@ -337,6 +407,10 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder)
{ {
@ -352,6 +426,10 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) public static Image Load(Configuration configuration, byte[] data, out IImageFormat format)
{ {
@ -365,26 +443,36 @@ namespace SixLabors.ImageSharp
/// Load a new instance of <see cref="Image"/> from the given encoded byte span. /// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data) => Load(Configuration.Default, data); public static Image Load(ReadOnlySpan<byte> data)
=> Load(Configuration.Default, data);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span. /// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary> /// </summary>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder) => public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder)
Load(Configuration.Default, data, decoder); => Load(Configuration.Default, data, decoder);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array. /// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary> /// </summary>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="format">The detected format.</param> /// <param name="format">The detected format.</param>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format) => public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format)
Load(Configuration.Default, data, out format); => Load(Configuration.Default, data, out format);
/// <summary> /// <summary>
/// Decodes a new instance of <see cref="Image"/> from the given encoded byte span. /// Decodes a new instance of <see cref="Image"/> from the given encoded byte span.
@ -392,7 +480,8 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, ReadOnlySpan<byte> data) => Load(configuration, data, out _); public static Image Load(Configuration configuration, ReadOnlySpan<byte> data)
=> Load(configuration, data, out _);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span. /// Load a new instance of <see cref="Image"/> from the given encoded byte span.
@ -400,6 +489,11 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The Configuration.</param> /// <param name="configuration">The Configuration.</param>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load( public static unsafe Image Load(
Configuration configuration, Configuration configuration,
@ -421,6 +515,9 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>> /// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load( public static unsafe Image Load(
Configuration configuration, Configuration configuration,

119
src/ImageSharp/Image.FromFile.cs

@ -19,19 +19,19 @@ namespace SixLabors.ImageSharp
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(string filePath) public static IImageFormat DetectFormat(string filePath)
{ => DetectFormat(Configuration.Default, filePath);
return DetectFormat(Configuration.Default, filePath);
}
/// <summary> /// <summary>
/// By reading the header on the provided file this calculates the images mime type. /// By reading the header on the provided file this calculates the images mime type.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>The mime type or null if none found.</returns> /// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, string filePath) public static IImageFormat DetectFormat(Configuration configuration, string filePath)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
using (Stream file = configuration.FileSystem.OpenRead(filePath)) using (Stream file = configuration.FileSystem.OpenRead(filePath))
{ {
return DetectFormat(configuration, file); return DetectFormat(configuration, file);
@ -42,22 +42,22 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
public static IImageInfo Identify(string filePath) => Identify(filePath, out IImageFormat _); public static IImageInfo Identify(string filePath)
=> Identify(filePath, out IImageFormat _);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
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);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="filePath">The image file to open and to read the header from.</param> /// <param name="filePath">The image file to open and to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns> /// </returns>
@ -86,7 +86,8 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(string path) => Load(Configuration.Default, path); public static Image Load(string path)
=> Load(Configuration.Default, path);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
@ -97,18 +98,21 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is not readable nor seekable. /// Thrown if the stream is not readable nor seekable.
/// </exception> /// </exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image{Rgba32}"/>.</returns>
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);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path) => Load(configuration, path, out _); public static Image Load(Configuration configuration, string path)
=> Load(configuration, path, out _);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image"/> class from the given file. /// Create a new instance of the <see cref="Image"/> class from the given file.
@ -116,13 +120,17 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The Configuration.</param> /// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path, IImageDecoder decoder) public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using (Stream stream = configuration.FileSystem.OpenRead(path))
{ {
return Load(configuration, stream, decoder); return Load(configuration, stream, decoder);
@ -134,57 +142,58 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// </exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
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);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// </exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path) public static Image<TPixel> Load<TPixel>(string path)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Load<TPixel>(Configuration.Default, path);
return Load<TPixel>(Configuration.Default, path);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// </exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format) public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Load<TPixel>(Configuration.Default, path, out format);
return Load<TPixel>(Configuration.Default, path, out format);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary> /// </summary>
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path) public static Image<TPixel> Load<TPixel>(Configuration configuration, string path)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using (Stream stream = configuration.FileSystem.OpenRead(path))
{ {
return Load<TPixel>(configuration, stream); return Load<TPixel>(configuration, stream);
@ -197,15 +206,18 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, out IImageFormat format) public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using (Stream stream = configuration.FileSystem.OpenRead(path))
{ {
return Load<TPixel>(configuration, stream, out format); return Load<TPixel>(configuration, stream, out format);
@ -219,13 +231,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param> /// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, string path, out IImageFormat format) public static Image Load(Configuration configuration, string path, out IImageFormat format)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using (Stream stream = configuration.FileSystem.OpenRead(path))
{ {
return Load(configuration, stream, out format); return Load(configuration, stream, out format);
@ -237,16 +252,14 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// </exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => Load<TPixel>(Configuration.Default, path, decoder);
return Load<TPixel>(Configuration.Default, path, decoder);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
@ -254,15 +267,19 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The Configuration.</param> /// <param name="configuration">The Configuration.</param>
/// <param name="path">The file path to the image.</param> /// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException"> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// Thrown if the stream is not readable nor seekable. /// <exception cref="ArgumentNullException">The path is null.</exception>
/// </exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path)) using (Stream stream = configuration.FileSystem.OpenRead(path))
{ {
return Load<TPixel>(configuration, stream, decoder); return Load<TPixel>(configuration, stream, decoder);

131
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. /// By reading the header on the provided stream this calculates the images format type.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>The format type or null if none found.</returns> /// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream); public static IImageFormat DetectFormat(Stream stream)
=> DetectFormat(Configuration.Default, stream);
/// <summary> /// <summary>
/// By reading the header on the provided stream this calculates the images format type. /// By reading the header on the provided stream this calculates the images format type.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <returns>The format type or null if none found.</returns> /// <returns>The format type or null if none found.</returns>
public static IImageFormat DetectFormat(Configuration configuration, Stream stream) public static IImageFormat DetectFormat(Configuration configuration, Stream stream)
=> WithSeekableStream(configuration, stream, false, s => InternalDetectFormat(s, configuration)); => 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. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _); public static IImageInfo Identify(Stream stream)
=> Identify(stream, out IImageFormat _);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
/// </summary> /// </summary>
/// <param name="stream">The image stream to read the header from.</param> /// <param name="stream">The image stream to read the header from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found. /// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns> /// </returns>
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);
/// <summary> /// <summary>
/// Reads the raw image information from the specified stream without fully decoding it. /// Reads the raw image information from the specified stream without fully decoding it.
@ -61,7 +71,10 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</param> /// <param name="stream">The image stream to read the information from.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns> /// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found. /// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns> /// </returns>
@ -79,18 +92,23 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
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);
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder. /// The pixel format is selected by the decoder.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Stream stream) => Load(Configuration.Default, stream); public static Image Load(Stream stream) => Load(Configuration.Default, stream);
@ -100,10 +118,14 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
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);
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -112,28 +134,38 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns>> /// <returns>A new <see cref="Image"/>.</returns>>
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) => public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
WithSeekableStream(configuration, stream, true, s => decoder.Decode(configuration, s)); => WithSeekableStream(configuration, stream, true, s => decoder.Decode(configuration, s));
/// <summary> /// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream. /// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="configuration">The configuration for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns>> /// <returns>A new <see cref="Image"/>.</returns>>
public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); public static Image Load(Configuration configuration, Stream stream)
=> Load(configuration, stream, out _);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream) public static Image<TPixel> Load<TPixel>(Stream stream)
@ -145,8 +177,10 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format) public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
@ -158,8 +192,10 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
@ -172,8 +208,11 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The Configuration.</param> /// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder)
@ -185,8 +224,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream) public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream)
@ -199,14 +241,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, out IImageFormat format) public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration));
(Image<TPixel> img, IImageFormat format) data = WithSeekableStream(configuration, stream, true, s => Decode<TPixel>(s, configuration)); (Image<TPixel> img, IImageFormat format) data = WithSeekableStream(configuration, stream, true, s => Decode<TPixel>(s, configuration));
format = data.format; format = data.format;
@ -221,7 +265,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders) foreach (KeyValuePair<IImageFormat, IImageDecoder> 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()); throw new UnknownImageFormatException(sb.ToString());
@ -234,12 +278,14 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param> /// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) 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)); (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, true, s => Decode(s, configuration));
format = data.format; format = data.format;
@ -254,7 +300,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair<IImageFormat, IImageDecoder> val in configuration.ImageFormatsManager.ImageDecoders) foreach (KeyValuePair<IImageFormat, IImageDecoder> 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()); throw new UnknownImageFormatException(sb.ToString());
@ -262,6 +308,9 @@ namespace SixLabors.ImageSharp
private static T WithSeekableStream<T>(Configuration configuration, Stream stream, bool buffer, Func<Stream, T> action) private static T WithSeekableStream<T>(Configuration configuration, Stream stream, bool buffer, Func<Stream, T> action)
{ {
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(stream, nameof(stream));
if (!stream.CanRead) if (!stream.CanRead)
{ {
throw new NotSupportedException("Cannot read from the stream."); throw new NotSupportedException("Cannot read from the stream.");

43
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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(TPixel[] data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -45,6 +46,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(byte[] data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(byte[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -57,6 +59,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -65,60 +68,68 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, byte[] data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, byte[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(new ReadOnlySpan<byte>(data)), width, height); => LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(new ReadOnlySpan<byte>(data)), width, height);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, ReadOnlySpan<byte> data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<byte> data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(data), width, height); => LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The Span containing the image Pixel data.</param> /// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, TPixel[] data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => LoadPixelData(configuration, new ReadOnlySpan<TPixel>(data), width, height);
return LoadPixelData(config, new ReadOnlySpan<TPixel>(data), width, height);
}
/// <summary> /// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data. /// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary> /// </summary>
/// <param name="config">The config for the decoder.</param> /// <param name="configuration">The configuration for the decoder.</param>
/// <param name="data">The Span containing the image Pixel data.</param> /// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="width">The width of the final image.</param> /// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param> /// <param name="height">The height of the final image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, ReadOnlySpan<TPixel> data, int width, int height) public static Image<TPixel> LoadPixelData<TPixel>(Configuration configuration, ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration));
int count = width * height; int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image<TPixel>(config, width, height); var image = new Image<TPixel>(configuration, width, height);
data = data.Slice(0, count); data = data.Slice(0, count);
data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup);

50
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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -20,22 +20,27 @@ namespace SixLabors.ImageSharp
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance. /// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam> /// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="config">The <see cref="ImageSharp.Configuration"/></param> /// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="pixelMemory">The pixel memory.</param> /// <param name="pixelMemory">The pixel memory.</param>
/// <param name="width">The width of the memory image.</param> /// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param> /// <param name="height">The height of the memory image.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param> /// <param name="metadata">The <see cref="ImageMetadata"/>.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The metadata is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns> /// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>( public static Image<TPixel> WrapMemory<TPixel>(
Configuration config, Configuration configuration,
Memory<TPixel> pixelMemory, Memory<TPixel> pixelMemory,
int width, int width,
int height, int height,
ImageMetadata metadata) ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory); var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory);
return new Image<TPixel>(config, memorySource, width, height, metadata); return new Image<TPixel>(configuration, memorySource, width, height, metadata);
} }
/// <summary> /// <summary>
@ -43,20 +48,19 @@ namespace SixLabors.ImageSharp
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance. /// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam> /// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="config">The <see cref="ImageSharp.Configuration"/></param> /// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="pixelMemory">The pixel memory.</param> /// <param name="pixelMemory">The pixel memory.</param>
/// <param name="width">The width of the memory image.</param> /// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param> /// <param name="height">The height of the memory image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns> /// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
public static Image<TPixel> WrapMemory<TPixel>( public static Image<TPixel> WrapMemory<TPixel>(
Configuration config, Configuration configuration,
Memory<TPixel> pixelMemory, Memory<TPixel> pixelMemory,
int width, int width,
int height) int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata());
return WrapMemory(config, pixelMemory, width, height, new ImageMetadata());
}
/// <summary> /// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels, /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@ -73,9 +77,7 @@ namespace SixLabors.ImageSharp
int width, int width,
int height) int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => WrapMemory(Configuration.Default, pixelMemory, width, height);
return WrapMemory(Configuration.Default, pixelMemory, width, height);
}
/// <summary> /// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels, /// 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. /// It will be disposed together with the result image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam> /// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="config">The <see cref="ImageSharp.Configuration"/></param> /// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image</param> /// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image</param>
/// <param name="width">The width of the memory image.</param> /// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param> /// <param name="height">The height of the memory image.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/></param> /// <param name="metadata">The <see cref="ImageMetadata"/></param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The metadata is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns> /// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>( public static Image<TPixel> WrapMemory<TPixel>(
Configuration config, Configuration configuration,
IMemoryOwner<TPixel> pixelMemoryOwner, IMemoryOwner<TPixel> pixelMemoryOwner,
int width, int width,
int height, int height,
ImageMetadata metadata) ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner); var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner);
return new Image<TPixel>(config, memorySource, width, height, metadata); return new Image<TPixel>(configuration, memorySource, width, height, metadata);
} }
/// <summary> /// <summary>
@ -111,20 +118,19 @@ namespace SixLabors.ImageSharp
/// It will be disposed together with the result image. /// It will be disposed together with the result image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam> /// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="config">The <see cref="ImageSharp.Configuration"/></param> /// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image.</param> /// <param name="pixelMemoryOwner">The <see cref="IMemoryOwner{T}"/> that is being transferred to the image.</param>
/// <param name="width">The width of the memory image.</param> /// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param> /// <param name="height">The height of the memory image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns> /// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static Image<TPixel> WrapMemory<TPixel>( public static Image<TPixel> WrapMemory<TPixel>(
Configuration config, Configuration configuration,
IMemoryOwner<TPixel> pixelMemoryOwner, IMemoryOwner<TPixel> pixelMemoryOwner,
int width, int width,
int height) int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata());
return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata());
}
/// <summary> /// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels, /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@ -143,8 +149,6 @@ namespace SixLabors.ImageSharp
int width, int width,
int height) int height)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
} }
} }

46
src/ImageSharp/ImageExtensions.cs

@ -7,12 +7,11 @@ using System.IO;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
/// <summary> /// <summary>
/// Extension methods over Image{TPixel}. /// Extension methods for the <see cref="Image"/> type.
/// </summary> /// </summary>
public static partial class ImageExtensions 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. /// Writes the image to the given stream using the currently loaded image format.
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
public static void Save(this Image source, string filePath) 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); IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
if (format is null) if (format is null)
{ {
@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp
sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:"); sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:");
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) 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()); 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:"); sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<IImageFormat, IImageEncoder> enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) foreach (KeyValuePair<IImageFormat, IImageEncoder> 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()); throw new NotSupportedException(sb.ToString());
} }
source.Save(filePath, encoder); source.Save(path, encoder);
} }
/// <summary> /// <summary>
/// Writes the image to the given stream using the currently loaded image format. /// Writes the image to the given stream using the currently loaded image format.
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param> /// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param> /// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">Thrown if the encoder is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
public static void Save(this Image source, string filePath, IImageEncoder encoder) /// <exception cref="ArgumentNullException">The encoder is null.</exception>
public static void Save(this Image source, string path, IImageEncoder encoder)
{ {
Guard.NotNull(path, nameof(path));
Guard.NotNull(encoder, nameof(encoder)); 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); source.Save(fs, encoder);
} }
@ -79,10 +80,20 @@ namespace SixLabors.ImageSharp
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</param> /// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image in.</param> /// <param name="format">The format to save the image in.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The format is null.</exception>
/// <exception cref="NotSupportedException">The stream is not writable.</exception>
/// <exception cref="NotSupportedException">No encoder available for provided format.</exception>
public static void Save(this Image source, Stream stream, IImageFormat format) public static void Save(this Image source, Stream stream, IImageFormat format)
{ {
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(format, nameof(format)); Guard.NotNull(format, nameof(format));
if (!stream.CanWrite)
{
throw new NotSupportedException("Cannot write to the stream.");
}
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null) if (encoder is null)
@ -92,7 +103,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair<IImageFormat, IImageEncoder> val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) foreach (KeyValuePair<IImageFormat, IImageEncoder> 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()); throw new NotSupportedException(sb.ToString());
@ -113,9 +124,12 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="format">The format.</param> /// <param name="format">The format.</param>
/// <exception cref="ArgumentNullException">The format is null.</exception>
/// <returns>The <see cref="string"/></returns> /// <returns>The <see cref="string"/></returns>
public static string ToBase64String(this Image source, IImageFormat format) public static string ToBase64String(this Image source, IImageFormat format)
{ {
Guard.NotNull(format, nameof(format));
using var stream = new MemoryStream(); using var stream = new MemoryStream();
source.Save(stream, format); source.Save(stream, format);

4
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Memory
{ {
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, 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);
} }
/// <summary> /// <summary>
@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Memory
} }
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
private Memory<T> GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * this.Width, this.Width); private Memory<T> GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width);
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
private Memory<T> GetSingleMemorySlow() => this.FastMemoryGroup.Single(); private Memory<T> GetSingleMemorySlow() => this.FastMemoryGroup.Single();

10
src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs

@ -33,5 +33,15 @@ namespace SixLabors.ImageSharp.Memory
/// the image buffers internally. /// the image buffers internally.
/// </remarks> /// </remarks>
bool IsValid { get; } bool IsValid { get; }
/// <summary>
/// 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 <see langword="foreach"/> block on
/// the <see cref="IMemoryGroup{T}"/> instance in use and the C# compiler will automatically invoke this
/// method behind the scenes. This method takes precedence over the <see cref="IEnumerable{T}.GetEnumerator"/>
/// implementation, which is still available when casting to one of the underlying interfaces.
/// </summary>
/// <returns>A new <see cref="MemoryGroupEnumerator{T}"/> instance mapping the current <see cref="Memory{T}"/> values in use.</returns>
new MemoryGroupEnumerator<T> GetEnumerator();
} }
} }

69
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
{
/// <summary>
/// A value-type enumerator for <see cref="MemoryGroup{T}"/> instances.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
public ref struct MemoryGroupEnumerator<T>
where T : struct
{
private readonly IMemoryGroup<T> memoryGroup;
private readonly int count;
private int index;
[MethodImpl(InliningOptions.ShortMethod)]
internal MemoryGroupEnumerator(MemoryGroup<T>.Owned memoryGroup)
{
this.memoryGroup = memoryGroup;
this.count = memoryGroup.Count;
this.index = -1;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal MemoryGroupEnumerator(MemoryGroup<T>.Consumed memoryGroup)
{
this.memoryGroup = memoryGroup;
this.count = memoryGroup.Count;
this.index = -1;
}
[MethodImpl(InliningOptions.ShortMethod)]
internal MemoryGroupEnumerator(MemoryGroupView<T> memoryGroup)
{
this.memoryGroup = memoryGroup;
this.count = memoryGroup.Count;
this.index = -1;
}
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
public Memory<T> Current
{
[MethodImpl(InliningOptions.ShortMethod)]
get => this.memoryGroup[this.index];
}
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
[MethodImpl(InliningOptions.ShortMethod)]
public bool MoveNext()
{
int index = this.index + 1;
if (index < this.count)
{
this.index = index;
return true;
}
return false;
}
}
}

8
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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -38,6 +38,12 @@ namespace SixLabors.ImageSharp.Memory
Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); Guard.MustBeLessThan(start, group.TotalLength, nameof(start));
int bufferIdx = (int)(start / group.BufferLength); int bufferIdx = (int)(start / group.BufferLength);
if (bufferIdx < 0)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (bufferIdx >= group.Count) if (bufferIdx >= group.Count)
{ {
throw new ArgumentOutOfRangeException(nameof(start)); throw new ArgumentOutOfRangeException(nameof(start));

15
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs

@ -5,6 +5,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory namespace SixLabors.ImageSharp.Memory
{ {
@ -37,6 +38,7 @@ namespace SixLabors.ImageSharp.Memory
public int Count public int Count
{ {
[MethodImpl(InliningOptions.ShortMethod)]
get get
{ {
this.EnsureIsValid(); this.EnsureIsValid();
@ -73,7 +75,15 @@ namespace SixLabors.ImageSharp.Memory
} }
} }
public IEnumerator<Memory<T>> GetEnumerator() /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public MemoryGroupEnumerator<T> GetEnumerator()
{
return new MemoryGroupEnumerator<T>(this);
}
/// <inheritdoc/>
IEnumerator<Memory<T>> IEnumerable<Memory<T>>.GetEnumerator()
{ {
this.EnsureIsValid(); this.EnsureIsValid();
for (int i = 0; i < this.Count; i++) for (int i = 0; i < this.Count; i++)
@ -82,7 +92,8 @@ namespace SixLabors.ImageSharp.Memory
} }
} }
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<Memory<T>>)this).GetEnumerator();
internal void Invalidate() internal void Invalidate()
{ {

36
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs

@ -2,16 +2,17 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory namespace SixLabors.ImageSharp.Memory
{ {
internal abstract partial class MemoryGroup<T> internal abstract partial class MemoryGroup<T>
{ {
// Analogous to the "consumed" variant of MemorySource /// <summary>
private sealed class Consumed : MemoryGroup<T> /// A <see cref="MemoryGroup{T}"/> implementation that consumes the underlying memory buffers.
/// </summary>
public sealed class Consumed : MemoryGroup<T>, IEnumerable<Memory<T>>
{ {
private readonly Memory<T>[] source; private readonly Memory<T>[] source;
@ -22,16 +23,31 @@ namespace SixLabors.ImageSharp.Memory
this.View = new MemoryGroupView<T>(this); this.View = new MemoryGroupView<T>(this);
} }
public override int Count => this.source.Length; public override int Count
{
[MethodImpl(InliningOptions.ShortMethod)]
get => this.source.Length;
}
public override Memory<T> this[int index] => this.source[index]; public override Memory<T> this[int index] => this.source[index];
public override IEnumerator<Memory<T>> GetEnumerator() /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public override MemoryGroupEnumerator<T> GetEnumerator()
{
return new MemoryGroupEnumerator<T>(this);
}
/// <inheritdoc/>
IEnumerator<Memory<T>> IEnumerable<Memory<T>>.GetEnumerator()
{ {
for (int i = 0; i < this.source.Length; i++) /* The runtime sees the Array class as if it implemented the
{ * type-generic collection interfaces explicitly, so here we
yield return this.source[i]; * can just cast the source array to IList<Memory<T>> (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<Memory<T>>)this.source).GetEnumerator();
} }
public override void Dispose() public override void Dispose()

29
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs

@ -5,13 +5,16 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory namespace SixLabors.ImageSharp.Memory
{ {
// Analogous to the "owned" variant of MemorySource
internal abstract partial class MemoryGroup<T> internal abstract partial class MemoryGroup<T>
{ {
private sealed class Owned : MemoryGroup<T> /// <summary>
/// A <see cref="MemoryGroup{T}"/> implementation that owns the underlying memory buffers.
/// </summary>
public sealed class Owned : MemoryGroup<T>, IEnumerable<Memory<T>>
{ {
private IMemoryOwner<T>[] memoryOwners; private IMemoryOwner<T>[] memoryOwners;
@ -29,6 +32,7 @@ namespace SixLabors.ImageSharp.Memory
public override int Count public override int Count
{ {
[MethodImpl(InliningOptions.ShortMethod)]
get get
{ {
this.EnsureNotDisposed(); this.EnsureNotDisposed();
@ -45,7 +49,15 @@ namespace SixLabors.ImageSharp.Memory
} }
} }
public override IEnumerator<Memory<T>> GetEnumerator() /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public override MemoryGroupEnumerator<T> GetEnumerator()
{
return new MemoryGroupEnumerator<T>(this);
}
/// <inheritdoc/>
IEnumerator<Memory<T>> IEnumerable<Memory<T>>.GetEnumerator()
{ {
this.EnsureNotDisposed(); this.EnsureNotDisposed();
return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator();
@ -69,14 +81,21 @@ namespace SixLabors.ImageSharp.Memory
this.IsValid = false; this.IsValid = false;
} }
[MethodImpl(InliningOptions.ShortMethod)]
private void EnsureNotDisposed() private void EnsureNotDisposed()
{ {
if (this.memoryOwners == null) if (this.memoryOwners is null)
{ {
throw new ObjectDisposedException(nameof(MemoryGroup<T>)); ThrowObjectDisposedException();
} }
} }
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(MemoryGroup<T>));
}
internal static void SwapContents(Owned a, Owned b) internal static void SwapContents(Owned a, Owned b)
{ {
a.EnsureNotDisposed(); a.EnsureNotDisposed();

16
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory namespace SixLabors.ImageSharp.Memory
{ {
@ -48,10 +47,21 @@ namespace SixLabors.ImageSharp.Memory
public abstract void Dispose(); public abstract void Dispose();
/// <inheritdoc /> /// <inheritdoc />
public abstract IEnumerator<Memory<T>> GetEnumerator(); public abstract MemoryGroupEnumerator<T> GetEnumerator();
/// <inheritdoc /> /// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); IEnumerator<Memory<T>> IEnumerable<Memory<T>>.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<Memory<T>>.GetEnumerator()");
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<Memory<T>>)this).GetEnumerator();
/// <summary> /// <summary>
/// Creates a new memory group, allocating it's buffers with the provided allocator. /// Creates a new memory group, allocating it's buffers with the provided allocator.

7
src/ImageSharp/Metadata/ImageMetadata.cs

@ -5,6 +5,7 @@ using System.Collections.Generic;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Metadata namespace SixLabors.ImageSharp.Metadata
{ {
@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Metadata
this.ExifProfile = other.ExifProfile?.DeepClone(); this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone(); this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
} }
/// <summary> /// <summary>
@ -122,6 +124,11 @@ namespace SixLabors.ImageSharp.Metadata
/// </summary> /// </summary>
public IccProfile IccProfile { get; set; } public IccProfile IccProfile { get; set; }
/// <summary>
/// Gets or sets the iptc profile.
/// </summary>
public IptcProfile IptcProfile { get; set; }
/// <summary> /// <summary>
/// Gets the metadata value associated with the specified key. /// Gets the metadata value associated with the specified key.
/// </summary> /// </summary>

3
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. /// by making a copy from another EXIF profile.
/// </summary> /// </summary>
/// <param name="other">The other EXIF profile, where the clone should be made from.</param> /// <param name="other">The other EXIF profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>>
private ExifProfile(ExifProfile other) private ExifProfile(ExifProfile other)
{ {
Guard.NotNull(other, nameof(other));
this.Parts = other.Parts; this.Parts = other.Parts;
this.thumbnailLength = other.thumbnailLength; this.thumbnailLength = other.thumbnailLength;
this.thumbnailOffset = other.thumbnailOffset; this.thumbnailOffset = other.thumbnailOffset;

BIN
src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf

Binary file not shown.

298
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
{
/// <summary>
/// Represents an IPTC profile providing access to the collection of values.
/// </summary>
public sealed class IptcProfile : IDeepCloneable<IptcProfile>
{
private Collection<IptcValue> values;
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
public IptcProfile()
: this((byte[])null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
/// <param name="data">The byte array to read the iptc profile from.</param>
public IptcProfile(byte[] data)
{
this.Data = data;
this.Initialize();
}
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class
/// by making a copy from another IPTC profile.
/// </summary>
/// <param name="other">The other IPTC profile, from which the clone should be made from.</param>
private IptcProfile(IptcProfile other)
{
Guard.NotNull(other, nameof(other));
if (other.values != null)
{
this.values = new Collection<IptcValue>();
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);
}
}
/// <summary>
/// Gets the byte data of the IPTC profile.
/// </summary>
public byte[] Data { get; private set; }
/// <summary>
/// Gets the values of this iptc profile.
/// </summary>
public IEnumerable<IptcValue> Values
{
get
{
this.Initialize();
return this.values;
}
}
/// <inheritdoc/>
public IptcProfile DeepClone() => new IptcProfile(this);
/// <summary>
/// Returns all value with the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <returns>The values found with the specified tag.</returns>
public List<IptcValue> GetValues(IptcTag tag)
{
var iptcValues = new List<IptcValue>();
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
iptcValues.Add(iptcValue);
}
}
return iptcValues;
}
/// <summary>
/// Removes all values with the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value to remove.</param>
/// <returns>True when the value was found and removed.</returns>
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;
}
/// <summary>
/// Removes values with the specified tag and value.
/// </summary>
/// <param name="tag">The tag of the iptc value to remove.</param>
/// <param name="value">The value of the iptc item to remove.</param>
/// <returns>True when the value was found and removed.</returns>
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;
}
/// <summary>
/// Changes the encoding for all the values.
/// </summary>
/// <param name="encoding">The encoding to use when storing the bytes.</param>
public void SetEncoding(Encoding encoding)
{
Guard.NotNull(encoding, nameof(encoding));
foreach (IptcValue value in this.Values)
{
value.Encoding = encoding;
}
}
/// <summary>
/// Sets the value for the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="encoding">The encoding to use when storing the bytes.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
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));
}
/// <summary>
/// Makes sure the datetime is formatted according to the iptc specification.
/// <example>
/// 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.
/// </example>
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="dateTimeOffset">The datetime.</param>
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);
}
/// <summary>
/// Sets the value of the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary>
/// Updates the data of the profile.
/// </summary>
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<IptcValue>();
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;
}
}
}
}

397
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
{
/// <summary>
/// Provides enumeration of all IPTC tags relevant for images.
/// </summary>
public enum IptcTag
{
/// <summary>
/// Unknown.
/// </summary>
Unknown = -1,
/// <summary>
/// Record version identifying the version of the Information Interchange Model.
/// Not repeatable. Max length is 2.
/// </summary>
RecordVersion = 0,
/// <summary>
/// Object type, not repeatable. Max Length is 67.
/// </summary>
ObjectType = 3,
/// <summary>
/// Object attribute. Max length is 68.
/// </summary>
ObjectAttribute = 4,
/// <summary>
/// Object Name, not repeatable. Max length is 64.
/// </summary>
Name = 5,
/// <summary>
/// Edit status, not repeatable. Max length is 64.
/// </summary>
EditStatus = 7,
/// <summary>
/// Editorial update, not repeatable. Max length is 2.
/// </summary>
EditorialUpdate = 8,
/// <summary>
/// Urgency, not repeatable. Max length is 2.
/// </summary>
Urgency = 10,
/// <summary>
/// Subject Reference. Max length is 236.
/// </summary>
SubjectReference = 12,
/// <summary>
/// Category, not repeatable. Max length is 3.
/// </summary>
Category = 15,
/// <summary>
/// Supplemental categories. Max length is 32.
/// </summary>
SupplementalCategories = 20,
/// <summary>
/// Fixture identifier, not repeatable. Max length is 32.
/// </summary>
FixtureIdentifier = 22,
/// <summary>
/// Keywords. Max length is 64.
/// </summary>
Keywords = 25,
/// <summary>
/// Location code. Max length is 3.
/// </summary>
LocationCode = 26,
/// <summary>
/// Location name. Max length is 64.
/// </summary>
LocationName = 27,
/// <summary>
/// Release date. Format should be CCYYMMDD.
/// Not repeatable, max length is 8.
/// <example>
/// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
/// </example>
/// </summary>
ReleaseDate = 30,
/// <summary>
/// Release time. Format should be HHMMSS±HHMM.
/// Not repeatable, max length is 11.
/// <example>
/// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
/// two hours ahead of UTC.
/// </example>
/// </summary>
ReleaseTime = 35,
/// <summary>
/// Expiration date. Format should be CCYYMMDD.
/// Not repeatable, max length is 8.
/// <example>
/// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
/// </example>
/// </summary>
ExpirationDate = 37,
/// <summary>
/// Expiration time. Format should be HHMMSS±HHMM.
/// Not repeatable, max length is 11.
/// <example>
/// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
/// two hours ahead of UTC.
/// </example>
/// </summary>
ExpirationTime = 38,
/// <summary>
/// Special instructions, not repeatable. Max length is 256.
/// </summary>
SpecialInstructions = 40,
/// <summary>
/// Action advised, not repeatable. Max length is 2.
/// </summary>
ActionAdvised = 42,
/// <summary>
/// Reference service. Max length is 10.
/// </summary>
ReferenceService = 45,
/// <summary>
/// Reference date. Format should be CCYYMMDD.
/// Not repeatable, max length is 8.
/// <example>
/// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
/// </example>
/// </summary>
ReferenceDate = 47,
/// <summary>
/// ReferenceNumber. Max length is 8.
/// </summary>
ReferenceNumber = 50,
/// <summary>
/// Created date. Format should be CCYYMMDD.
/// Not repeatable, max length is 8.
/// <example>
/// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
/// </example>
/// </summary>
CreatedDate = 55,
/// <summary>
/// Created time. Format should be HHMMSS±HHMM.
/// Not repeatable, max length is 11.
/// <example>
/// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
/// two hours ahead of UTC.
/// </example>
/// </summary>
CreatedTime = 60,
/// <summary>
/// Digital creation date. Format should be CCYYMMDD.
/// Not repeatable, max length is 8.
/// <example>
/// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
/// </example>
/// </summary>
DigitalCreationDate = 62,
/// <summary>
/// Digital creation time. Format should be HHMMSS±HHMM.
/// Not repeatable, max length is 11.
/// <example>
/// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
/// two hours ahead of UTC.
/// </example>
/// </summary>
DigitalCreationTime = 63,
/// <summary>
/// Originating program, not repeatable. Max length is 32.
/// </summary>
OriginatingProgram = 65,
/// <summary>
/// Program version, not repeatable. Max length is 10.
/// </summary>
ProgramVersion = 70,
/// <summary>
/// Object cycle, not repeatable. Max length is 1.
/// </summary>
ObjectCycle = 75,
/// <summary>
/// Byline. Max length is 32.
/// </summary>
Byline = 80,
/// <summary>
/// Byline title. Max length is 32.
/// </summary>
BylineTitle = 85,
/// <summary>
/// City, not repeatable. Max length is 32.
/// </summary>
City = 90,
/// <summary>
/// Sub location, not repeatable. Max length is 32.
/// </summary>
SubLocation = 92,
/// <summary>
/// Province/State, not repeatable. Max length is 32.
/// </summary>
ProvinceState = 95,
/// <summary>
/// Country code, not repeatable. Max length is 3.
/// </summary>
CountryCode = 100,
/// <summary>
/// Country, not repeatable. Max length is 64.
/// </summary>
Country = 101,
/// <summary>
/// Original transmission reference, not repeatable. Max length is 32.
/// </summary>
OriginalTransmissionReference = 103,
/// <summary>
/// Headline, not repeatable. Max length is 256.
/// </summary>
Headline = 105,
/// <summary>
/// Credit, not repeatable. Max length is 32.
/// </summary>
Credit = 110,
/// <summary>
/// Source, not repeatable. Max length is 32.
/// </summary>
Source = 115,
/// <summary>
/// Copyright notice, not repeatable. Max length is 128.
/// </summary>
CopyrightNotice = 116,
/// <summary>
/// Contact. Max length 128.
/// </summary>
Contact = 118,
/// <summary>
/// Caption, not repeatable. Max length is 2000.
/// </summary>
Caption = 120,
/// <summary>
/// Local caption.
/// </summary>
LocalCaption = 121,
/// <summary>
/// Caption writer. Max length is 32.
/// </summary>
CaptionWriter = 122,
/// <summary>
/// Image type, not repeatable. Max length is 2.
/// </summary>
ImageType = 130,
/// <summary>
/// Image orientation, not repeatable. Max length is 1.
/// </summary>
ImageOrientation = 131,
/// <summary>
/// Custom field 1
/// </summary>
CustomField1 = 200,
/// <summary>
/// Custom field 2
/// </summary>
CustomField2 = 201,
/// <summary>
/// Custom field 3
/// </summary>
CustomField3 = 202,
/// <summary>
/// Custom field 4
/// </summary>
CustomField4 = 203,
/// <summary>
/// Custom field 5
/// </summary>
CustomField5 = 204,
/// <summary>
/// Custom field 6
/// </summary>
CustomField6 = 205,
/// <summary>
/// Custom field 7
/// </summary>
CustomField7 = 206,
/// <summary>
/// Custom field 8
/// </summary>
CustomField8 = 207,
/// <summary>
/// Custom field 9
/// </summary>
CustomField9 = 208,
/// <summary>
/// Custom field 10
/// </summary>
CustomField10 = 209,
/// <summary>
/// Custom field 11
/// </summary>
CustomField11 = 210,
/// <summary>
/// Custom field 12
/// </summary>
CustomField12 = 211,
/// <summary>
/// Custom field 13
/// </summary>
CustomField13 = 212,
/// <summary>
/// Custom field 14
/// </summary>
CustomField14 = 213,
/// <summary>
/// Custom field 15
/// </summary>
CustomField15 = 214,
/// <summary>
/// Custom field 16
/// </summary>
CustomField16 = 215,
/// <summary>
/// Custom field 17
/// </summary>
CustomField17 = 216,
/// <summary>
/// Custom field 18
/// </summary>
CustomField18 = 217,
/// <summary>
/// Custom field 19
/// </summary>
CustomField19 = 218,
/// <summary>
/// Custom field 20
/// </summary>
CustomField20 = 219,
}
}

162
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
{
/// <summary>
/// Extension methods for IPTC tags.
/// </summary>
public static class IptcTagExtensions
{
/// <summary>
/// Maximum length of the IPTC value with the given tag according to the specification.
/// </summary>
/// <param name="tag">The tag to check the max length for.</param>
/// <returns>The maximum length.</returns>
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
};
}
/// <summary>
/// Determines if the given tag can be repeated according to the specification.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if the tag can occur multiple times.</returns>
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;
}
}
/// <summary>
/// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if its a datetime tag.</returns>
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;
}
}
/// <summary>
/// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if its a time tag.</returns>
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;
}
}
}
}

219
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
{
/// <summary>
/// Represents a single value of the IPTC profile.
/// </summary>
public sealed class IptcValue : IDeepCloneable<IptcValue>
{
private byte[] data = Array.Empty<byte>();
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;
}
/// <summary>
/// Gets or sets the encoding to use for the Value.
/// </summary>
public Encoding Encoding
{
get => this.encoding;
set
{
if (value != null)
{
this.encoding = value;
}
}
}
/// <summary>
/// Gets the tag of the iptc value.
/// </summary>
public IptcTag Tag { get; }
/// <summary>
/// Gets or sets a value indicating whether to be enforce value length restrictions according
/// to the specification.
/// </summary>
public bool Strict { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
public string Value
{
get => this.encoding.GetString(this.data);
set
{
if (string.IsNullOrEmpty(value))
{
this.data = Array.Empty<byte>();
}
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;
}
}
}
/// <summary>
/// Gets the length of the value.
/// </summary>
public int Length => this.data.Length;
/// <inheritdoc/>
public IptcValue DeepClone() => new IptcValue(this);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="IptcValue"/>.
/// </summary>
/// <param name="obj">The object to compare this <see cref="IptcValue"/> with.</param>
/// <returns>True when the specified object is equal to the current <see cref="IptcValue"/>.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
return this.Equals(obj as IptcValue);
}
/// <summary>
/// Determines whether the specified iptc value is equal to the current <see cref="IptcValue"/>.
/// </summary>
/// <param name="other">The iptc value to compare this <see cref="IptcValue"/> with.</param>
/// <returns>True when the specified iptc value is equal to the current <see cref="IptcValue"/>.</returns>
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;
}
/// <summary>
/// Serves as a hash of this type.
/// </summary>
/// <returns>A hash code for the current instance.</returns>
public override int GetHashCode() => HashCode.Combine(this.data, this.Tag);
/// <summary>
/// Converts this instance to a byte array.
/// </summary>
/// <returns>A <see cref="byte"/> array.</returns>
public byte[] ToByteArray()
{
var result = new byte[this.data.Length];
this.data.CopyTo(result, 0);
return result;
}
/// <summary>
/// Returns a string that represents the current value.
/// </summary>
/// <returns>A string that represents the current value.</returns>
public override string ToString() => this.Value;
/// <summary>
/// Returns a string that represents the current value with the specified encoding.
/// </summary>
/// <param name="encoding">The encoding to use.</param>
/// <returns>A string that represents the current value with the specified encoding.</returns>
public string ToString(Encoding encoding)
{
Guard.NotNull(encoding, nameof(encoding));
return encoding.GetString(this.data);
}
}
}

11
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)

4
src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.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. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
@ -39,6 +40,9 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc/> /// <inheritdoc/>
public Configuration Configuration { get; } public Configuration Configuration { get; }
/// <inheritdoc/>
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> GetResultImage() public Image<TPixel> GetResultImage()
{ {

8
src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing
Image image, Image image,
float opacity) float opacity)
{ {
var options = new GraphicsOptions(); var options = source.GetGraphicsOptions();
return source.ApplyProcessor( return source.ApplyProcessor(
new DrawImageProcessor( new DrawImageProcessor(
image, image,
@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing
image, image,
Point.Empty, Point.Empty,
colorBlending, colorBlending,
new GraphicsOptions().AlphaCompositionMode, source.GetGraphicsOptions().AlphaCompositionMode,
opacity)); opacity));
/// <summary> /// <summary>
@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Processing
Point location, Point location,
float opacity) float opacity)
{ {
var options = new GraphicsOptions(); var options = source.GetGraphicsOptions();
return source.ApplyProcessor( return source.ApplyProcessor(
new DrawImageProcessor( new DrawImageProcessor(
image, image,
@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing
image, image,
location, location,
colorBlending, colorBlending,
new GraphicsOptions().AlphaCompositionMode, source.GetGraphicsOptions().AlphaCompositionMode,
opacity)); opacity));
/// <summary> /// <summary>

8
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. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors.Filters;
@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Lomograph(this IImageProcessingContext source) public static IImageProcessingContext Lomograph(this IImageProcessingContext source)
=> source.ApplyProcessor(new LomographProcessor()); => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()));
/// <summary> /// <summary>
/// Alters the colors of the image recreating an old Lomograph camera effect. /// Alters the colors of the image recreating an old Lomograph camera effect.
@ -28,6 +28,6 @@ namespace SixLabors.ImageSharp.Processing
/// </param> /// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle) public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle)
=> source.ApplyProcessor(new LomographProcessor(), rectangle); => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()), rectangle);
} }
} }

6
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. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors.Filters;
@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Polaroid(this IImageProcessingContext source) public static IImageProcessingContext Polaroid(this IImageProcessingContext source)
=> source.ApplyProcessor(new PolaroidProcessor()); => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()));
/// <summary> /// <summary>
/// Alters the colors of the image recreating an old Polaroid camera effect. /// Alters the colors of the image recreating an old Polaroid camera effect.
@ -28,6 +28,6 @@ namespace SixLabors.ImageSharp.Processing
/// </param> /// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle) public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle)
=> source.ApplyProcessor(new PolaroidProcessor(), rectangle); => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()), rectangle);
} }
} }

4
src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="color">The color to set as the background.</param> /// <param name="color">The color to set as the background.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) =>
BackgroundColor(source, new GraphicsOptions(), color); BackgroundColor(source, source.GetGraphicsOptions(), color);
/// <summary> /// <summary>
/// Replaces the background color of image with the given one. /// Replaces the background color of image with the given one.
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source, this IImageProcessingContext source,
Color color, Color color,
Rectangle rectangle) => Rectangle rectangle) =>
BackgroundColor(source, new GraphicsOptions(), color, rectangle); BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle);
/// <summary> /// <summary>
/// Replaces the background color of image with the given one. /// Replaces the background color of image with the given one.

10
src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source) => public static IImageProcessingContext Glow(this IImageProcessingContext source) =>
Glow(source, new GraphicsOptions()); Glow(source, source.GetGraphicsOptions());
/// <summary> /// <summary>
/// Applies a radial glow effect to an image. /// Applies a radial glow effect to an image.
@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color)
{ {
return Glow(source, new GraphicsOptions(), color); return Glow(source, source.GetGraphicsOptions(), color);
} }
/// <summary> /// <summary>
@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radius">The the radius.</param> /// <param name="radius">The the radius.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) =>
Glow(source, new GraphicsOptions(), radius); Glow(source, source.GetGraphicsOptions(), radius);
/// <summary> /// <summary>
/// Applies a radial glow effect to an image. /// Applies a radial glow effect to an image.
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param> /// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) => public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) =>
source.Glow(new GraphicsOptions(), rectangle); source.Glow(source.GetGraphicsOptions(), rectangle);
/// <summary> /// <summary>
/// Applies a radial glow effect to an image. /// Applies a radial glow effect to an image.
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing
Color color, Color color,
float radius, float radius,
Rectangle rectangle) => Rectangle rectangle) =>
source.Glow(new GraphicsOptions(), color, ValueSize.Absolute(radius), rectangle); source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
/// <summary> /// <summary>
/// Applies a radial glow effect to an image. /// Applies a radial glow effect to an image.

10
src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source) => public static IImageProcessingContext Vignette(this IImageProcessingContext source) =>
Vignette(source, new GraphicsOptions()); Vignette(source, source.GetGraphicsOptions());
/// <summary> /// <summary>
/// Applies a radial vignette effect to an image. /// Applies a radial vignette effect to an image.
@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="color">The color to set as the vignette.</param> /// <param name="color">The color to set as the vignette.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) =>
Vignette(source, new GraphicsOptions(), color); Vignette(source, source.GetGraphicsOptions(), color);
/// <summary> /// <summary>
/// Applies a radial vignette effect to an image. /// Applies a radial vignette effect to an image.
@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source, this IImageProcessingContext source,
float radiusX, float radiusX,
float radiusY) => float radiusY) =>
Vignette(source, new GraphicsOptions(), radiusX, radiusY); Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY);
/// <summary> /// <summary>
/// Applies a radial vignette effect to an image. /// Applies a radial vignette effect to an image.
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param> /// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) => public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) =>
Vignette(source, new GraphicsOptions(), rectangle); Vignette(source, source.GetGraphicsOptions(), rectangle);
/// <summary> /// <summary>
/// Applies a radial vignette effect to an image. /// Applies a radial vignette effect to an image.
@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing
float radiusX, float radiusX,
float radiusY, float radiusY,
Rectangle rectangle) => Rectangle rectangle) =>
source.Vignette(new GraphicsOptions(), color, radiusX, radiusY, rectangle); source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle);
/// <summary> /// <summary>
/// Applies a radial vignette effect to an image. /// Applies a radial vignette effect to an image.

67
src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs

@ -19,6 +19,10 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
/// <param name="source">The image to mutate.</param> /// <param name="source">The image to mutate.</param>
/// <param name="operation">The operation to perform on the source.</param> /// <param name="operation">The operation to perform on the source.</param>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate(this Image source, Action<IImageProcessingContext> operation) public static void Mutate(this Image source, Action<IImageProcessingContext> operation)
=> Mutate(source, source.GetConfiguration(), operation); => Mutate(source, source.GetConfiguration(), operation);
@ -28,6 +32,11 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image to mutate.</param> /// <param name="source">The image to mutate.</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="operation">The operation to perform on the source.</param> /// <param name="operation">The operation to perform on the source.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate(this Image source, Configuration configuration, Action<IImageProcessingContext> operation) public static void Mutate(this Image source, Configuration configuration, Action<IImageProcessingContext> operation)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
@ -44,6 +53,10 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to mutate.</param> /// <param name="source">The image to mutate.</param>
/// <param name="operation">The operation to perform on the source.</param> /// <param name="operation">The operation to perform on the source.</param>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation) public static void Mutate<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Mutate(source, source.GetConfiguration(), operation); => Mutate(source, source.GetConfiguration(), operation);
@ -55,6 +68,11 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image to mutate.</param> /// <param name="source">The image to mutate.</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="operation">The operation to perform on the source.</param> /// <param name="operation">The operation to perform on the source.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate<TPixel>(this Image<TPixel> source, Configuration configuration, Action<IImageProcessingContext> operation) public static void Mutate<TPixel>(this Image<TPixel> source, Configuration configuration, Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -75,6 +93,10 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to mutate.</param> /// <param name="source">The image to mutate.</param>
/// <param name="operations">The operations to perform on the source.</param> /// <param name="operations">The operations to perform on the source.</param>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operations are null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations) public static void Mutate<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Mutate(source, source.GetConfiguration(), operations); => Mutate(source, source.GetConfiguration(), operations);
@ -86,6 +108,11 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image to mutate.</param> /// <param name="source">The image to mutate.</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="operations">The operations to perform on the source.</param> /// <param name="operations">The operations to perform on the source.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operations are null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static void Mutate<TPixel>(this Image<TPixel> source, Configuration configuration, params IImageProcessor[] operations) public static void Mutate<TPixel>(this Image<TPixel> source, Configuration configuration, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -104,7 +131,11 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
/// <param name="source">The image to clone.</param> /// <param name="source">The image to clone.</param>
/// <param name="operation">The operation to perform on the clone.</param> /// <param name="operation">The operation to perform on the clone.</param>
/// <returns>The new <see cref="SixLabors.ImageSharp.Image"/>.</returns> /// <returns>The new <see cref="Image"/>.</returns>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
public static Image Clone(this Image source, Action<IImageProcessingContext> operation) public static Image Clone(this Image source, Action<IImageProcessingContext> operation)
=> Clone(source, source.GetConfiguration(), operation); => Clone(source, source.GetConfiguration(), operation);
@ -114,7 +145,12 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image to clone.</param> /// <param name="source">The image to clone.</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="operation">The operation to perform on the clone.</param> /// <param name="operation">The operation to perform on the clone.</param>
/// <returns>The new <see cref="SixLabors.ImageSharp.Image"/>.</returns> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
/// <returns>The new <see cref="Image"/>.</returns>
public static Image Clone(this Image source, Configuration configuration, Action<IImageProcessingContext> operation) public static Image Clone(this Image source, Configuration configuration, Action<IImageProcessingContext> operation)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
@ -133,7 +169,11 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to clone.</param> /// <param name="source">The image to clone.</param>
/// <param name="operation">The operation to perform on the clone.</param> /// <param name="operation">The operation to perform on the clone.</param>
/// <returns>The new <see cref="SixLabors.ImageSharp.Image{TPixel}"/></returns> /// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
/// <returns>The new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation) public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Clone(source, source.GetConfiguration(), operation); => Clone(source, source.GetConfiguration(), operation);
@ -145,7 +185,12 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image to clone.</param> /// <param name="source">The image to clone.</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="operation">The operation to perform on the clone.</param> /// <param name="operation">The operation to perform on the clone.</param>
/// <returns>The new <see cref="SixLabors.ImageSharp.Image{TPixel}"/></returns> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operation is null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
/// <returns>The new <see cref="Image{TPixel}"/></returns>
public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Configuration configuration, Action<IImageProcessingContext> operation) public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Configuration configuration, Action<IImageProcessingContext> operation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -167,7 +212,11 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to clone.</param> /// <param name="source">The image to clone.</param>
/// <param name="operations">The operations to perform on the clone.</param> /// <param name="operations">The operations to perform on the clone.</param>
/// <returns>The new <see cref="SixLabors.ImageSharp.Image{TPixel}"/></returns> /// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operations are null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
/// <returns>The new <see cref="Image{TPixel}"/></returns>
public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations) public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> Clone(source, source.GetConfiguration(), operations); => Clone(source, source.GetConfiguration(), operations);
@ -179,7 +228,12 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image to clone.</param> /// <param name="source">The image to clone.</param>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="operations">The operations to perform on the clone.</param> /// <param name="operations">The operations to perform on the clone.</param>
/// <returns>The new <see cref="SixLabors.ImageSharp.Image{TPixel}"/></returns> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The source is null.</exception>
/// <exception cref="ArgumentNullException">The operations are null.</exception>
/// <exception cref="ObjectDisposedException">The source has been disposed.</exception>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
/// <returns>The new <see cref="Image{TPixel}"/></returns>
public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Configuration configuration, params IImageProcessor[] operations) public static Image<TPixel> Clone<TPixel>(this Image<TPixel> source, Configuration configuration, params IImageProcessor[] operations)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -200,6 +254,7 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
/// <param name="source">The image processing context.</param> /// <param name="source">The image processing context.</param>
/// <param name="operations">The operations to perform on the source.</param> /// <param name="operations">The operations to perform on the source.</param>
/// <exception cref="ImageProcessingException">The processing operation failed.</exception>
/// <returns>The <see cref="IImageProcessor{TPixel}"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessor{TPixel}"/> to allow chaining of operations.</returns>
public static IImageProcessingContext ApplyProcessors( public static IImageProcessingContext ApplyProcessors(
this IImageProcessingContext source, this IImageProcessingContext source,

7
src/ImageSharp/Processing/IImageProcessingContext.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. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Processing
@ -15,6 +16,12 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
Configuration Configuration { get; } Configuration Configuration { get; }
/// <summary>
/// Gets a set of properties for the Image Processing Context.
/// </summary>
/// <remarks>This can be used for storing global settings and defaults to be accessable to processors.</remarks>
IDictionary<object, object> Properties { get; }
/// <summary> /// <summary>
/// Gets the image dimensions at the current point in the processing pipeline. /// Gets the image dimensions at the current point in the processing pipeline.
/// </summary> /// </summary>

9
src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs

@ -11,11 +11,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LomographProcessor" /> class. /// Initializes a new instance of the <see cref="LomographProcessor" /> class.
/// </summary> /// </summary>
public LomographProcessor() /// <param name="graphicsOptions">Graphics options to use within the processor.</param>
public LomographProcessor(GraphicsOptions graphicsOptions)
: base(KnownFilterMatrices.LomographFilter) : base(KnownFilterMatrices.LomographFilter)
{ {
this.GraphicsOptions = graphicsOptions;
} }
/// <summary>
/// Gets the options effecting blending and composition
/// </summary>
public GraphicsOptions GraphicsOptions { get; }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) => public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) =>
new LomographProcessor<TPixel>(configuration, this, source, sourceRectangle); new LomographProcessor<TPixel>(configuration, this, source, sourceRectangle);

4
src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs

@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255); private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255);
private readonly LomographProcessor definition;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LomographProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="LomographProcessor{TPixel}"/> class.
@ -24,12 +25,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
public LomographProcessor(Configuration configuration, LomographProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public LomographProcessor(Configuration configuration, LomographProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, definition, source, sourceRectangle) : base(configuration, definition, source, sourceRectangle)
{ {
this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply() 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(); base.AfterImageApply();
} }
} }

9
src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs

@ -11,11 +11,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PolaroidProcessor" /> class. /// Initializes a new instance of the <see cref="PolaroidProcessor" /> class.
/// </summary> /// </summary>
public PolaroidProcessor() /// <param name="graphicsOptions">Graphics options to use within the processor.</param>
public PolaroidProcessor(GraphicsOptions graphicsOptions)
: base(KnownFilterMatrices.PolaroidFilter) : base(KnownFilterMatrices.PolaroidFilter)
{ {
this.GraphicsOptions = graphicsOptions;
} }
/// <summary>
/// Gets the options effecting blending and composition
/// </summary>
public GraphicsOptions GraphicsOptions { get; }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) => public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) =>
new PolaroidProcessor<TPixel>(configuration, this, source, sourceRectangle); new PolaroidProcessor<TPixel>(configuration, this, source, sourceRectangle);

6
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 LightOrange = Color.FromRgba(255, 153, 102, 128);
private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0); private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0);
private readonly PolaroidProcessor definition;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PolaroidProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="PolaroidProcessor{TPixel}"/> class.
@ -25,13 +26,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, definition, source, sourceRectangle) : base(configuration, definition, source, sourceRectangle)
{ {
this.definition = definition;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply() protected override void AfterImageApply()
{ {
new VignetteProcessor(VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle); new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).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(); base.AfterImageApply();
} }
} }

19
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// </summary> /// </summary>
public sealed class GlowProcessor : IImageProcessor public sealed class GlowProcessor : IImageProcessor
{ {
/// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary>
/// <param name="color">The color or the glow.</param>
public GlowProcessor(Color color)
: this(color, 0)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class. /// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary> /// </summary>
@ -29,16 +20,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary>
/// <param name="color">The color or the glow.</param>
/// <param name="radius">The radius of the glow.</param>
internal GlowProcessor(Color color, ValueSize radius)
: this(new GraphicsOptions(), color, radius)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class. /// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary> /// </summary>

9
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// </summary> /// </summary>
public sealed class VignetteProcessor : IImageProcessor public sealed class VignetteProcessor : IImageProcessor
{ {
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor" /> class.
/// </summary>
/// <param name="color">The color of the vignette.</param>
public VignetteProcessor(Color color)
: this(new GraphicsOptions(), color)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor" /> class. /// Initializes a new instance of the <see cref="VignetteProcessor" /> class.
/// </summary> /// </summary>

83
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<DrawImageProcessor>();
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<DrawImageProcessor>();
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<DrawImageProcessor>();
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<DrawImageProcessor>();
Assert.Equal(0.5, dip.Opacity);
Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode);
}
}
}

4
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -394,10 +394,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[Theory] [Theory]
[WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsImageFormatException_OnInvalidPaletteSize<TPixel>(TestImageProvider<TPixel> provider) public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Assert.Throws<ImageFormatException>(() => Assert.Throws<InvalidImageContentException>(() =>
{ {
using (provider.GetImage(BmpDecoder)) using (provider.GetImage(BmpDecoder))
{ {

4
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)]
public void UnrecoverableImage_Throws_ImageFormatException<TPixel>(TestImageProvider<TPixel> provider) public void UnrecoverableImage_Throws_InvalidImageContentException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<ImageFormatException>(provider.GetImage); where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<InvalidImageContentException>(provider.GetImage);
} }
} }

2
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs

@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder(); var decoder = new PngDecoder();
ImageFormatException exception = ImageFormatException exception =
Assert.Throws<ImageFormatException>(() => decoder.Decode<Rgb24>(null, memStream)); Assert.Throws<InvalidImageContentException>(() => decoder.Decode<Rgb24>(null, memStream));
Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message);
} }

23
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 TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6
}; };
public static readonly string[] TestImagesIssue1177 =
{
TestImages.Png.Issue1177_1,
TestImages.Png.Issue1177_2
};
[Theory] [Theory]
[WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
public void Decode<TPixel>(TestImageProvider<TPixel> provider) public void Decode<TPixel>(TestImageProvider<TPixel> provider)
@ -243,6 +249,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Null(ex); Assert.Null(ex);
} }
[Theory]
[WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)]
public void Issue1177<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
}
});
Assert.Null(ex);
}
[Theory] [Theory]
[WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)] [WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)]
public void Issue1127<TPixel>(TestImageProvider<TPixel> provider) public void Issue1127<TPixel>(TestImageProvider<TPixel> provider)

172
tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs

@ -0,0 +1,172 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public class GraphicOptionsDefaultsExtensionsTests
{
[Fact]
public void SetDefaultOptionsOnProcessingContext()
{
var option = new GraphicsOptions();
var config = new Configuration();
var context = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(config, null, true);
context.SetGraphicsOptions(option);
// sets the prop on the processing context not on the configuration
Assert.Equal(option, context.Properties[typeof(GraphicsOptions)]);
Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys);
}
[Fact]
public void UpdateDefaultOptionsOnProcessingContext_AlwaysNewInstance()
{
var option = new GraphicsOptions()
{
BlendPercentage = 0.9f
};
var config = new Configuration();
var context = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(config, null, true);
context.SetGraphicsOptions(option);
context.SetGraphicsOptions(o =>
{
Assert.Equal(0.9f, o.BlendPercentage); // has origional values
o.BlendPercentage = 0.4f;
});
var returnedOption = context.GetGraphicsOptions();
Assert.Equal(0.4f, returnedOption.BlendPercentage);
Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated
}
[Fact]
public void SetDefaultOptionsOnConfiguration()
{
var option = new GraphicsOptions();
var config = new Configuration();
config.SetGraphicsOptions(option);
Assert.Equal(option, config.Properties[typeof(GraphicsOptions)]);
}
[Fact]
public void UpdateDefaultOptionsOnConfiguration_AlwaysNewInstance()
{
var option = new GraphicsOptions()
{
BlendPercentage = 0.9f
};
var config = new Configuration();
config.SetGraphicsOptions(option);
config.SetGraphicsOptions(o =>
{
Assert.Equal(0.9f, o.BlendPercentage); // has origional values
o.BlendPercentage = 0.4f;
});
var returnedOption = config.GetGraphicsOptions();
Assert.Equal(0.4f, returnedOption.BlendPercentage);
Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated
}
[Fact]
public void GetDefaultOptionsFromConfiguration_SettingNullThenReturnsNewInstance()
{
var config = new Configuration();
var options = config.GetGraphicsOptions();
Assert.NotNull(options);
config.SetGraphicsOptions((GraphicsOptions)null);
var options2 = config.GetGraphicsOptions();
Assert.NotNull(options2);
// we set it to null should now be a new instance
Assert.NotEqual(options, options2);
}
[Fact]
public void GetDefaultOptionsFromConfiguration_IgnoreIncorectlyTypesDictionEntry()
{
var config = new Configuration();
config.Properties[typeof(GraphicsOptions)] = "wronge type";
var options = config.GetGraphicsOptions();
Assert.NotNull(options);
Assert.IsType<GraphicsOptions>(options);
}
[Fact]
public void GetDefaultOptionsFromConfiguration_AlwaysReturnsInstance()
{
var config = new Configuration();
Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys);
var options = config.GetGraphicsOptions();
Assert.NotNull(options);
}
[Fact]
public void GetDefaultOptionsFromConfiguration_AlwaysReturnsSameValue()
{
var config = new Configuration();
var options = config.GetGraphicsOptions();
var options2 = config.GetGraphicsOptions();
Assert.Equal(options, options2);
}
[Fact]
public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstance()
{
var config = new Configuration();
var context = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(config, null, true);
var ctxOptions = context.GetGraphicsOptions();
Assert.NotNull(ctxOptions);
}
[Fact]
public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstanceEvenIfSetToNull()
{
var config = new Configuration();
var context = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(config, null, true);
context.SetGraphicsOptions((GraphicsOptions)null);
var ctxOptions = context.GetGraphicsOptions();
Assert.NotNull(ctxOptions);
}
[Fact]
public void GetDefaultOptionsFromProcessingContext_FallbackToConfigsInstance()
{
var option = new GraphicsOptions();
var config = new Configuration();
config.SetGraphicsOptions(option);
var context = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(config, null, true);
var ctxOptions = context.GetGraphicsOptions();
Assert.Equal(option, ctxOptions);
}
[Fact]
public void GetDefaultOptionsFromProcessingContext_IgnoreIncorectlyTypesDictionEntry()
{
var config = new Configuration();
var context = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(config, null, true);
context.Properties[typeof(GraphicsOptions)] = "wronge type";
var options = context.GetGraphicsOptions();
Assert.NotNull(options);
Assert.IsType<GraphicsOptions>(options);
}
}
}

35
tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs

@ -1,8 +1,10 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -150,6 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
public static TheoryData<long, int, long, int> GetBoundedSlice_ErrorData = new TheoryData<long, int, long, int>() public static TheoryData<long, int, long, int> GetBoundedSlice_ErrorData = new TheoryData<long, int, long, int>()
{ {
{ 300, 100, -1, 91 },
{ 300, 100, 110, 91 }, { 300, 100, 110, 91 },
{ 42, 7, 0, 8 }, { 42, 7, 0, 8 },
{ 42, 7, 1, 7 }, { 42, 7, 1, 7 },
@ -165,7 +168,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
} }
[Fact] [Fact]
public void Fill() public void FillWithFastEnumerator()
{ {
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true); using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true);
group.Fill(42); group.Fill(42);
@ -177,6 +180,34 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
} }
} }
[Fact]
public void FillWithSlowGenericEnumerator()
{
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true);
group.Fill(42);
int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
IReadOnlyList<Memory<int>> groupAsList = group;
foreach (Memory<int> memory in groupAsList)
{
Assert.True(memory.Span.SequenceEqual(expectedRow));
}
}
[Fact]
public void FillWithSlowEnumerator()
{
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true);
group.Fill(42);
int[] expectedRow = Enumerable.Repeat(42, 10).ToArray();
IEnumerable groupAsList = group;
foreach (Memory<int> memory in groupAsList)
{
Assert.True(memory.Span.SequenceEqual(expectedRow));
}
}
[Fact] [Fact]
public void Clear() public void Clear()
{ {

359
tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs

@ -0,0 +1,359 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
{
public class IptcProfileTests
{
private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false };
public static IEnumerable<object[]> AllIptcTags()
{
foreach (object tag in Enum.GetValues(typeof(IptcTag)))
{
yield return new object[] { tag };
}
}
[Theory]
[MemberData("AllIptcTags")]
public void IptcProfile_SetValue_WithStrictOption_Works(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var value = new string('s', tag.MaxLength() + 1);
var expectedLength = tag.MaxLength();
// act
profile.SetValue(tag, value);
// assert
IptcValue actual = profile.GetValues(tag).First();
Assert.Equal(expectedLength, actual.Value.Length);
}
[Theory]
[InlineData(IptcTag.DigitalCreationDate)]
[InlineData(IptcTag.ExpirationDate)]
[InlineData(IptcTag.CreatedDate)]
[InlineData(IptcTag.ReferenceDate)]
[InlineData(IptcTag.ReleaseDate)]
public void IptcProfile_SetDateValue_Works(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var datetime = new DateTimeOffset(new DateTime(1994, 3, 17));
// act
profile.SetDateTimeValue(tag, datetime);
// assert
IptcValue actual = profile.GetValues(tag).First();
Assert.Equal("19940317", actual.Value);
}
[Theory]
[InlineData(IptcTag.CreatedTime)]
[InlineData(IptcTag.DigitalCreationTime)]
[InlineData(IptcTag.ExpirationTime)]
[InlineData(IptcTag.ReleaseTime)]
public void IptcProfile_SetTimeValue_Works(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var dateTimeUtc = new DateTime(1994, 3, 17, 14, 15, 16, DateTimeKind.Utc);
DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTimeUtc).ToOffset(TimeSpan.FromHours(2));
// act
profile.SetDateTimeValue(tag, dateTimeOffset);
// assert
IptcValue actual = profile.GetValues(tag).First();
Assert.Equal("161516+0200", actual.Value);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)]
public void ReadIptcMetadata_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
Assert.NotNull(image.Metadata.IptcProfile);
var iptcValues = image.Metadata.IptcProfile.Values.ToList();
ContainsIptcValue(iptcValues, IptcTag.Caption, "description");
ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer");
ContainsIptcValue(iptcValues, IptcTag.Headline, "headline");
ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions");
ContainsIptcValue(iptcValues, IptcTag.Byline, "author");
ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title");
ContainsIptcValue(iptcValues, IptcTag.Credit, "credits");
ContainsIptcValue(iptcValues, IptcTag.Source, "source");
ContainsIptcValue(iptcValues, IptcTag.Name, "title");
ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414");
ContainsIptcValue(iptcValues, IptcTag.City, "city");
ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation");
ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state");
ContainsIptcValue(iptcValues, IptcTag.Country, "country");
ContainsIptcValue(iptcValues, IptcTag.Category, "category");
ContainsIptcValue(iptcValues, IptcTag.Urgency, "1");
ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords");
ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright");
}
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)]
public void ReadApp13_WithEmptyIptc_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
Assert.Null(image.Metadata.IptcProfile);
}
[Fact]
public void IptcProfile_ToAndFromByteArray_Works()
{
// arrange
var profile = new IptcProfile();
var expectedCaptionWriter = "unittest";
var expectedCaption = "test";
profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter);
profile.SetValue(IptcTag.Caption, expectedCaption);
// act
profile.UpdateData();
byte[] profileBytes = profile.Data;
var profileFromBytes = new IptcProfile(profileBytes);
// assert
var iptcValues = profileFromBytes.Values.ToList();
ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter);
ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption);
}
[Fact]
public void IptcProfile_CloneIsDeep()
{
// arrange
var profile = new IptcProfile();
var captionWriter = "unittest";
var caption = "test";
profile.SetValue(IptcTag.CaptionWriter, captionWriter);
profile.SetValue(IptcTag.Caption, caption);
// act
IptcProfile clone = profile.DeepClone();
clone.SetValue(IptcTag.Caption, "changed");
// assert
Assert.Equal(2, clone.Values.Count());
var cloneValues = clone.Values.ToList();
ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter);
ContainsIptcValue(cloneValues, IptcTag.Caption, "changed");
ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption);
}
[Fact]
public void IptcValue_CloneIsDeep()
{
// arrange
var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true);
// act
IptcValue clone = iptcValue.DeepClone();
clone.Value = "changed";
// assert
Assert.NotEqual(iptcValue.Value, clone.Value);
}
[Fact]
public void WritingImage_PreservesIptcProfile()
{
// arrange
var image = new Image<Rgba32>(1, 1);
image.Metadata.IptcProfile = new IptcProfile();
var expectedCaptionWriter = "unittest";
var expectedCaption = "test";
image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter);
image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption);
// act
Image<Rgba32> reloadedImage = WriteAndReadJpeg(image);
// assert
IptcProfile actual = reloadedImage.Metadata.IptcProfile;
Assert.NotNull(actual);
var iptcValues = actual.Values.ToList();
ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter);
ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption);
}
[Fact]
public void IptcProfile_SetNewValue_RespectsMaxLength()
{
// arrange
var profile = new IptcProfile();
}
[Theory]
[InlineData(IptcTag.ObjectAttribute)]
[InlineData(IptcTag.SubjectReference)]
[InlineData(IptcTag.SupplementalCategories)]
[InlineData(IptcTag.Keywords)]
[InlineData(IptcTag.LocationCode)]
[InlineData(IptcTag.LocationName)]
[InlineData(IptcTag.ReferenceService)]
[InlineData(IptcTag.ReferenceDate)]
[InlineData(IptcTag.ReferenceNumber)]
[InlineData(IptcTag.Byline)]
[InlineData(IptcTag.BylineTitle)]
[InlineData(IptcTag.Contact)]
[InlineData(IptcTag.LocalCaption)]
[InlineData(IptcTag.CaptionWriter)]
public void IptcProfile_AddRepeatable_Works(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var expectedValue1 = "test";
var expectedValue2 = "another one";
profile.SetValue(tag, expectedValue1, false);
// act
profile.SetValue(tag, expectedValue2, false);
// assert
var values = profile.Values.ToList();
Assert.Equal(2, values.Count);
ContainsIptcValue(values, tag, expectedValue1);
ContainsIptcValue(values, tag, expectedValue2);
}
[Theory]
[InlineData(IptcTag.RecordVersion)]
[InlineData(IptcTag.ObjectType)]
[InlineData(IptcTag.Name)]
[InlineData(IptcTag.EditStatus)]
[InlineData(IptcTag.EditorialUpdate)]
[InlineData(IptcTag.Urgency)]
[InlineData(IptcTag.Category)]
[InlineData(IptcTag.FixtureIdentifier)]
[InlineData(IptcTag.ReleaseDate)]
[InlineData(IptcTag.ReleaseTime)]
[InlineData(IptcTag.ExpirationDate)]
[InlineData(IptcTag.ExpirationTime)]
[InlineData(IptcTag.SpecialInstructions)]
[InlineData(IptcTag.ActionAdvised)]
[InlineData(IptcTag.CreatedDate)]
[InlineData(IptcTag.CreatedTime)]
[InlineData(IptcTag.DigitalCreationDate)]
[InlineData(IptcTag.DigitalCreationTime)]
[InlineData(IptcTag.OriginatingProgram)]
[InlineData(IptcTag.ProgramVersion)]
[InlineData(IptcTag.ObjectCycle)]
[InlineData(IptcTag.City)]
[InlineData(IptcTag.SubLocation)]
[InlineData(IptcTag.ProvinceState)]
[InlineData(IptcTag.CountryCode)]
[InlineData(IptcTag.Country)]
[InlineData(IptcTag.OriginalTransmissionReference)]
[InlineData(IptcTag.Headline)]
[InlineData(IptcTag.Credit)]
[InlineData(IptcTag.CopyrightNotice)]
[InlineData(IptcTag.Caption)]
[InlineData(IptcTag.ImageType)]
[InlineData(IptcTag.ImageOrientation)]
public void IptcProfile_AddNoneRepeatable_DoesOverrideOldValue(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var expectedValue = "another one";
profile.SetValue(tag, "test", false);
// act
profile.SetValue(tag, expectedValue, false);
// assert
var values = profile.Values.ToList();
Assert.Equal(1, values.Count);
ContainsIptcValue(values, tag, expectedValue);
}
[Fact]
public void IptcProfile_RemoveByTag_RemovesAllEntrys()
{
// arange
var profile = new IptcProfile();
profile.SetValue(IptcTag.Byline, "test");
profile.SetValue(IptcTag.Byline, "test2");
// act
var result = profile.RemoveValue(IptcTag.Byline);
// assert
Assert.True(result, "removed result should be true");
Assert.Empty(profile.Values);
}
[Fact]
public void IptcProfile_RemoveByTagAndValue_Works()
{
// arange
var profile = new IptcProfile();
profile.SetValue(IptcTag.Byline, "test");
profile.SetValue(IptcTag.Byline, "test2");
// act
var result = profile.RemoveValue(IptcTag.Byline, "test2");
// assert
Assert.True(result, "removed result should be true");
ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test");
}
[Fact]
public void IptcProfile_GetValue_RetrievesAllEntrys()
{
// arange
var profile = new IptcProfile();
profile.SetValue(IptcTag.Byline, "test");
profile.SetValue(IptcTag.Byline, "test2");
profile.SetValue(IptcTag.Caption, "test");
// act
List<IptcValue> result = profile.GetValues(IptcTag.Byline);
// assert
Assert.NotNull(result);
Assert.Equal(2, result.Count);
}
private static void ContainsIptcValue(List<IptcValue> values, IptcTag tag, string value)
{
Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}");
Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value), false)), $"expected iptc value '{value}' was not found for tag '{tag}'");
}
private static Image<Rgba32> WriteAndReadJpeg(Image<Rgba32> image)
{
using (var memStream = new MemoryStream())
{
image.SaveAsJpeg(memStream);
image.Dispose();
memStream.Position = 0;
return Image.Load<Rgba32>(memStream);
}
}
}
}

2
tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.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. // Licensed under the Apache License, Version 2.0.
using System.ComponentModel.DataAnnotations;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -25,6 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Processing
this.source = new Image<Rgba32>(91 + 324, 123 + 56); this.source = new Image<Rgba32>(91 + 324, 123 + 56);
this.rect = new Rectangle(91, 123, 324, 56); // make this random? this.rect = new Rectangle(91, 123, 324, 56); // make this random?
this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(this.source.GetConfiguration(), this.source, false); this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations<Rgba32>(this.source.GetConfiguration(), this.source, false);
this.internalOperations.SetGraphicsOptions(this.options);
this.operations = this.internalOperations; this.operations = this.internalOperations;
} }

10
tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs

@ -10,15 +10,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
{ {
public class BackgroundColorTest : BaseImageOperationsExtensionTest public class BackgroundColorTest : BaseImageOperationsExtensionTest
{ {
private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer();
[Fact] [Fact]
public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet()
{ {
this.operations.BackgroundColor(Color.BlanchedAlmond); this.operations.BackgroundColor(Color.BlanchedAlmond);
BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>(); BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>();
Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, processor.GraphicsOptions);
Assert.Equal(Color.BlanchedAlmond, processor.Color); Assert.Equal(Color.BlanchedAlmond, processor.Color);
} }
@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect);
BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>(this.rect); BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>(this.rect);
Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, processor.GraphicsOptions);
Assert.Equal(Color.BlanchedAlmond, processor.Color); Assert.Equal(Color.BlanchedAlmond, processor.Color);
} }
@ -38,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); this.operations.BackgroundColor(this.options, Color.BlanchedAlmond);
BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>(); BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>();
Assert.Equal(this.options, processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, processor.GraphicsOptions);
Assert.Equal(Color.BlanchedAlmond, processor.Color); Assert.Equal(Color.BlanchedAlmond, processor.Color);
} }
@ -48,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect);
BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>(this.rect); BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>(this.rect);
Assert.Equal(this.options, processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, processor.GraphicsOptions);
Assert.Equal(Color.BlanchedAlmond, processor.Color); Assert.Equal(Color.BlanchedAlmond, processor.Color);
} }
} }

2
tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs

@ -56,6 +56,8 @@ namespace SixLabors.ImageSharp.Tests.Processing
public Configuration Configuration { get; } public Configuration Configuration { get; }
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
public Image<TPixel> GetResultImage() public Image<TPixel> GetResultImage()
{ {
return this.Source; return this.Source;

4
tests/ImageSharp.Tests/Processing/Filters/ContrastTest.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. // Licensed under the Apache License, Version 2.0.
using Xunit; using Xunit;
@ -28,4 +28,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
Assert.Equal(1.5F, processor.Amount); Assert.Equal(1.5F, processor.Amount);
} }
} }
} }

6
tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs

@ -16,14 +16,16 @@ namespace SixLabors.ImageSharp.Tests
public void Lomograph_amount_LomographProcessorDefaultsSet() public void Lomograph_amount_LomographProcessorDefaultsSet()
{ {
this.operations.Lomograph(); this.operations.Lomograph();
this.Verify<LomographProcessor>(); var processor = this.Verify<LomographProcessor>();
Assert.Equal(processor.GraphicsOptions, this.options);
} }
[Fact] [Fact]
public void Lomograph_amount_rect_LomographProcessorDefaultsSet() public void Lomograph_amount_rect_LomographProcessorDefaultsSet()
{ {
this.operations.Lomograph(this.rect); this.operations.Lomograph(this.rect);
this.Verify<LomographProcessor>(this.rect); var processor = this.Verify<LomographProcessor>(this.rect);
Assert.Equal(processor.GraphicsOptions, this.options);
} }
} }
} }

6
tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs

@ -14,14 +14,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters
public void Polaroid_amount_PolaroidProcessorDefaultsSet() public void Polaroid_amount_PolaroidProcessorDefaultsSet()
{ {
this.operations.Polaroid(); this.operations.Polaroid();
this.Verify<PolaroidProcessor>(); var processor = this.Verify<PolaroidProcessor>();
Assert.Equal(processor.GraphicsOptions, this.options);
} }
[Fact] [Fact]
public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet()
{ {
this.operations.Polaroid(this.rect); this.operations.Polaroid(this.rect);
this.Verify<PolaroidProcessor>(this.rect); var processor = this.Verify<PolaroidProcessor>(this.rect);
Assert.Equal(processor.GraphicsOptions, this.options);
} }
} }
} }

10
tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs

@ -11,15 +11,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
{ {
public class GlowTest : BaseImageOperationsExtensionTest public class GlowTest : BaseImageOperationsExtensionTest
{ {
private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer();
[Fact] [Fact]
public void Glow_GlowProcessorWithDefaultValues() public void Glow_GlowProcessorWithDefaultValues()
{ {
this.operations.Glow(); this.operations.Glow();
GlowProcessor p = this.Verify<GlowProcessor>(); GlowProcessor p = this.Verify<GlowProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(Color.Black, p.GlowColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius);
} }
@ -30,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Glow(Color.Aquamarine); this.operations.Glow(Color.Aquamarine);
GlowProcessor p = this.Verify<GlowProcessor>(); GlowProcessor p = this.Verify<GlowProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Aquamarine, p.GlowColor); Assert.Equal(Color.Aquamarine, p.GlowColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius);
} }
@ -41,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Glow(3.5f); this.operations.Glow(3.5f);
GlowProcessor p = this.Verify<GlowProcessor>(); GlowProcessor p = this.Verify<GlowProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(Color.Black, p.GlowColor);
Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); Assert.Equal(ValueSize.Absolute(3.5f), p.Radius);
} }
@ -53,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Glow(rect); this.operations.Glow(rect);
GlowProcessor p = this.Verify<GlowProcessor>(rect); GlowProcessor p = this.Verify<GlowProcessor>(rect);
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(Color.Black, p.GlowColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius);
} }

10
tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs

@ -10,15 +10,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
{ {
public class VignetteTest : BaseImageOperationsExtensionTest public class VignetteTest : BaseImageOperationsExtensionTest
{ {
private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer();
[Fact] [Fact]
public void Vignette_VignetteProcessorWithDefaultValues() public void Vignette_VignetteProcessorWithDefaultValues()
{ {
this.operations.Vignette(); this.operations.Vignette();
VignetteProcessor p = this.Verify<VignetteProcessor>(); VignetteProcessor p = this.Verify<VignetteProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(Color.Black, p.VignetteColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
@ -30,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Vignette(Color.Aquamarine); this.operations.Vignette(Color.Aquamarine);
VignetteProcessor p = this.Verify<VignetteProcessor>(); VignetteProcessor p = this.Verify<VignetteProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Aquamarine, p.VignetteColor); Assert.Equal(Color.Aquamarine, p.VignetteColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
@ -42,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Vignette(3.5f, 12123f); this.operations.Vignette(3.5f, 12123f);
VignetteProcessor p = this.Verify<VignetteProcessor>(); VignetteProcessor p = this.Verify<VignetteProcessor>();
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(Color.Black, p.VignetteColor);
Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX);
Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY);
@ -55,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Vignette(rect); this.operations.Vignette(rect);
VignetteProcessor p = this.Verify<VignetteProcessor>(rect); VignetteProcessor p = this.Verify<VignetteProcessor>(rect);
Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(this.options, p.GraphicsOptions);
Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(Color.Black, p.VignetteColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);

6
tests/ImageSharp.Tests/TestImages.cs

@ -95,6 +95,10 @@ namespace SixLabors.ImageSharp.Tests
public const string Issue1014_6 = "Png/issues/Issue_1014_6.png"; public const string Issue1014_6 = "Png/issues/Issue_1014_6.png";
public const string Issue1127 = "Png/issues/Issue_1127.png"; public const string Issue1127 = "Png/issues/Issue_1127.png";
// Issue 1177: https://github.com/SixLabors/ImageSharp/issues/1177
public const string Issue1177_1 = "Png/issues/Issue_1177_1.png";
public const string Issue1177_2 = "Png/issues/Issue_1177_2.png";
public static class Bad public static class Bad
{ {
// Odd chunk lengths // Odd chunk lengths
@ -162,6 +166,8 @@ namespace SixLabors.ImageSharp.Tests
public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg"; public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg";
public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; public const string Testorig12bit = "Jpg/baseline/testorig12.jpg";
public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg"; public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg";
public const string Iptc = "Jpg/baseline/iptc.jpg";
public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg";
public static readonly string[] All = public static readonly string[] All =
{ {

BIN
tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Jpg/baseline/iptc.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Png/issues/Issue_1177_1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
tests/Images/Input/Png/issues/Issue_1177_2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Loading…
Cancel
Save