Browse Source

Merge branch 'master' into release/rc-1

pull/1574/head
James Jackson-South 6 years ago
parent
commit
3cccbaad2b
  1. 22
      src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs
  2. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  3. 2
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  4. 16
      src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
  5. 2
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  6. 18
      src/ImageSharp/Formats/Gif/GifThrowHelper.cs
  7. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
  8. 4
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs
  9. 24
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
  10. 153
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  11. 68
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  12. 27
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  13. 28
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  14. 10
      src/ImageSharp/Formats/Png/PngThrowHelper.cs
  15. 6
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  16. 14
      src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
  17. 147
      src/ImageSharp/Image.FromBytes.cs
  18. 119
      src/ImageSharp/Image.FromFile.cs
  19. 131
      src/ImageSharp/Image.FromStream.cs
  20. 43
      src/ImageSharp/Image.LoadPixelData.cs
  21. 50
      src/ImageSharp/Image.WrapMemory.cs
  22. 46
      src/ImageSharp/ImageExtensions.cs
  23. 4
      src/ImageSharp/Memory/Buffer2D{T}.cs
  24. 10
      src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs
  25. 69
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs
  26. 8
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs
  27. 15
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs
  28. 36
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs
  29. 29
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  30. 16
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  31. 7
      src/ImageSharp/Metadata/ImageMetadata.cs
  32. 3
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  33. BIN
      src/ImageSharp/Metadata/Profiles/IPTC/IIMV4.2_IPTC.pdf
  34. 298
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  35. 397
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
  36. 162
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
  37. 219
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  38. 11
      src/ImageSharp/Metadata/Profiles/IPTC/README.md
  39. 67
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  40. 4
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  41. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs
  42. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
  43. 23
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  44. 35
      tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs
  45. 359
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
  46. 6
      tests/ImageSharp.Tests/TestImages.cs
  47. 3
      tests/Images/Input/Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg
  48. 3
      tests/Images/Input/Jpg/baseline/iptc.jpg
  49. 3
      tests/Images/Input/Png/issues/Issue_1177_1.png
  50. 3
      tests/Images/Input/Png/issues/Issue_1177_2.png

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/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}.");
}
}
}

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

@ -1141,24 +1141,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);
}
}
}

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

@ -12,21 +12,21 @@ namespace SixLabors.ImageSharp.Formats.Png
internal static class PngThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type.");
}
}

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

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

