Browse Source

Merge branch 'master' into webp

pull/1552/head
Brian Popow 6 years ago
committed by GitHub
parent
commit
598fe5139a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      Directory.Build.props
  2. 2
      Directory.Build.targets
  3. 78
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  4. 11
      src/ImageSharp/Common/Exceptions/ImageProcessingException.cs
  5. 22
      src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
  6. 10
      src/ImageSharp/Common/Helpers/InliningOptions.cs
  7. 7
      src/ImageSharp/Configuration.cs
  8. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  9. 2
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  10. 16
      src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
  11. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  12. 18
      src/ImageSharp/Formats/Gif/GifThrowHelper.cs
  13. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  14. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
  15. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  16. 153
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  17. 68
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  18. 27
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  19. 54
      src/ImageSharp/Formats/Png/PngChunkType.cs
  20. 42
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  21. 29
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  22. 15
      src/ImageSharp/Formats/Png/PngThrowHelper.cs
  23. 2
      src/ImageSharp/Formats/Png/Zlib/Deflater.cs
  24. 5
      src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
  25. 125
      src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
  26. 2
      src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
  27. 6
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  28. 14
      src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
  29. 100
      src/ImageSharp/GraphicOptionsDefaultsExtensions.cs
  30. 147
      src/ImageSharp/Image.FromBytes.cs
  31. 119
      src/ImageSharp/Image.FromFile.cs
  32. 131
      src/ImageSharp/Image.FromStream.cs
  33. 43
      src/ImageSharp/Image.LoadPixelData.cs
  34. 50
      src/ImageSharp/Image.WrapMemory.cs
  35. 58
      src/ImageSharp/ImageExtensions.cs
  36. 35
      src/ImageSharp/ImageFrame{TPixel}.cs
  37. 34
      src/ImageSharp/Image{TPixel}.cs
  38. 4
      src/ImageSharp/Memory/Buffer2D{T}.cs
  39. 10
      src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
  40. 69
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
  41. 8
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
  42. 15
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
  43. 36
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
  44. 29
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  45. 16
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  46. 7
      src/ImageSharp/Metadata/ImageMetadata.cs
  47. 3
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  48. BIN
      src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf
  49. 298
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  50. 397
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
  51. 162
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
  52. 219
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  53. 11
      src/ImageSharp/Metadata/Profiles/IPTC/README.md
  54. 41
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  55. 4
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  56. 8
      src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs
  57. 8
      src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs
  58. 6
      src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs
  59. 4
      src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs
  60. 10
      src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs
  61. 10
      src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs
  62. 67
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  63. 7
      src/ImageSharp/Processing/IImageProcessingContext.cs
  64. 9
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs
  65. 4
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
  66. 9
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs
  67. 6
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
  68. 19
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  69. 9
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  70. 42
      src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs
  71. 51
      src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
  72. 45
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  73. 5
      tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs
  74. 75
      tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
  75. 3
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  76. 4
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  77. 5
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  78. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  79. 69
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  80. 27
      tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
  81. 17
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  82. 256
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  83. 4
      tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
  84. 172
      tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs
  85. 22
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs
  86. 6
      tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs
  87. 10
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
  88. 9
      tests/ImageSharp.Tests/Image/ImageTests.cs
  89. 11
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  90. 35
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
  91. 369
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
  92. 2
      tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
  93. 10
      tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs
  94. 2
      tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs
  95. 4
      tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs
  96. 6
      tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs
  97. 6
      tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs
  98. 10
      tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs
  99. 10
      tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs
  100. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

24
Directory.Build.props

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

2
Directory.Build.targets

@ -18,7 +18,7 @@
<!-- Package versions for package references across all projects -->
<ItemGroup>
<!--Global Dependencies-->
<PackageReference Update="Microsoft.Net.Compilers.Toolset" Version="3.3.1" />
<PackageReference Update="Microsoft.Net.Compilers.Toolset" PrivateAssets="All" Version="3.3.1" />
<PackageReference Update="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0" />
<PackageReference Update="StyleCop.Analyzers" PrivateAssets="All" Version="1.1.118" />

78
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -78,84 +78,6 @@ namespace SixLabors.ImageSharp.Advanced
where TPixel : unmanaged, IPixel<TPixel>
=> source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source));
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source image.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this ImageFrame<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
IMemoryGroup<TPixel> mg = source.GetPixelMemoryGroup();
if (mg.Count > 1)
{
throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!");
}
return mg.Single().Span;
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory in the source image's pixel format
/// stored in row major order.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="InvalidOperationException">Thrown when the backing buffer is discontiguous.</exception>
[Obsolete(
@"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")]
public static Span<TPixel> GetPixelSpan<TPixel>(this Image<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
return source.Frames.RootFrame.GetPixelSpan();
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this ImageFrame<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as <see cref="Span{T}"/> of of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="source">The source.</param>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
public static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, int rowIndex)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(source, nameof(source));
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex));
return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the the first pixel on that row.

11
src/ImageSharp/Common/Exceptions/ImageProcessingException.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp
/// </summary>
public sealed class ImageProcessingException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class.
/// </summary>
public ImageProcessingException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class with the name of the
/// parameter that causes this exception.
@ -32,4 +39,4 @@ namespace SixLabors.ImageSharp
{
}
}
}
}

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

7
src/ImageSharp/Configuration.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
@ -74,6 +75,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>
/// Gets the currently registered <see cref="IImageFormat"/>s.
/// </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)
{
BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap.");
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap.");
}
if (cmd[0] == RleCommand)
@ -569,7 +569,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap.");
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap.");
}
if (cmd[0] == RleCommand)
@ -648,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap.");
BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap.");
}
if (cmd[0] == RleCommand)
@ -1431,7 +1431,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
{
BmpThrowHelper.ThrowImageFormatException(
BmpThrowHelper.ThrowInvalidImageContentException(
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");
}
@ -1445,7 +1445,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int skipAmount = this.fileHeader.Offset - (int)this.stream.Position;
if ((skipAmount + (int)this.stream.Position) > this.stream.Length)
{
BmpThrowHelper.ThrowImageFormatException("Invalid fileheader offset found. Offset is greater than the stream length.");
BmpThrowHelper.ThrowInvalidImageContentException("Invalid fileheader offset found. Offset is greater than the stream length.");
}
if (skipAmount > 0)

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

@ -393,7 +393,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
default:
// Compression type 3 (1DHuffman) is not supported.
BmpThrowHelper.ThrowImageFormatException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24.");
BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24.");
break;
}

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.
using System;
@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal static class BmpThrowHelper
{
/// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// 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 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="NotSupportedException"/>-s
/// Cold path optimization for throwing <see cref="NotSupportedException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
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)
{
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)

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.
using System;
@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
// TODO: Run fallback scalar code here
// However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007
throw new NotImplementedException("Your CPU architecture is too modern!");
JpegThrowHelper.ThrowNotImplementedException("Your CPU architecture is too modern!");
}
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:

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

@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
if (xDensity <= 0)
{
JpegThrowHelper.ThrowImageFormatException($"X-Density {xDensity} must be greater than 0.");
JpegThrowHelper.ThrowInvalidImageContentException($"X-Density {xDensity} must be greater than 0.");
}
if (yDensity <= 0)
{
JpegThrowHelper.ThrowImageFormatException($"Y-Density {yDensity} must be greater than 0.");
JpegThrowHelper.ThrowInvalidImageContentException($"Y-Density {yDensity} must be greater than 0.");
}
this.MajorVersion = majorVersion;

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'
};
/// <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>
/// Gets the EXIF specific markers.
/// </summary>

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

@ -14,6 +14,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private readonly byte[] markerBuffer = new byte[2];
/// <summary>
/// The DC Huffman tables
/// The DC Huffman tables.
/// </summary>
private HuffmanTable[] dcHuffmanTables;
@ -56,37 +57,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private HuffmanTable[] acHuffmanTables;
/// <summary>
/// The reset interval determined by RST markers
/// The reset interval determined by RST markers.
/// </summary>
private ushort resetInterval;
/// <summary>
/// Whether the image has an EXIF marker
/// Whether the image has an EXIF marker.
/// </summary>
private bool isExif;
/// <summary>
/// Contains exif data
/// Contains exif data.
/// </summary>
private byte[] exifData;
/// <summary>
/// Whether the image has an ICC marker
/// Whether the image has an ICC marker.
/// </summary>
private bool isIcc;
/// <summary>
/// Contains ICC data
/// Contains ICC data.
/// </summary>
private byte[] iccData;
/// <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>
private JFifMarker jFif;
/// <summary>
/// Contains information about the Adobe marker
/// Contains information about the Adobe marker.
/// </summary>
private AdobeMarker adobe;
@ -213,6 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage<TPixel>();
}
@ -226,6 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream, true);
this.InitExifProfile();
this.InitIccProfile();
this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata);
@ -246,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
JpegThrowHelper.ThrowImageFormatException("Missing SOI marker.");
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
}
this.InputStream.Read(this.markerBuffer, 0, 2);
@ -344,10 +357,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
case JpegConstants.Markers.APP13:
this.InputStream.Skip(remaining);
break;
case JpegConstants.Markers.APP13:
this.ProcessApp13Marker(remaining);
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
@ -407,7 +423,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
: JpegColorSpace.Cmyk;
}
JpegThrowHelper.ThrowImageFormatException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}");
JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}");
return default;
}
@ -437,6 +453,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
/// <summary>
/// Initializes the IPTC profile.
/// </summary>
private void InitIptcProfile()
{
if (this.isIptc)
{
var profile = new IptcProfile(this.iptcData);
this.Metadata.IptcProfile = profile;
}
}
/// <summary>
/// Assigns derived metadata properties to <see cref="Metadata"/>, eg. horizontal and vertical resolution if it has a JFIF header.
/// </summary>
@ -582,6 +610,95 @@ 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="remaining">The remaining bytes in the segment block.</param>
private void ProcessApp13Marker(int remaining)
{
if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata)
{
this.InputStream.Skip(remaining);
return;
}
this.InputStream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length);
remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
{
var resourceBlockData = new byte[remaining];
this.InputStream.Read(resourceBlockData, 0, remaining);
Span<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>
/// Processes the application header containing the Adobe identifier
/// which stores image encoding information for DCT filters.
@ -704,7 +821,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
if (this.Frame != null)
{
JpegThrowHelper.ThrowImageFormatException("Multiple SOF markers. Only single frame jpegs supported.");
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
}
// Read initial marker definitions.
@ -714,7 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// We only support 8-bit and 12-bit precision.
if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1)
{
JpegThrowHelper.ThrowImageFormatException("Only 8-Bit and 12-Bit precision supported.");
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported.");
}
this.Precision = this.temp[0];
@ -811,13 +928,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Types 0..1 DC..AC
if (tableType > 1)
{
JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table type.");
JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table type.");
}
// Max tables of each type
if (tableIndex > 3)
{
JpegThrowHelper.ThrowImageFormatException("Bad Huffman Table index.");
JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index.");
}
this.InputStream.Read(huffmanData.Array, 0, 16);
@ -836,7 +953,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (codeLengthSum > 256 || codeLengthSum > length)
{
JpegThrowHelper.ThrowImageFormatException("Huffman table has excessive length.");
JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
}
using (IManagedByteBuffer huffmanValues = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
@ -878,7 +995,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
if (this.Frame is null)
{
JpegThrowHelper.ThrowImageFormatException("No readable SOFn (Start Of Frame) marker found.");
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
}
int selectorsCount = this.InputStream.ReadByte();
@ -899,7 +1016,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (componentIndex < 0)
{
JpegThrowHelper.ThrowImageFormatException($"Unknown component selector {componentIndex}.");
JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}.");
}
ref JpegComponent component = ref this.Frame.Components[componentIndex];

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

@ -4,6 +4,7 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -13,6 +14,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -231,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker.
this.WriteApplicationHeader(metadata);
// Write Exif and ICC profiles
// Write Exif, ICC and IPTC profiles
this.WriteProfiles(metadata);
// Write the quantization tables.
@ -647,9 +649,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Writes the EXIF profile.
/// </summary>
/// <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)
{
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>
/// Writes the App1 header.
/// </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)
{
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[1] = JpegConstants.Markers.APP1; // Application Marker
this.buffer[2] = (byte)((app1Length >> 8) & 0xFF);
this.buffer[3] = (byte)(app1Length & 0xFF);
this.buffer[1] = appMarker;
this.buffer[2] = (byte)((length >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF);
this.outputStream.Write(this.buffer, 0, 4);
}
@ -805,6 +856,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
metadata.SyncProfiles();
this.WriteExifProfile(metadata.ExifProfile);
this.WriteIccProfile(metadata.IccProfile);
this.WriteIptcProfile(metadata.IptcProfile);
}
/// <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.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg
@ -8,25 +9,33 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal static class JpegThrowHelper
{
/// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>'s.
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[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)]
public static void ThrowBadMarker(string marker, int length) => throw new ImageFormatException($"Marker {marker} has bad length {length}.");
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTable() => throw new ImageFormatException("Bad Quantization Table index.");
public static void ThrowBadQuantizationTable() => throw new InvalidImageContentException("Bad Quantization Table index.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling() => throw new ImageFormatException("Bad sampling factor.");
public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new ImageFormatException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}.");
public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageDimensions(int width, int height) => throw new ImageFormatException($"Invalid image dimensions: {width}x{height}.");
public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}.");
}
}
}

54
src/ImageSharp/Formats/Png/PngChunkType.cs

@ -73,6 +73,58 @@ namespace SixLabors.ImageSharp.Formats.Png
/// either alpha values associated with palette entries (for indexed-color images)
/// or a single transparent color (for grayscale and true color images).
/// </summary>
Transparency = 0x74524E53U
Transparency = 0x74524E53U,
/// <summary>
/// The tIME chunk gives the time of the last image modification (not the time of initial image creation).
/// </summary>
Time = 0x74494d45,
/// <summary>
/// The bKGD chunk specifies a default background colour to present the image against.
/// If there is any other preferred background, either user-specified or part of a larger page (as in a browser),
/// the bKGD chunk should be ignored.
/// </summary>
Background = 0x624b4744,
/// <summary>
/// The iCCP chunk contains a embedded color profile. If the iCCP chunk is present,
/// the image samples conform to the colour space represented by the embedded ICC profile as defined by the International Color Consortium.
/// </summary>
EmbeddedColorProfile = 0x69434350,
/// <summary>
/// The sBIT chunk defines the original number of significant bits (which can be less than or equal to the sample depth).
/// This allows PNG decoders to recover the original data losslessly even if the data had a sample depth not directly supported by PNG.
/// </summary>
SignificantBits = 0x73424954,
/// <summary>
/// If the sRGB chunk is present, the image samples conform to the sRGB colour space [IEC 61966-2-1] and should be displayed
/// using the specified rendering intent defined by the International Color Consortium.
/// </summary>
StandardRgbColourSpace = 0x73524742,
/// <summary>
/// The hIST chunk gives the approximate usage frequency of each colour in the palette.
/// </summary>
Histogram = 0x68495354,
/// <summary>
/// The sPLT chunk contains the suggested palette.
/// </summary>
SuggestedPalette = 0x73504c54,
/// <summary>
/// The cHRM chunk may be used to specify the 1931 CIE x,y chromaticities of the red,
/// green, and blue display primaries used in the image, and the referenced white point.
/// </summary>
Chroma = 0x6348524d,
/// <summary>
/// Malformed chunk named CgBI produced by apple, which is not conform to the specification.
/// Related issue is here https://github.com/SixLabors/ImageSharp/issues/410
/// </summary>
ProprietaryApple = 0x43674249
}
}

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

@ -9,7 +9,7 @@ using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
@ -215,6 +215,9 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngChunkType.End:
this.isEndChunkReached = true;
break;
case PngChunkType.ProprietaryApple:
PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded.");
break;
}
}
finally
@ -380,7 +383,12 @@ namespace SixLabors.ImageSharp.Formats.Png
private void InitializeImage<TPixel>(ImageMetadata metadata, out Image<TPixel> image)
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.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
this.bytesPerSample = 1;
@ -1034,7 +1042,7 @@ namespace SixLabors.ImageSharp.Formats.Png
var uncompressedBytes = new List<byte>();
// Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
// Note: this uses a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
while (bytesRead != 0)
{
@ -1136,24 +1144,22 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk)
{
if (!chunk.IsCritical)
{
return;
}
Span<byte> chunkType = stackalloc byte[4];
uint crc = this.ReadChunkCrc();
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.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan());
this.crc.Reset();
this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan());
uint crc = this.ReadChunkCrc();
if (this.crc.Value != crc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
if (this.crc.Value != crc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
}
}
}

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

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

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