46
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());
@ -113,9 +124,12 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <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(this Image source, IImageFormat format)
{
Guard.NotNull(format, nameof(format));
using var stream = new MemoryStream();
source.Save(stream, format);

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

@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Memory
{
DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y));
DebugGuard.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 value with the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <returns>The values found with the specified tag.</returns>
public List<IptcValue> GetValues(IptcTag tag)
{
var iptcValues = new List<IptcValue>();
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
iptcValues.Add(iptcValue);
}
}
return iptcValues;
}
/// <summary>
/// Removes all values with the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value to remove.</param>
/// <returns>True when the value was found and removed.</returns>
public bool RemoveValue(IptcTag tag)
{
this.Initialize();
bool removed = false;
for (int i = this.values.Count - 1; i >= 0; i--)
{
if (this.values[i].Tag == tag)
{
this.values.RemoveAt(i);
removed = true;
}
}
return removed;
}
/// <summary>
/// Removes values with the specified tag and value.
/// </summary>
/// <param name="tag">The tag of the iptc value to remove.</param>
/// <param name="value">The value of the iptc item to remove.</param>
/// <returns>True when the value was found and removed.</returns>
public bool RemoveValue(IptcTag tag, string value)
{
this.Initialize();
bool removed = false;
for (int i = this.values.Count - 1; i >= 0; i--)
{
if (this.values[i].Tag == tag && this.values[i].Value.Equals(value))
{
this.values.RemoveAt(i);
removed = true;
}
}
return removed;
}
/// <summary>
/// Changes the encoding for all the values.
/// </summary>
/// <param name="encoding">The encoding to use when storing the bytes.</param>
public void SetEncoding(Encoding encoding)
{
Guard.NotNull(encoding, nameof(encoding));
foreach (IptcValue value in this.Values)
{
value.Encoding = encoding;
}
}
/// <summary>
/// Sets the value for the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="encoding">The encoding to use when storing the bytes.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true)
{
Guard.NotNull(encoding, nameof(encoding));
Guard.NotNull(value, nameof(value));
if (!tag.IsRepeatable())
{
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
iptcValue.Strict = strict;
iptcValue.Encoding = encoding;
iptcValue.Value = value;
return;
}
}
}
this.values.Add(new IptcValue(tag, encoding, value, strict));
}
/// <summary>
/// Makes sure the datetime is formatted according to the iptc specification.
/// <example>
/// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989.
/// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time,
/// two hours ahead of UTC.
/// </example>
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="dateTimeOffset">The datetime.</param>
public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset)
{
if (!tag.IsDate() && !tag.IsTime())
{
throw new ArgumentException("iptc tag is not a time or date type");
}
var formattedDate = tag.IsDate()
? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture)
: dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture)
.Replace(":", string.Empty);
this.SetValue(tag, Encoding.UTF8, formattedDate);
}
/// <summary>
/// Sets the value of the specified tag.
/// </summary>
/// <param name="tag">The tag of the iptc value.</param>
/// <param name="value">The value.</param>
/// <param name="strict">
/// Indicates if length restrictions from the specification should be followed strictly.
/// Defaults to true.
/// </param>
public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict);
/// <summary>
/// Updates the data of the profile.
/// </summary>
public void UpdateData()
{
var length = 0;
foreach (IptcValue value in this.Values)
{
length += value.Length + 5;
}
this.Data = new byte[length];
int i = 0;
foreach (IptcValue value in this.Values)
{
this.Data[i++] = 28;
this.Data[i++] = 2;
this.Data[i++] = (byte)value.Tag;
this.Data[i++] = (byte)(value.Length >> 8);
this.Data[i++] = (byte)value.Length;
if (value.Length > 0)
{
Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length);
i += value.Length;
}
}
}
private void Initialize()
{
if (this.values != null)
{
return;
}
this.values = new Collection<IptcValue>();
if (this.Data == null || this.Data[0] != 0x1c)
{
return;
}
int i = 0;
while (i + 4 < this.Data.Length)
{
if (this.Data[i++] != 28)
{
continue;
}
i++;
var tag = (IptcTag)this.Data[i++];
int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2));
i += 2;
var iptcData = new byte[count];
if ((count > 0) && (i + count <= this.Data.Length))
{
Buffer.BlockCopy(this.Data, i, iptcData, 0, count);
this.values.Add(new IptcValue(tag, iptcData, false));
}
i += count;
}
}
}
}

397
src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs

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

162
src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs

@ -0,0 +1,162 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
/// <summary>
/// Extension methods for IPTC tags.
/// </summary>
public static class IptcTagExtensions
{
/// <summary>
/// Maximum length of the IPTC value with the given tag according to the specification.
/// </summary>
/// <param name="tag">The tag to check the max length for.</param>
/// <returns>The maximum length.</returns>
public static int MaxLength(this IptcTag tag)
{
return tag switch
{
IptcTag.RecordVersion => 2,
IptcTag.ObjectType => 67,
IptcTag.ObjectAttribute => 68,
IptcTag.Name => 64,
IptcTag.EditStatus => 64,
IptcTag.EditorialUpdate => 2,
IptcTag.Urgency => 1,
IptcTag.SubjectReference => 236,
IptcTag.Category => 3,
IptcTag.SupplementalCategories => 32,
IptcTag.FixtureIdentifier => 32,
IptcTag.Keywords => 64,
IptcTag.LocationCode => 3,
IptcTag.LocationName => 64,
IptcTag.ReleaseDate => 8,
IptcTag.ReleaseTime => 11,
IptcTag.ExpirationDate => 8,
IptcTag.ExpirationTime => 11,
IptcTag.SpecialInstructions => 256,
IptcTag.ActionAdvised => 2,
IptcTag.ReferenceService => 10,
IptcTag.ReferenceDate => 8,
IptcTag.ReferenceNumber => 8,
IptcTag.CreatedDate => 8,
IptcTag.CreatedTime => 11,
IptcTag.DigitalCreationDate => 8,
IptcTag.DigitalCreationTime => 11,
IptcTag.OriginatingProgram => 32,
IptcTag.ProgramVersion => 10,
IptcTag.ObjectCycle => 1,
IptcTag.Byline => 32,
IptcTag.BylineTitle => 32,
IptcTag.City => 32,
IptcTag.SubLocation => 32,
IptcTag.ProvinceState => 32,
IptcTag.CountryCode => 3,
IptcTag.Country => 64,
IptcTag.OriginalTransmissionReference => 32,
IptcTag.Headline => 256,
IptcTag.Credit => 32,
IptcTag.Source => 32,
IptcTag.CopyrightNotice => 128,
IptcTag.Contact => 128,
IptcTag.Caption => 2000,
IptcTag.CaptionWriter => 32,
IptcTag.ImageType => 2,
IptcTag.ImageOrientation => 1,
_ => 256
};
}
/// <summary>
/// Determines if the given tag can be repeated according to the specification.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if the tag can occur multiple times.</returns>
public static bool IsRepeatable(this IptcTag tag)
{
switch (tag)
{
case IptcTag.RecordVersion:
case IptcTag.ObjectType:
case IptcTag.Name:
case IptcTag.EditStatus:
case IptcTag.EditorialUpdate:
case IptcTag.Urgency:
case IptcTag.Category:
case IptcTag.FixtureIdentifier:
case IptcTag.ReleaseDate:
case IptcTag.ReleaseTime:
case IptcTag.ExpirationDate:
case IptcTag.ExpirationTime:
case IptcTag.SpecialInstructions:
case IptcTag.ActionAdvised:
case IptcTag.CreatedDate:
case IptcTag.CreatedTime:
case IptcTag.DigitalCreationDate:
case IptcTag.DigitalCreationTime:
case IptcTag.OriginatingProgram:
case IptcTag.ProgramVersion:
case IptcTag.ObjectCycle:
case IptcTag.City:
case IptcTag.SubLocation:
case IptcTag.ProvinceState:
case IptcTag.CountryCode:
case IptcTag.Country:
case IptcTag.OriginalTransmissionReference:
case IptcTag.Headline:
case IptcTag.Credit:
case IptcTag.Source:
case IptcTag.CopyrightNotice:
case IptcTag.Caption:
case IptcTag.ImageType:
case IptcTag.ImageOrientation:
return false;
default:
return true;
}
}
/// <summary>
/// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if its a datetime tag.</returns>
public static bool IsDate(this IptcTag tag)
{
switch (tag)
{
case IptcTag.CreatedDate:
case IptcTag.DigitalCreationDate:
case IptcTag.ExpirationDate:
case IptcTag.ReferenceDate:
case IptcTag.ReleaseDate:
return true;
default:
return false;
}
}
/// <summary>
/// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM.
/// </summary>
/// <param name="tag">The tag to check.</param>
/// <returns>True, if its a time tag.</returns>
public static bool IsTime(this IptcTag tag)
{
switch (tag)
{
case IptcTag.CreatedTime:
case IptcTag.DigitalCreationTime:
case IptcTag.ExpirationTime:
case IptcTag.ReleaseTime:
return true;
default:
return false;
}
}
}
}

219
src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs

@ -0,0 +1,219 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
/// <summary>
/// Represents a single value of the IPTC profile.
/// </summary>
public sealed class IptcValue : IDeepCloneable<IptcValue>
{
private byte[] data = Array.Empty<byte>();
private Encoding encoding;
internal IptcValue(IptcValue other)
{
if (other.data != null)
{
this.data = new byte[other.data.Length];
other.data.AsSpan().CopyTo(this.data);
}
if (other.Encoding != null)
{
this.Encoding = (Encoding)other.Encoding.Clone();
}
this.Tag = other.Tag;
this.Strict = other.Strict;
}
internal IptcValue(IptcTag tag, byte[] value, bool strict)
{
Guard.NotNull(value, nameof(value));
this.Strict = strict;
this.Tag = tag;
this.data = value;
this.encoding = Encoding.UTF8;
}
internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict)
{
this.Strict = strict;
this.Tag = tag;
this.encoding = encoding;
this.Value = value;
}
internal IptcValue(IptcTag tag, string value, bool strict)
{
this.Strict = strict;
this.Tag = tag;
this.encoding = Encoding.UTF8;
this.Value = value;
}
/// <summary>
/// Gets or sets the encoding to use for the Value.
/// </summary>
public Encoding Encoding
{
get => this.encoding;
set
{
if (value != null)
{
this.encoding = value;
}
}
}
/// <summary>
/// Gets the tag of the iptc value.
/// </summary>
public IptcTag Tag { get; }
/// <summary>
/// Gets or sets a value indicating whether to be enforce value length restrictions according
/// to the specification.
/// </summary>
public bool Strict { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
public string Value
{
get => this.encoding.GetString(this.data);
set
{
if (string.IsNullOrEmpty(value))
{
this.data = Array.Empty<byte>();
}
else
{
int maxLength = this.Tag.MaxLength();
byte[] valueBytes;
if (this.Strict && value.Length > maxLength)
{
var cappedValue = value.Substring(0, maxLength);
valueBytes = this.encoding.GetBytes(cappedValue);
// It is still possible that the bytes of the string exceed the limit.
if (valueBytes.Length > maxLength)
{
throw new ArgumentException($"The iptc value exceeds the limit of {maxLength} bytes for the tag {this.Tag}");
}
}
else
{
valueBytes = this.encoding.GetBytes(value);
}
this.data = valueBytes;
}
}
}
/// <summary>
/// Gets the length of the value.
/// </summary>
public int Length => this.data.Length;
/// <inheritdoc/>
public IptcValue DeepClone() => new IptcValue(this);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="IptcValue"/>.
/// </summary>
/// <param name="obj">The object to compare this <see cref="IptcValue"/> with.</param>
/// <returns>True when the specified object is equal to the current <see cref="IptcValue"/>.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
return this.Equals(obj as IptcValue);
}
/// <summary>
/// Determines whether the specified iptc value is equal to the current <see cref="IptcValue"/>.
/// </summary>
/// <param name="other">The iptc value to compare this <see cref="IptcValue"/> with.</param>
/// <returns>True when the specified iptc value is equal to the current <see cref="IptcValue"/>.</returns>
public bool Equals(IptcValue other)
{
if (other is null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (this.Tag != other.Tag)
{
return false;
}
if (this.data.Length != other.data.Length)
{
return false;
}
for (int i = 0; i < this.data.Length; i++)
{
if (this.data[i] != other.data[i])
{
return false;
}
}
return true;
}
/// <summary>
/// Serves as a hash of this type.
/// </summary>
/// <returns>A hash code for the current instance.</returns>
public override int GetHashCode() => HashCode.Combine(this.data, this.Tag);
/// <summary>
/// Converts this instance to a byte array.
/// </summary>
/// <returns>A <see cref="byte"/> array.</returns>
public byte[] ToByteArray()
{
var result = new byte[this.data.Length];
this.data.CopyTo(result, 0);
return result;
}
/// <summary>
/// Returns a string that represents the current value.
/// </summary>
/// <returns>A string that represents the current value.</returns>
public override string ToString() => this.Value;
/// <summary>
/// Returns a string that represents the current value with the specified encoding.
/// </summary>
/// <param name="encoding">The encoding to use.</param>
/// <returns>A string that represents the current value with the specified encoding.</returns>
public string ToString(Encoding encoding)
{
Guard.NotNull(encoding, nameof(encoding));
return encoding.GetString(this.data);
}
}
}

11
src/ImageSharp/Metadata/Profiles/IPTC/README.md

@ -0,0 +1,11 @@
IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET)
Information about IPTC can be found here in the following sources:
- [metacpan.org, APP13-segment](https://metacpan.org/pod/Image::MetaData::JPEG::Structures#Structure-of-a-Photoshop-style-APP13-segment)
- [iptc.org](https://www.iptc.org/std/photometadata/documentation/userguide/)
- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf)
- [Tag Overview](https://exiftool.org/TagNames/IPTC.html)

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,

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

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

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

@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
var decoder = new PngDecoder();
ImageFormatException exception =
Assert.Throws<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);
}

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

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

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()
{

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

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

6
tests/ImageSharp.Tests/TestImages.cs

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

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

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7508a28e39026ed8ebc9751138d014450b2f636a343838d8e08dbc7e19ad6df
size 18329

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

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c8a0747d9282bfd7e8e7f4a0119c53c702bf600384b786ef9b5263457f38ada
size 18611

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

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cef2be6012f4604f9f30b51273661058df0201be4de508235f372eb2304b2132
size 7023

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

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7067af724977e1ecd8fc761f50226eaaa9e9d4142be963b4edbbf0918b8eba1d
size 57125
Loading…
Cancel
Save