@ -12,21 +12,24 @@ namespace SixLabors.ImageSharp.Formats.Png
internal static class PngThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
public static void ThrowInvalidChunkType(string message) => throw new InvalidImageContentException(message);
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type");
[MethodImpl(InliningOptions.ColdPath)]
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.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;
}
Array.Copy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
this.inputOff += more;
this.lookahead += more;
@ -397,8 +397,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
[MethodImpl(InliningOptions.ShortMethod)]
@ -464,6 +462,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// </summary>
/// <param name="curMatch">The current match.</param>
/// <returns>True if a match greater than the minimum length is found</returns>
[MethodImpl(InliningOptions.HotPath)]
private bool FindLongestMatch(int curMatch)
{
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 static readonly short[] StaticLCodes;
private static readonly byte[] StaticLLength;
private static readonly short[] StaticDCodes;
private static readonly byte[] StaticDLength;
private Tree literalTree;
private Tree distTree;
private Tree blTree;
@ -58,49 +53,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private int extraBits;
private bool isDisposed;
// TODO: These should be pre-generated array/readonlyspans.
static DeflaterHuffman()
{
// See RFC 1951 3.2.6
// Literal codes
StaticLCodes = new short[LiteralNumber];
StaticLLength = new byte[LiteralNumber];
int i = 0;
while (i < 144)
{
StaticLCodes[i] = BitReverse((0x030 + i) << 8);
StaticLLength[i++] = 8;
}
while (i < 256)
{
StaticLCodes[i] = BitReverse((0x190 - 144 + i) << 7);
StaticLLength[i++] = 9;
}
while (i < 280)
{
StaticLCodes[i] = BitReverse((0x000 - 256 + i) << 9);
StaticLLength[i++] = 7;
}
while (i < LiteralNumber)
{
StaticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8);
StaticLLength[i++] = 8;
}
// Distance codes
StaticDCodes = new short[DistanceNumber];
StaticDLength = new byte[DistanceNumber];
for (i = 0; i < DistanceNumber; i++)
{
StaticDCodes[i] = BitReverse(i << 11);
StaticDLength[i] = 5;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DeflaterHuffman"/> class.
/// </summary>
@ -122,12 +74,80 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
}
#pragma warning disable SA1201 // Elements should appear in the correct order
// See RFC 1951 3.2.6
// Literal codes
private static readonly short[] StaticLCodes = new short[]
{
12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9,
137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5,
133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13,
141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19,
275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499,
11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491,
27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507,
7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487,
23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503,
15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495,
31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511,
0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36,
100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163
};
private static ReadOnlySpan<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>
/// 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>
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>
/// Gets the pending buffer to use.
@ -413,8 +433,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.distTree = null;
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
[MethodImpl(InliningOptions.ShortMethod)]
@ -553,6 +571,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
}
[MethodImpl(InliningOptions.HotPath)]
public void BuildTree()
{
int numSymbols = this.elementCount;
@ -964,8 +983,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true;
}
GC.SuppressFinalize(this);
}
}
}

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

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

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

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

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

@ -9,23 +9,19 @@ namespace SixLabors.ImageSharp.Formats.Tga
internal static class TgaThrowHelper
{
/// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// Cold path optimization for throwing <see cref="ImageFormatException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
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="NotSupportedException"/>-s
/// Cold path optimization for throwing <see cref="NotSupportedException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
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.
/// </summary>
/// <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>
public static IImageFormat DetectFormat(byte[] data)
{
return DetectFormat(Configuration.Default, data);
}
=> DetectFormat(Configuration.Default, data);
/// <summary>
/// By reading the header on the provided byte array this calculates the images format.
/// </summary>
/// <param name="configuration">The configuration.</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>
public static IImageFormat DetectFormat(Configuration configuration, byte[] data)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return DetectFormat(configuration, stream);
@ -42,7 +44,8 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
@ -53,7 +56,8 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <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>
/// <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>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
@ -65,13 +69,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</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>
/// <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>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Identify(configuration, stream, out format);
@ -82,14 +89,20 @@ namespace SixLabors.ImageSharp
/// Load a new instance of <see cref="Image{Rgba32}"/> from the given encoded byte array.
/// </summary>
/// <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>
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>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing encoded image data.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(byte[] data)
where TPixel : unmanaged, IPixel<TPixel>
@ -101,6 +114,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte array containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
@ -112,10 +128,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte array containing encoded image data.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
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="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
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="decoder">The decoder.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
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="decoder">The decoder.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(data, nameof(data));
using (var stream = new MemoryStream(data, 0, data.Length, false, true))
{
return Load<TPixel>(configuration, stream, decoder);
@ -173,9 +212,9 @@ namespace SixLabors.ImageSharp
}
/// <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>
/// <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>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data)
{
@ -183,13 +222,16 @@ namespace SixLabors.ImageSharp
}
/// <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>
/// <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>
public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan<byte> data)
{
Guard.NotNull(configuration, nameof(configuration));
int maxHeaderSize = configuration.MaxHeaderSize;
if (maxHeaderSize <= 0)
{
@ -214,28 +256,34 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="data">The byte span containing encoded image data.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data);
/// <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>
/// <param name="data">The byte span containing image data.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
=> Load<TPixel>(Configuration.Default, data, out format);
/// <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>
/// <param name="data">The byte span containing encoded image data.</param>
/// <param name="decoder">The decoder.</param>
/// <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>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
@ -247,6 +295,9 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing encoded image data.</param>
/// <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>
public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
@ -267,6 +318,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte span containing image data.</param>
/// <param name="decoder">The decoder.</param>
/// <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>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration,
@ -290,6 +344,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte span containing image data.</param>
/// <param name="format">The <see cref="IImageFormat"/> of the decoded image.</param>
/// <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>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration,
@ -311,25 +368,38 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="data">The byte array containing image data.</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>
public static Image Load(byte[] data, out IImageFormat format) =>
Load(Configuration.Default, data, out format);
public static Image Load(byte[] data, out IImageFormat format)
=> Load(Configuration.Default, data, out format);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte array containing encoded image data.</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>
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>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</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>
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>
/// 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="data">The byte array containing image data.</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>
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="data">The byte array containing image data.</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>
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.
/// </summary>
/// <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>
public static Image Load(ReadOnlySpan<byte> data) => Load(Configuration.Default, data);
public static Image Load(ReadOnlySpan<byte> data)
=> Load(Configuration.Default, data);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte span.
/// </summary>
/// <param name="data">The byte span containing image data.</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>
public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder) =>
Load(Configuration.Default, data, decoder);
public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder);
/// <summary>
/// Load a new instance of <see cref="Image"/> from the given encoded byte array.
/// </summary>
/// <param name="data">The byte span containing image data.</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>
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format) =>
Load(Configuration.Default, data, out format);
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format)
=> Load(Configuration.Default, data, out format);
/// <summary>
/// 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="data">The byte span containing image data.</param>
/// <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>
/// 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="data">The byte span containing image data.</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>
public static unsafe Image Load(
Configuration configuration,
@ -421,6 +515,9 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="data">The byte span containing image data.</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>
public static unsafe Image Load(
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>
/// <returns>The mime type or null if none found.</returns>
public static IImageFormat DetectFormat(string filePath)
{
return DetectFormat(Configuration.Default, filePath);
}
=> DetectFormat(Configuration.Default, filePath);
/// <summary>
/// By reading the header on the provided file this calculates the images mime type.
/// </summary>
/// <param name="configuration">The configuration.</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>
public static IImageFormat DetectFormat(Configuration configuration, string filePath)
{
Guard.NotNull(configuration, nameof(configuration));
using (Stream file = configuration.FileSystem.OpenRead(filePath))
{
return DetectFormat(configuration, file);
@ -42,22 +42,22 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(string filePath) => Identify(filePath, out IImageFormat _);
public static IImageInfo Identify(string filePath)
=> Identify(filePath, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <returns>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </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>
/// 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="filePath">The image file to open and to read the header from.</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>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
@ -86,7 +86,8 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <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>
/// 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.
/// </exception>
/// <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>
/// Create a new instance of the <see cref="Image"/> class from the given file.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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>
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>
/// 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="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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>
public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, decoder);
@ -134,57 +142,58 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The path 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>
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>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The path 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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path)
where TPixel : unmanaged, IPixel<TPixel>
{
return Load<TPixel>(Configuration.Default, path);
}
=> Load<TPixel>(Configuration.Default, path);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The path 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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
return Load<TPixel>(Configuration.Default, path, out format);
}
=> Load<TPixel>(Configuration.Default, path, out format);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given file.
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(configuration, stream);
@ -197,15 +206,18 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(configuration, stream, out format);
@ -219,13 +231,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="path">The file path to the image.</param>
/// <param name="format">The mime type of the decoded image.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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>
public static Image Load(Configuration configuration, string path, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load(configuration, stream, out format);
@ -237,16 +252,14 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The path 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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
return Load<TPixel>(Configuration.Default, path, decoder);
}
=> Load<TPixel>(Configuration.Default, path, decoder);
/// <summary>
/// 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="path">The file path to the image.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">
/// Thrown if the stream is not readable nor seekable.
/// </exception>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path 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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, string path, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(path, nameof(path));
using (Stream stream = configuration.FileSystem.OpenRead(path))
{
return Load<TPixel>(configuration, stream, decoder);

131
src/ImageSharp/Image.FromStream.cs

@ -19,16 +19,20 @@ namespace SixLabors.ImageSharp
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <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>
public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream);
public static IImageFormat DetectFormat(Stream stream)
=> DetectFormat(Configuration.Default, stream);
/// <summary>
/// By reading the header on the provided stream this calculates the images format type.
/// </summary>
/// <param name="configuration">The configuration.</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>
public static IImageFormat DetectFormat(Configuration configuration, Stream stream)
=> WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration));
@ -37,22 +41,28 @@ namespace SixLabors.ImageSharp
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <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>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </returns>
public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _);
public static IImageInfo Identify(Stream stream)
=> Identify(stream, out IImageFormat _);
/// <summary>
/// Reads the raw image information from the specified stream without fully decoding it.
/// </summary>
/// <param name="stream">The image stream to read the header from.</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>
/// The <see cref="IImageInfo"/> or null if suitable info detector not found.
/// </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>
/// Reads the raw image information from the specified stream without fully decoding it.
@ -60,7 +70,10 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration.</param>
/// <param name="stream">The image stream to read the information from.</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>
/// The <see cref="IImageInfo"/> or null if suitable info detector is not found.
/// </returns>
@ -78,18 +91,23 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</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="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>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>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// The pixel format is selected by the decoder.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</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>The <see cref="Image"/>.</returns>
public static Image Load(Stream stream) => Load(Configuration.Default, stream);
@ -99,10 +117,14 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</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>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>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
@ -111,19 +133,29 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</exception>
/// <exception cref="ArgumentNullException">The configuration is null.</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>>
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) =>
WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder)
{
Guard.NotNull(decoder, nameof(decoder));
return WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s));
}
/// <summary>
/// Decode a new instance of the <see cref="Image"/> class from the given stream.
/// </summary>
/// <param name="configuration">The configuration for the decoder.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns>>
public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _);
@ -131,8 +163,10 @@ namespace SixLabors.ImageSharp
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given stream.
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream)
@ -144,8 +178,10 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</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="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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, out IImageFormat format)
@ -157,8 +193,10 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Stream stream, IImageDecoder decoder)
@ -171,8 +209,11 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The Configuration.</param>
/// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, IImageDecoder decoder)
@ -184,8 +225,11 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="NotSupportedException">Thrown if the stream is not readable.</exception>
/// <exception cref="UnknownImageFormatException">Image cannot be loaded.</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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream)
@ -198,14 +242,16 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</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="UnknownImageFormatException">Image cannot be loaded.</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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, Stream stream, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
(Image<TPixel> img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode<TPixel>(s, configuration));
format = data.format;
@ -220,7 +266,7 @@ namespace SixLabors.ImageSharp
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());
@ -233,12 +279,14 @@ namespace SixLabors.ImageSharp
/// <param name="configuration">The configuration options.</param>
/// <param name="stream">The stream containing image information.</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="UnknownImageFormatException">Image cannot be loaded.</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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, Stream stream, out IImageFormat format)
{
Guard.NotNull(configuration, nameof(configuration));
(Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration));
format = data.format;
@ -253,7 +301,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair<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());
@ -261,6 +309,9 @@ namespace SixLabors.ImageSharp
private static T WithSeekableStream<T>(Configuration configuration, Stream stream, Func<Stream, T> action)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(stream, nameof(stream));
if (!stream.CanRead)
{
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.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(TPixel[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<TPixel> data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
@ -45,6 +46,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(byte[] data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
@ -57,6 +59,7 @@ namespace SixLabors.ImageSharp
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentException">The data length is incorrect.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(ReadOnlySpan<byte> data, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
@ -65,60 +68,68 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </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="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <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>
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>
=> 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>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </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="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <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>
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>
=> LoadPixelData(config, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
=> LoadPixelData(configuration, MemoryMarshal.Cast<byte, TPixel>(data), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </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="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <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>
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>
{
return LoadPixelData(config, new ReadOnlySpan<TPixel>(data), width, height);
}
=> LoadPixelData(configuration, new ReadOnlySpan<TPixel>(data), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </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="width">The width 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>
/// <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>
{
Guard.NotNull(configuration, nameof(configuration));
int count = width * height;
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.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.
using System;
@ -20,22 +20,27 @@ namespace SixLabors.ImageSharp
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <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="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</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>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
Configuration configuration,
Memory<TPixel> pixelMemory,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
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>
@ -43,20 +48,19 @@ namespace SixLabors.ImageSharp
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <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="width">The width 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>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
Configuration configuration,
Memory<TPixel> pixelMemory,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(config, pixelMemory, width, height, new ImageMetadata());
}
=> WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata());
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@ -73,9 +77,7 @@ namespace SixLabors.ImageSharp
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(Configuration.Default, pixelMemory, width, height);
}
=> WrapMemory(Configuration.Default, pixelMemory, width, height);
/// <summary>
/// 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.
/// </summary>
/// <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="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</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>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
Configuration configuration,
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));
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>
@ -111,20 +118,19 @@ namespace SixLabors.ImageSharp
/// It will be disposed together with the result image.
/// </summary>
/// <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="width">The width 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>
public static Image<TPixel> WrapMemory<TPixel>(
Configuration config,
Configuration configuration,
IMemoryOwner<TPixel> pixelMemoryOwner,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata());
}
=> WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata());
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
@ -143,8 +149,6 @@ namespace SixLabors.ImageSharp
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
{
return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
=> WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
}
}

58
src/ImageSharp/ImageExtensions.cs

@ -7,12 +7,11 @@ using System.IO;
using System.Text;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods over Image{TPixel}.
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="filePath">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void Save(this Image source, string filePath)
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception>
public static void Save(this Image source, string path)
{
Guard.NotNullOrWhiteSpace(filePath, nameof(filePath));
Guard.NotNull(path, nameof(path));
string ext = Path.GetExtension(filePath);
string ext = Path.GetExtension(path);
IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext);
if (format is null)
{
@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp
sb.AppendLine($"No encoder was found for extension '{ext}'. Registered encoders include:");
foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats)
{
sb.AppendLine($" - {fmt.Name} : {string.Join(", ", fmt.FileExtensions)}");
sb.AppendFormat(" - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
}
throw new NotSupportedException(sb.ToString());
@ -48,26 +47,28 @@ namespace SixLabors.ImageSharp
sb.AppendLine($"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
foreach (KeyValuePair<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());
}
source.Save(filePath, encoder);
source.Save(path, encoder);
}
/// <summary>
/// Writes the image to the given stream using the currently loaded image format.
/// </summary>
/// <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>
/// <exception cref="ArgumentNullException">Thrown if the encoder is null.</exception>
public static void Save(this Image source, string filePath, IImageEncoder encoder)
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <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));
using (Stream fs = source.GetConfiguration().FileSystem.Create(filePath))
using (Stream fs = source.GetConfiguration().FileSystem.Create(path))
{
source.Save(fs, encoder);
}
@ -79,10 +80,20 @@ namespace SixLabors.ImageSharp
/// <param name="source">The source image.</param>
/// <param name="stream">The stream to save the image to.</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)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(format, nameof(format));
if (!stream.CanWrite)
{
throw new NotSupportedException("Cannot write to the stream.");
}
IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format);
if (encoder is null)
@ -92,7 +103,7 @@ namespace SixLabors.ImageSharp
foreach (KeyValuePair<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());
@ -103,15 +114,22 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Returns a Base64 encoded string from the given image.
/// The result is prepended with a Data URI <see href="https://en.wikipedia.org/wiki/Data_URI_scheme"/>
/// <para>
/// <example>
/// For example:
/// <see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/>
/// </example>
/// </para>
/// </summary>
/// <example><see href="data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA=="/></example>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The source image</param>
/// <param name="format">The format.</param>
/// <exception cref="ArgumentNullException">The format is null.</exception>
/// <returns>The <see cref="string"/></returns>
public static string ToBase64String<TPixel>(this Image<TPixel> source, IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
public static string ToBase64String(this Image source, IImageFormat format)
{
Guard.NotNull(format, nameof(format));
using var stream = new MemoryStream();
source.Save(stream, format);

35
src/ImageSharp/ImageFrame{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
@ -166,6 +167,40 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> GetPixelRowSpan(int rowIndex)
{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
return this.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/>.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
{
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
if (mg.Count > 1)
{
span = default;
return false;
}
span = mg.Single().Span;
return true;
}
/// <summary>
/// Gets a reference to the pixel at the specified position.
/// </summary>

34
src/ImageSharp/Image{TPixel}.cs

@ -163,6 +163,40 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> GetPixelRowSpan(int rowIndex)
{
Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex));
Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex));
return this.PixelSource.PixelBuffer.GetRowSpan(rowIndex);
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format
/// stored in row major order, if the backing buffer is contiguous.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/>.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool TryGetSinglePixelSpan(out Span<TPixel> span)
{
IMemoryGroup<TPixel> mg = this.GetPixelMemoryGroup();
if (mg.Count > 1)
{
span = default;
return false;
}
span = mg.Single().Span;
return true;
}
/// <summary>
/// Clones the current image
/// </summary>

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

@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Memory
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.MustBeLessThan(y, this.Height, nameof(y));
return this.FastMemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width);
return this.FastMemoryGroup.View.GetBoundedSlice(y * (long)this.Width, this.Width);
}
/// <summary>
@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Memory
}
[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)]
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.
/// </remarks>
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.
using System;
@ -38,6 +38,12 @@ namespace SixLabors.ImageSharp.Memory
Guard.MustBeLessThan(start, group.TotalLength, nameof(start));
int bufferIdx = (int)(start / group.BufferLength);
if (bufferIdx < 0)
{
throw new ArgumentOutOfRangeException(nameof(start));
}
if (bufferIdx >= group.Count)
{
throw new ArgumentOutOfRangeException(nameof(start));

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

@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
@ -37,6 +38,7 @@ namespace SixLabors.ImageSharp.Memory
public int Count
{
[MethodImpl(InliningOptions.ShortMethod)]
get
{
this.EnsureIsValid();
@ -73,7 +75,15 @@ namespace SixLabors.ImageSharp.Memory
}
}
public IEnumerator<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();
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()
{

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

@ -2,16 +2,17 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
internal abstract partial class MemoryGroup<T>
{
// Analogous to the "consumed" variant of MemorySource
private sealed class Consumed : MemoryGroup<T>
/// <summary>
/// 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;
@ -22,16 +23,31 @@ namespace SixLabors.ImageSharp.Memory
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 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++)
{
yield return this.source[i];
}
/* The runtime sees the Array class as if it implemented the
* type-generic collection interfaces explicitly, so here we
* can just cast the source array to IList<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()

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

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

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

@ -6,7 +6,6 @@ using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
@ -48,10 +47,21 @@ namespace SixLabors.ImageSharp.Memory
public abstract void Dispose();
/// <inheritdoc />
public abstract IEnumerator<Memory<T>> GetEnumerator();
public abstract MemoryGroupEnumerator<T> GetEnumerator();
/// <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>
/// 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.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Metadata
{
@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Metadata
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
this.IptcProfile = other.IptcProfile?.DeepClone();
}
/// <summary>
@ -122,6 +124,11 @@ namespace SixLabors.ImageSharp.Metadata
/// </summary>
public IccProfile IccProfile { get; set; }
/// <summary>
/// Gets or sets the iptc profile.
/// </summary>
public IptcProfile IptcProfile { get; set; }
/// <summary>
/// Gets the metadata value associated with the specified key.
/// </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.
/// </summary>
/// <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)
{
Guard.NotNull(other, nameof(other));
this.Parts = other.Parts;
this.thumbnailLength = other.thumbnailLength;
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 values 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)

41
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -247,15 +247,33 @@ namespace SixLabors.ImageSharp.Processing
/// Prepends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) => this.Prepend(_ => matrix);
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
}
/// <summary>
/// Appends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to append.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) => this.Append(_ => matrix);
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
}
/// <summary>
/// Returns the combined matrix for a given source size.
@ -268,6 +286,11 @@ namespace SixLabors.ImageSharp.Processing
/// Returns the combined matrix for a given source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
public Matrix3x2 BuildMatrix(Rectangle sourceRectangle)
{
@ -284,9 +307,19 @@ namespace SixLabors.ImageSharp.Processing
matrix *= factory(size);
}
CheckDegenerate(matrix);
return matrix;
}
private static void CheckDegenerate(Matrix3x2 matrix)
{
if (TransformUtilities.IsDegenerate(matrix))
{
throw new DegenerateTransformException("Matrix is degenerate. Check input values.");
}
}
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> factory)
{
this.matrixFactories.Insert(0, factory);
@ -299,4 +332,4 @@ namespace SixLabors.ImageSharp.Processing
return this;
}
}
}
}

4
src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
@ -39,6 +40,9 @@ namespace SixLabors.ImageSharp.Processing
/// <inheritdoc/>
public Configuration Configuration { get; }
/// <inheritdoc/>
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
/// <inheritdoc/>
public Image<TPixel> GetResultImage()
{

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

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing
Image image,
float opacity)
{
var options = new GraphicsOptions();
var options = source.GetGraphicsOptions();
return source.ApplyProcessor(
new DrawImageProcessor(
image,
@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing
image,
Point.Empty,
colorBlending,
new GraphicsOptions().AlphaCompositionMode,
source.GetGraphicsOptions().AlphaCompositionMode,
opacity));
/// <summary>
@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Processing
Point location,
float opacity)
{
var options = new GraphicsOptions();
var options = source.GetGraphicsOptions();
return source.ApplyProcessor(
new DrawImageProcessor(
image,
@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing
image,
location,
colorBlending,
new GraphicsOptions().AlphaCompositionMode,
source.GetGraphicsOptions().AlphaCompositionMode,
opacity));
/// <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.
using SixLabors.ImageSharp.Processing.Processors.Filters;
@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Lomograph(this IImageProcessingContext source)
=> source.ApplyProcessor(new LomographProcessor());
=> source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()));
/// <summary>
/// Alters the colors of the image recreating an old Lomograph camera effect.
@ -28,6 +28,6 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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.
using SixLabors.ImageSharp.Processing.Processors.Filters;
@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Polaroid(this IImageProcessingContext source)
=> source.ApplyProcessor(new PolaroidProcessor());
=> source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()));
/// <summary>
/// Alters the colors of the image recreating an old Polaroid camera effect.
@ -28,6 +28,6 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) =>
BackgroundColor(source, new GraphicsOptions(), color);
BackgroundColor(source, source.GetGraphicsOptions(), color);
/// <summary>
/// Replaces the background color of image with the given one.
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
Color color,
Rectangle rectangle) =>
BackgroundColor(source, new GraphicsOptions(), color, rectangle);
BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle);
/// <summary>
/// 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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source) =>
Glow(source, new GraphicsOptions());
Glow(source, source.GetGraphicsOptions());
/// <summary>
/// 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>
public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color)
{
return Glow(source, new GraphicsOptions(), color);
return Glow(source, source.GetGraphicsOptions(), color);
}
/// <summary>
@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radius">The the radius.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) =>
Glow(source, new GraphicsOptions(), radius);
Glow(source, source.GetGraphicsOptions(), radius);
/// <summary>
/// Applies a radial glow effect to an image.
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) =>
source.Glow(new GraphicsOptions(), rectangle);
source.Glow(source.GetGraphicsOptions(), rectangle);
/// <summary>
/// Applies a radial glow effect to an image.
@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing
Color color,
float radius,
Rectangle rectangle) =>
source.Glow(new GraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
/// <summary>
/// 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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source) =>
Vignette(source, new GraphicsOptions());
Vignette(source, source.GetGraphicsOptions());
/// <summary>
/// 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>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) =>
Vignette(source, new GraphicsOptions(), color);
Vignette(source, source.GetGraphicsOptions(), color);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
float radiusX,
float radiusY) =>
Vignette(source, new GraphicsOptions(), radiusX, radiusY);
Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) =>
Vignette(source, new GraphicsOptions(), rectangle);
Vignette(source, source.GetGraphicsOptions(), rectangle);
/// <summary>
/// Applies a radial vignette effect to an image.
@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing
float radiusX,
float radiusY,
Rectangle rectangle) =>
source.Vignette(new GraphicsOptions(), color, radiusX, radiusY, rectangle);
source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle);
/// <summary>
/// Applies a radial vignette effect to an image.

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

@ -19,6 +19,10 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
/// <param name="source">The image to mutate.</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)
=> Mutate(source, source.GetConfiguration(), operation);
@ -28,6 +32,11 @@ namespace SixLabors.ImageSharp.Processing
/// <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="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)
{
Guard.NotNull(configuration, nameof(configuration));
@ -44,6 +53,10 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to mutate.</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)
where TPixel : unmanaged, IPixel<TPixel>
=> Mutate(source, source.GetConfiguration(), operation);
@ -55,6 +68,11 @@ namespace SixLabors.ImageSharp.Processing
/// <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="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)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -75,6 +93,10 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to mutate.</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)
where TPixel : unmanaged, IPixel<TPixel>
=> Mutate(source, source.GetConfiguration(), operations);
@ -86,6 +108,11 @@ namespace SixLabors.ImageSharp.Processing
/// <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="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)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -104,7 +131,11 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
/// <param name="source">The image to 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)
=> Clone(source, source.GetConfiguration(), operation);
@ -114,7 +145,12 @@ namespace SixLabors.ImageSharp.Processing
/// <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="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)
{
Guard.NotNull(configuration, nameof(configuration));
@ -133,7 +169,11 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to 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)
where TPixel : unmanaged, IPixel<TPixel>
=> Clone(source, source.GetConfiguration(), operation);
@ -145,7 +185,12 @@ namespace SixLabors.ImageSharp.Processing
/// <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="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)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -167,7 +212,11 @@ namespace SixLabors.ImageSharp.Processing
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to 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)
where TPixel : unmanaged, IPixel<TPixel>
=> Clone(source, source.GetConfiguration(), operations);
@ -179,7 +228,12 @@ namespace SixLabors.ImageSharp.Processing
/// <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="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)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -200,6 +254,7 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
/// <param name="source">The image processing context.</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>
public static IImageProcessingContext ApplyProcessors(
this IImageProcessingContext source,

7
src/ImageSharp/Processing/IImageProcessingContext.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp.Processing
@ -15,6 +16,12 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
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>
/// Gets the image dimensions at the current point in the processing pipeline.
/// </summary>

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

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

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

@ -11,11 +11,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <summary>
/// Initializes a new instance of the <see cref="PolaroidProcessor" /> class.
/// </summary>
public PolaroidProcessor()
/// <param name="graphicsOptions">Graphics options to use within the processor.</param>
public PolaroidProcessor(GraphicsOptions graphicsOptions)
: base(KnownFilterMatrices.PolaroidFilter)
{
this.GraphicsOptions = graphicsOptions;
}
/// <summary>
/// Gets the options effecting blending and composition
/// </summary>
public GraphicsOptions GraphicsOptions { get; }
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle 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 VeryDarkOrange = Color.FromRgb(102, 34, 0);
private readonly PolaroidProcessor definition;
/// <summary>
/// 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)
: base(configuration, definition, source, sourceRectangle)
{
this.definition = definition;
}
/// <inheritdoc/>
protected override void AfterImageApply()
{
new VignetteProcessor(VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle);
new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle);
new GlowProcessor(this.definition.GraphicsOptions, LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle);
base.AfterImageApply();
}
}

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

@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// </summary>
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>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </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>
/// Initializes a new instance of the <see cref="GlowProcessor" /> class.
/// </summary>

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

@ -10,15 +10,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// </summary>
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>
/// Initializes a new instance of the <see cref="VignetteProcessor" /> class.
/// </summary>

42
src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Represents an error that occurs during a transform operation.
/// </summary>
public sealed class DegenerateTransformException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="DegenerateTransformException"/> class.
/// </summary>
public DegenerateTransformException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DegenerateTransformException" /> class
/// with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public DegenerateTransformException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DegenerateTransformException" /> class
/// with a specified error message and a reference to the inner exception that is
/// the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public DegenerateTransformException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

51
src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs

@ -12,6 +12,57 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
internal static class TransformUtilities
{
/// <summary>
/// Returns a value that indicates whether the specified matrix is degenerate
/// containing one or more values equivalent to <see cref="float.NaN"/> or a
/// zero determinant and therefore cannot be used for linear transforms.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
public static bool IsDegenerate(Matrix3x2 matrix)
=> IsNaN(matrix) || IsZero(matrix.GetDeterminant());
/// <summary>
/// Returns a value that indicates whether the specified matrix is degenerate
/// containing one or more values equivalent to <see cref="float.NaN"/> or a
/// zero determinant and therefore cannot be used for linear transforms.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
public static bool IsDegenerate(Matrix4x4 matrix)
=> IsNaN(matrix) || IsZero(matrix.GetDeterminant());
[MethodImpl(InliningOptions.ShortMethod)]
private static bool IsZero(float a)
=> a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared;
/// <summary>
/// Returns a value that indicates whether the specified matrix contains any values
/// that are not a number <see cref="float.NaN"/>.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static bool IsNaN(Matrix3x2 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32);
}
/// <summary>
/// Returns a value that indicates whether the specified matrix contains any values
/// that are not a number <see cref="float.NaN"/>.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static bool IsNaN(Matrix4x4 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34)
|| float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44);
}
/// <summary>
/// Applies the projective transform against the given coordinates flattened into the 2D space.
/// </summary>

45
src/ImageSharp/Processing/ProjectiveTransformBuilder.cs

@ -1,9 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Processing
@ -263,27 +264,51 @@ namespace SixLabors.ImageSharp.Processing
/// Prepends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) => this.Prepend(_ => matrix);
public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
}
/// <summary>
/// Appends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to append.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) => this.Append(_ => matrix);
public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
}
/// <summary>
/// Returns the combined matrix for a given source size.
/// </summary>
/// <param name="sourceSize">The source image size.</param>
/// <returns>The <see cref="Matrix4x4"/>.</returns>
public Matrix4x4 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
public Matrix4x4 BuildMatrix(Size sourceSize)
=> this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
/// <summary>
/// Returns the combined matrix for a given source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Matrix4x4"/>.</returns>
public Matrix4x4 BuildMatrix(Rectangle sourceRectangle)
{
@ -300,9 +325,19 @@ namespace SixLabors.ImageSharp.Processing
matrix *= factory(size);
}
CheckDegenerate(matrix);
return matrix;
}
private static void CheckDegenerate(Matrix4x4 matrix)
{
if (TransformUtilities.IsDegenerate(matrix))
{
throw new DegenerateTransformException("Matrix is degenerate. Check input values.");
}
}
private ProjectiveTransformBuilder Prepend(Func<Size, Matrix4x4> factory)
{
this.matrixFactories.Insert(0, factory);
@ -315,4 +350,4 @@ namespace SixLabors.ImageSharp.Processing
return this;
}
}
}
}

5
tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs

@ -61,7 +61,10 @@ namespace SixLabors.ImageSharp.Tests.Advanced
{
using Image<TPixel> image0 = provider.GetImage();
var targetBuffer = new TPixel[image0.Width * image0.Height];
image0.GetPixelSpan().CopyTo(targetBuffer);
Assert.True(image0.TryGetSinglePixelSpan(out Span<TPixel> sourceBuffer));
sourceBuffer.CopyTo(targetBuffer);
var managerOfExternalMemory = new TestMemoryManager<TPixel>(targetBuffer);

75
tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs

@ -0,0 +1,75 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
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);
DrawImageProcessor 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);
DrawImageProcessor 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);
DrawImageProcessor 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);
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();
Assert.Equal(0.5, dip.Opacity);
Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode);
}
}
}

3
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -128,7 +128,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
using (Image<Rgba32> background = provider.GetImage())
using (var overlay = new Image<Rgba32>(50, 50))
{
overlay.GetPixelSpan().Fill(Color.Black);
Assert.True(overlay.TryGetSinglePixelSpan(out Span<Rgba32> overlaySpan));
overlaySpan.Fill(Color.Black);
background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F));

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

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

5
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -164,7 +164,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
ImageFrame<Rgba32> first = kumin1.Frames[i];
ImageFrame<Rgba32> second = kumin2.Frames[i];
first.ComparePixelBufferTo(second.GetPixelSpan());
Assert.True(second.TryGetSinglePixelSpan(out Span<Rgba32> secondSpan));
first.ComparePixelBufferTo(secondSpan);
}
}
}

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

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

69
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -1,10 +1,14 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -215,5 +219,70 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
}
[Fact]
public void Encode_PreservesIptcProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
var encoder = new JpegEncoder();
// act
using var memStream = new MemoryStream();
input.Save(memStream, encoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
IptcProfile actual = output.Metadata.IptcProfile;
Assert.NotNull(actual);
IEnumerable<IptcValue> values = input.Metadata.IptcProfile.Values;
Assert.Equal(values, actual.Values);
}
[Fact]
public void Encode_PreservesExifProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.ExifProfile = new ExifProfile();
input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test");
var encoder = new JpegEncoder();
// act
using var memStream = new MemoryStream();
input.Save(memStream, encoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
ExifProfile actual = output.Metadata.ExifProfile;
Assert.NotNull(actual);
IReadOnlyList<IExifValue> values = input.Metadata.ExifProfile.Values;
Assert.Equal(values, actual.Values);
}
[Fact]
public void Encode_PreservesIccProfile()
{
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array);
var encoder = new JpegEncoder();
// act
using var memStream = new MemoryStream();
input.Save(memStream, encoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
IccProfile actual = output.Metadata.IccProfile;
Assert.NotNull(actual);
IccProfile values = input.Metadata.IccProfile;
Assert.Equal(values.Entries, actual.Entries);
}
}
}

27
tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs

@ -13,15 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[Fact]
public void ChunkTypeIdsAreCorrect()
{
Assert.Equal(PngChunkType.Header, GetType("IHDR"));
Assert.Equal(PngChunkType.Palette, GetType("PLTE"));
Assert.Equal(PngChunkType.Data, GetType("IDAT"));
Assert.Equal(PngChunkType.End, GetType("IEND"));
Assert.Equal(PngChunkType.Header, GetType("IHDR"));
Assert.Equal(PngChunkType.Palette, GetType("PLTE"));
Assert.Equal(PngChunkType.Data, GetType("IDAT"));
Assert.Equal(PngChunkType.End, GetType("IEND"));
Assert.Equal(PngChunkType.Transparency, GetType("tRNS"));
Assert.Equal(PngChunkType.Text, GetType("tEXt"));
Assert.Equal(PngChunkType.Gamma, GetType("gAMA"));
Assert.Equal(PngChunkType.Physical, GetType("pHYs"));
Assert.Equal(PngChunkType.Exif, GetType("eXIf"));
Assert.Equal(PngChunkType.Text, GetType("tEXt"));
Assert.Equal(PngChunkType.InternationalText, GetType("iTXt"));
Assert.Equal(PngChunkType.CompressedText, GetType("zTXt"));
Assert.Equal(PngChunkType.Chroma, GetType("cHRM"));
Assert.Equal(PngChunkType.Gamma, GetType("gAMA"));
Assert.Equal(PngChunkType.Physical, GetType("pHYs"));
Assert.Equal(PngChunkType.Exif, GetType("eXIf"));
Assert.Equal(PngChunkType.Time, GetType("tIME"));
Assert.Equal(PngChunkType.Background, GetType("bKGD"));
Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP"));
Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB"));
Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT"));
Assert.Equal(PngChunkType.Histogram, GetType("hIST"));
Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT"));
Assert.Equal(PngChunkType.ProprietaryApple, GetType("CgBI"));
}
private static PngChunkType GetType(string text)

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

@ -6,6 +6,7 @@ using System.IO;
using System.Text;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -72,12 +73,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
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);
}
}
[Fact]
public void CalculateCrc_Works()
{
// arrange
var data = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
var crc = new Crc32();
// act
crc.Update(data);
// assert
Assert.Equal(0x88AA689F, crc.Value);
}
private static string GetChunkTypeName(uint value)
{
var data = new byte[4];

256
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -4,7 +4,6 @@
using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,67 +25,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
public static readonly string[] CommonTestImages =
{
TestImages.Png.Splash,
TestImages.Png.Indexed,
TestImages.Png.FilterVar,
TestImages.Png.Bad.ChunkLength1,
TestImages.Png.Bad.CorruptedChunk,
TestImages.Png.VimImage1,
TestImages.Png.VimImage2,
TestImages.Png.VersioningImage1,
TestImages.Png.VersioningImage2,
TestImages.Png.SnakeGame,
TestImages.Png.Banner7Adam7InterlaceMode,
TestImages.Png.Banner8Index,
TestImages.Png.Bad.ChunkLength2,
TestImages.Png.VimImage2,
TestImages.Png.Rgb24BppTrans,
TestImages.Png.GrayA8Bit,
TestImages.Png.Gray1BitTrans,
TestImages.Png.Bad.ZlibOverflow,
TestImages.Png.Bad.ZlibOverflow2,
TestImages.Png.Bad.ZlibZtxtBadHeader,
TestImages.Png.Bad.Issue1047_BadEndChunk
};
public static readonly string[] TestImages48Bpp =
{
TestImages.Png.Rgb48Bpp,
TestImages.Png.Rgb48BppInterlaced
};
public static readonly string[] TestImages64Bpp =
{
TestImages.Png.Rgba64Bpp,
TestImages.Png.Rgb48BppTrans
TestImages.Png.Bad.ChunkLength1,
TestImages.Png.Bad.ChunkLength2,
};
public static readonly string[] TestImagesL16Bit =
public static readonly string[] TestImagesIssue1014 =
{
TestImages.Png.L16Bit,
TestImages.Png.Issue1014_1, TestImages.Png.Issue1014_2,
TestImages.Png.Issue1014_3, TestImages.Png.Issue1014_4,
TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6
};
public static readonly string[] TestImagesGrayAlpha16Bit =
public static readonly string[] TestImagesIssue1177 =
{
TestImages.Png.GrayAlpha16Bit,
TestImages.Png.GrayTrns16BitInterlaced
TestImages.Png.Issue1177_1,
TestImages.Png.Issue1177_2
};
public static readonly string[] TestImagesL8BitInterlaced =
{
TestImages.Png.GrayAlpha1BitInterlaced,
TestImages.Png.GrayAlpha2BitInterlaced,
TestImages.Png.Gray4BitInterlaced,
TestImages.Png.GrayA8BitInterlaced
};
public static readonly string[] TestImagesIssue1014 =
public static readonly string[] CorruptedTestImages =
{
TestImages.Png.Issue1014_1, TestImages.Png.Issue1014_2,
TestImages.Png.Issue1014_3, TestImages.Png.Issue1014_4,
TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6
TestImages.Png.Bad.CorruptedChunk,
TestImages.Png.Bad.ZlibOverflow,
TestImages.Png.Bad.ZlibOverflow2,
TestImages.Png.Bad.ZlibZtxtBadHeader,
};
[Theory]
@ -97,25 +69,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
}
}
// We don't have another x-plat reference decoder that can be compared for this image.
if (provider.Utility.SourceFileOrDescription == TestImages.Png.Bad.Issue1047_BadEndChunk)
{
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider, ImageComparer.Exact, (IImageDecoder)SystemDrawingReferenceDecoder.Instance);
}
}
else
{
image.CompareToOriginal(provider, ImageComparer.Exact);
}
[Theory]
[WithFile(TestImages.Png.GrayA8Bit, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Gray1BitTrans, PixelTypes.Rgba32)]
public void Decode_GrayWithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
}
}
[Theory]
[WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)]
public void Decode_Interlaced_ImageIsCorrect<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(TestImages.Png.Banner7Adam7InterlaceMode, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)]
public void Decode_Interlaced<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
@ -126,7 +101,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
[Theory]
[WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgb48)]
[WithFile(TestImages.Png.Indexed, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.PalettedFourColor, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.PalettedSixteenColor, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)]
public void Decode_Indexed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
}
}
[Theory]
[WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgb48)]
[WithFile(TestImages.Png.Rgb48BppInterlaced, PixelTypes.Rgb48)]
public void Decode_48Bpp<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -138,7 +131,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
[Theory]
[WithFileCollection(nameof(TestImages64Bpp), PixelTypes.Rgba64)]
[WithFile(TestImages.Png.Rgba64Bpp, PixelTypes.Rgba64)]
[WithFile(TestImages.Png.Rgb48BppTrans, PixelTypes.Rgba64)]
public void Decode_64Bpp<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -150,7 +144,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
[Theory]
[WithFileCollection(nameof(TestImagesL8BitInterlaced), PixelTypes.Rgba32)]
[WithFile(TestImages.Png.GrayAlpha1BitInterlaced, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Gray4BitInterlaced, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes.Rgba32)]
public void Decoder_L8bitInterlaced<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -162,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
[Theory]
[WithFileCollection(nameof(TestImagesL16Bit), PixelTypes.Rgb48)]
[WithFile(TestImages.Png.L16Bit, PixelTypes.Rgb48)]
public void Decode_L16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -174,7 +171,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
[Theory]
[WithFileCollection(nameof(TestImagesGrayAlpha16Bit), PixelTypes.Rgba64)]
[WithFile(TestImages.Png.GrayAlpha16Bit, PixelTypes.Rgba64)]
[WithFile(TestImages.Png.GrayTrns16BitInterlaced, PixelTypes.Rgba64)]
public void Decode_GrayAlpha16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -187,7 +185,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[Theory]
[WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes)]
public void Decoder_CanDecodeGrey8bitWithAlpha<TPixel>(TestImageProvider<TPixel> provider)
public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact);
}
}
[Theory]
[WithFileCollection(nameof(CorruptedTestImages), PixelTypes.Rgba32)]
public void Decoder_CanDecode_CorruptedImages<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
@ -226,9 +236,63 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
[Theory]
[WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)]
public void Decode_MissingDataChunk_ThrowsException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
}
});
Assert.NotNull(ex);
Assert.Contains("PNG Image does not contain a data chunk", ex.Message);
}
[Theory]
[WithFile(TestImages.Png.Bad.BitDepthZero, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Bad.BitDepthThree, PixelTypes.Rgba32)]
public void Decode_InvalidBitDepth_ThrowsException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
}
});
Assert.NotNull(ex);
Assert.Contains("Invalid or unsupported bit depth", ex.Message);
}
[Theory]
[WithFile(TestImages.Png.Bad.ColorTypeOne, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Bad.ColorTypeNine, PixelTypes.Rgba32)]
public void Decode_InvalidColorType_ThrowsException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
}
});
Assert.NotNull(ex);
Assert.Contains("Invalid or unsupported color type", ex.Message);
}
// https://github.com/SixLabors/ImageSharp/issues/1014
[Theory]
[WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)]
public void Issue1014<TPixel>(TestImageProvider<TPixel> provider)
public void Issue1014_DataSplitOverMultipleIDatChunks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
@ -243,6 +307,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Null(ex);
}
// https://github.com/SixLabors/ImageSharp/issues/1177
[Theory]
[WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)]
public void Issue1177_CRC_Omitted<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);
}
// https://github.com/SixLabors/ImageSharp/issues/1127
[Theory]
[WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)]
public void Issue1127<TPixel>(TestImageProvider<TPixel> provider)
@ -260,6 +343,53 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Null(ex);
}
// https://github.com/SixLabors/ImageSharp/issues/1047
[Theory]
[WithFile(TestImages.Png.Bad.Issue1047_BadEndChunk, PixelTypes.Rgba32)]
public void Issue1047<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
// We don't have another x-plat reference decoder that can be compared for this image.
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance);
}
}
});
Assert.Null(ex);
}
// https://github.com/SixLabors/ImageSharp/issues/410
[Theory]
[WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)]
public void Issue410_MalformedApplePng<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
System.Exception ex = Record.Exception(
() =>
{
using (Image<TPixel> image = provider.GetImage(PngDecoder))
{
image.DebugSave(provider);
// We don't have another x-plat reference decoder that can be compared for this image.
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance);
}
}
});
Assert.NotNull(ex);
Assert.Contains("Proprietary Apple PNG detected!", ex.Message);
}
[Theory]
[WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)]
[WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)]

4
tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs

@ -9,6 +9,7 @@ using ImageMagick;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tga
{
@ -46,7 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
{
magickImage.AutoOrient();
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
Span<TPixel> resultPixels = result.GetPixelSpan();
Assert.True(result.TryGetSinglePixelSpan(out Span<TPixel> resultPixels));
using (IPixelCollection pixels = magickImage.GetPixelsUnsafe())
{

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);
}
}
}

22
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs

@ -198,7 +198,9 @@ namespace SixLabors.ImageSharp.Tests
using (Image<TPixel> cloned = img.Frames.CloneFrame(0))
{
Assert.Equal(2, img.Frames.Count);
cloned.ComparePixelBufferTo(img.GetPixelSpan());
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
cloned.ComparePixelBufferTo(imgSpan);
}
}
}
@ -210,7 +212,8 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> img = provider.GetImage())
{
var sourcePixelData = img.GetPixelSpan().ToArray();
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
TPixel[] sourcePixelData = imgSpan.ToArray();
img.Frames.AddFrame(new ImageFrame<TPixel>(Configuration.Default, 10, 10));
using (Image<TPixel> cloned = img.Frames.ExportFrame(0))
@ -242,7 +245,8 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void AddFrameFromPixelData()
{
var pixelData = this.Image.Frames.RootFrame.GetPixelSpan().ToArray();
Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<Rgba32> imgSpan));
var pixelData = imgSpan.ToArray();
this.Image.Frames.AddFrame(pixelData);
Assert.Equal(2, this.Image.Frames.Count);
}
@ -251,8 +255,10 @@ namespace SixLabors.ImageSharp.Tests
public void AddFrame_clones_sourceFrame()
{
var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
var addedFrame = this.Image.Frames.AddFrame(otherFrame);
addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan());
ImageFrame<Rgba32> addedFrame = this.Image.Frames.AddFrame(otherFrame);
Assert.True(otherFrame.TryGetSinglePixelSpan(out Span<Rgba32> otherFrameSpan));
addedFrame.ComparePixelBufferTo(otherFrameSpan);
Assert.NotEqual(otherFrame, addedFrame);
}
@ -260,8 +266,10 @@ namespace SixLabors.ImageSharp.Tests
public void InsertFrame_clones_sourceFrame()
{
var otherFrame = new ImageFrame<Rgba32>(Configuration.Default, 10, 10);
var addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);
addedFrame.ComparePixelBufferTo(otherFrame.GetPixelSpan());
ImageFrame<Rgba32> addedFrame = this.Image.Frames.InsertFrame(0, otherFrame);
Assert.True(otherFrame.TryGetSinglePixelSpan(out Span<Rgba32> otherFrameSpan));
addedFrame.ComparePixelBufferTo(otherFrameSpan);
Assert.NotEqual(otherFrame, addedFrame);
}

6
tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs

@ -159,7 +159,8 @@ namespace SixLabors.ImageSharp.Tests
var expectedClone = (Image<TPixel>)cloned;
expectedClone.ComparePixelBufferTo(img.GetPixelSpan());
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
expectedClone.ComparePixelBufferTo(imgSpan);
}
}
}
@ -171,7 +172,8 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> img = provider.GetImage())
{
var sourcePixelData = img.GetPixelSpan().ToArray();
Assert.True(img.TryGetSinglePixelSpan(out Span<TPixel> imgSpan));
var sourcePixelData = imgSpan.ToArray();
ImageFrameCollection nonGenericFrameCollection = img.Frames;

10
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -91,7 +91,8 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData))
{
ref Rgba32 pixel0 = ref image.GetPixelSpan()[0];
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
ref Rgba32 pixel0 = ref imageSpan[0];
Assert.True(Unsafe.AreSame(ref array[0], ref pixel0));
Assert.Equal(cfg, image.GetConfiguration());
@ -118,7 +119,8 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height))
{
Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory());
image.GetPixelSpan().Fill(bg);
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
@ -153,8 +155,8 @@ namespace SixLabors.ImageSharp.Tests
using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height))
{
Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory());
image.GetPixelSpan().Fill(bg);
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> imageSpan));
imageSpan.Fill(bg);
for (var i = 10; i < 20; i++)
{
image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);

9
tests/ImageSharp.Tests/Image/ImageTests.cs

@ -27,7 +27,8 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.Equal(11 * 23, imageSpan.Length);
image.ComparePixelBufferTo(default(Rgba32));
Assert.Equal(Configuration.Default, image.GetConfiguration());
@ -43,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.Equal(11 * 23, imageSpan.Length);
image.ComparePixelBufferTo(default(Rgba32));
Assert.Equal(configuration, image.GetConfiguration());
@ -60,7 +62,8 @@ namespace SixLabors.ImageSharp.Tests
{
Assert.Equal(11, image.Width);
Assert.Equal(23, image.Height);
Assert.Equal(11 * 23, image.GetPixelSpan().Length);
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.Equal(11 * 23, imageSpan.Length);
image.ComparePixelBufferTo(color);
Assert.Equal(configuration, image.GetConfiguration());

11
tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
@ -17,5 +18,15 @@ namespace SixLabors.ImageSharp.Tests
image.Mutate(c => c.Resize(1000, 1000));
image.DebugSave(provider);
}
[Theory]
[WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)]
public void GetSingleSpan(TestImageProvider<Rgba32> provider)
{
provider.LimitAllocatorBufferCapacity().InPixels(10);
using Image<Rgba32> image = provider.GetImage();
Assert.False(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
Assert.False(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<Rgba32> imageFrameSpan));
}
}
}

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.
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
@ -150,6 +152,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
public static TheoryData<long, int, long, int> GetBoundedSlice_ErrorData = new TheoryData<long, int, long, int>()
{
{ 300, 100, -1, 91 },
{ 300, 100, 110, 91 },
{ 42, 7, 0, 8 },
{ 42, 7, 1, 7 },
@ -165,7 +168,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
}
[Fact]
public void Fill()
public void FillWithFastEnumerator()
{
using MemoryGroup<int> group = this.CreateTestGroup(100, 10, true);
group.Fill(42);
@ -177,6 +180,34 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers
}
}
[Fact]
public void FillWithSlowGenericEnumerator()
{
using MemoryGroup<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]
public void Clear()
{

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

@ -0,0 +1,369 @@
// 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(nameof(AllIptcTags))]
public void IptcProfile_SetValue_WithStrictEnabled_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]
[MemberData(nameof(AllIptcTags))]
public void IptcProfile_SetValue_WithStrictDisabled_Works(IptcTag tag)
{
// arrange
var profile = new IptcProfile();
var value = new string('s', tag.MaxLength() + 1);
var expectedLength = value.Length;
// act
profile.SetValue(tag, value, false);
// 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);
}
[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()
{
// arrange
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()
{
// arrange
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_RetrievesAllEntries()
{
// arrange
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.
// Licensed under the Apache License, Version 2.0.
using System.ComponentModel.DataAnnotations;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -25,6 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Processing
this.source = new Image<Rgba32>(91 + 324, 123 + 56);
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.SetGraphicsOptions(this.options);
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
{
private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer();
[Fact]
public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet()
{
this.operations.BackgroundColor(Color.BlanchedAlmond);
BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>();
Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, GraphicsOptionsComparer);
Assert.Equal(this.options, processor.GraphicsOptions);
Assert.Equal(Color.BlanchedAlmond, processor.Color);
}
@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
this.operations.BackgroundColor(Color.BlanchedAlmond, 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);
}
@ -38,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
this.operations.BackgroundColor(this.options, Color.BlanchedAlmond);
BackgroundColorProcessor processor = this.Verify<BackgroundColorProcessor>();
Assert.Equal(this.options, processor.GraphicsOptions, GraphicsOptionsComparer);
Assert.Equal(this.options, processor.GraphicsOptions);
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);
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);
}
}

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

@ -56,6 +56,8 @@ namespace SixLabors.ImageSharp.Tests.Processing
public Configuration Configuration { get; }
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
public Image<TPixel> GetResultImage()
{
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.
using Xunit;
@ -28,4 +28,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
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()
{
this.operations.Lomograph();
this.Verify<LomographProcessor>();
var processor = this.Verify<LomographProcessor>();
Assert.Equal(processor.GraphicsOptions, this.options);
}
[Fact]
public void Lomograph_amount_rect_LomographProcessorDefaultsSet()
{
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()
{
this.operations.Polaroid();
this.Verify<PolaroidProcessor>();
var processor = this.Verify<PolaroidProcessor>();
Assert.Equal(processor.GraphicsOptions, this.options);
}
[Fact]
public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet()
{
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
{
private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer();
[Fact]
public void Glow_GlowProcessorWithDefaultValues()
{
this.operations.Glow();
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(ValueSize.PercentageOfWidth(.5f), p.Radius);
}
@ -30,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Glow(Color.Aquamarine);
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(ValueSize.PercentageOfWidth(.5f), p.Radius);
}
@ -41,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Glow(3.5f);
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(ValueSize.Absolute(3.5f), p.Radius);
}
@ -53,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Glow(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(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
{
private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer();
[Fact]
public void Vignette_VignetteProcessorWithDefaultValues()
{
this.operations.Vignette();
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(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
@ -30,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Vignette(Color.Aquamarine);
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(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
@ -42,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Vignette(3.5f, 12123f);
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(ValueSize.Absolute(3.5f), p.RadiusX);
Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY);
@ -55,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
this.operations.Vignette(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(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
private static void VerifyAllPixelsAreWhiteOrTransparent<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
Span<TPixel> data = image.Frames.RootFrame.GetPixelSpan();
Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span<TPixel> data));
var white = new Rgb24(255, 255, 255);
foreach (TPixel pixel in data)
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save