diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs
index 4fa07592e..716e89e68 100644
--- a/src/ImageSharp/Metadata/ImageMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageMetadata.cs
@@ -66,6 +66,7 @@ namespace SixLabors.ImageSharp.Metadata
this.ExifProfile = other.ExifProfile?.DeepClone();
this.IccProfile = other.IccProfile?.DeepClone();
+ this.IptcProfile = other.IptcProfile?.DeepClone();
}
///
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
index 11d0bd01b..29c21d611 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
@@ -57,8 +57,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// by making a copy from another EXIF profile.
///
/// The other EXIF profile, where the clone should be made from.
+ /// is null.>
private ExifProfile(ExifProfile other)
{
+ Guard.NotNull(other, nameof(other));
+
this.Parts = other.Parts;
this.thumbnailLength = other.thumbnailLength;
this.thumbnailOffset = other.thumbnailOffset;
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
index 2b0281b3b..57704949c 100644
--- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
@@ -15,16 +15,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// This source code is from the Magick.Net project:
/// https://github.com/dlemstra/Magick.NET/tree/master/src/Magick.NET/Shared/Profiles/Iptc/IptcProfile.cs
///
- public sealed class IptcProfile
+ public sealed class IptcProfile : IDeepCloneable
{
private Collection values;
- private byte[] data;
-
///
/// Initializes a new instance of the class.
///
public IptcProfile()
+ : this((byte[])null)
{
}
@@ -34,9 +33,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// The byte array to read the iptc profile from.
public IptcProfile(byte[] data)
{
- this.data = data;
+ this.Data = data;
+ }
+
+ private IptcProfile(IptcProfile other)
+ {
+ Guard.NotNull(other, nameof(other));
+
+ if (other.values != null)
+ {
+ this.values = new Collection();
+
+ foreach (IptcValue value in other.Values)
+ {
+ this.values.Add(value.DeepClone());
+ }
+ }
+
+ if (other.Data != null)
+ {
+ this.Data = new byte[other.Data.Length];
+ other.Data.AsSpan().CopyTo(this.Data);
+ }
}
+ ///
+ /// Gets the byte data of the IPTC profile.
+ ///
+ public byte[] Data { get; private set; }
+
///
/// Gets the values of this iptc profile.
///
@@ -49,6 +74,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
}
+ ///
+ public IptcProfile DeepClone() => new IptcProfile(this);
+
///
/// Returns the value with the specified tag.
///
@@ -143,19 +171,19 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
length += value.Length + 5;
}
- this.data = new byte[length];
+ 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;
+ 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);
+ Buffer.BlockCopy(value.ToByteArray(), 0, this.Data, i, value.Length);
i += value.Length;
}
}
@@ -170,30 +198,30 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
this.values = new Collection();
- if (this.data == null || this.data[0] != 0x1c)
+ if (this.Data == null || this.Data[0] != 0x1c)
{
return;
}
int i = 0;
- while (i + 4 < this.data.Length)
+ while (i + 4 < this.Data.Length)
{
- if (this.data[i++] != 28)
+ if (this.Data[i++] != 28)
{
continue;
}
i++;
- var tag = (IptcTag)this.data[i++];
+ var tag = (IptcTag)this.Data[i++];
- int count = BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(i, 2));
+ int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2));
i += 2;
var iptcData = new byte[count];
- if ((count > 0) && (i + count <= this.data.Length))
+ if ((count > 0) && (i + count <= this.Data.Length))
{
- Buffer.BlockCopy(this.data, i, iptcData, 0, count);
+ Buffer.BlockCopy(this.Data, i, iptcData, 0, count);
this.values.Add(new IptcValue(tag, iptcData));
}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
index c23a7793e..a5977fd27 100644
--- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
@@ -9,11 +9,27 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
///
/// A value of the iptc profile.
///
- public sealed class IptcValue
+ public sealed class IptcValue : IDeepCloneable
{
private byte[] data;
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;
+ }
+
internal IptcValue(IptcTag tag, byte[] value)
{
Guard.NotNull(value, nameof(value));
@@ -74,6 +90,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
///
public int Length => this.data.Length;
+ ///
+ public IptcValue DeepClone() => new IptcValue(this);
+
///
/// Determines whether the specified object is equal to the current .
///
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
new file mode 100644
index 000000000..045859c36
--- /dev/null
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
@@ -0,0 +1,88 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+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 };
+
+ [Theory]
+ [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)]
+ public void ReadIptcProfile(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(JpegDecoder))
+ {
+ Assert.NotNull(image.Metadata.IptcProfile);
+ IEnumerable iptcValues = image.Metadata.IptcProfile.Values;
+ 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.Title, "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.Priority, "1");
+ ContainsIptcValue(iptcValues, IptcTag.Keyword, "keywords");
+ ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright");
+ }
+ }
+
+ [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());
+ ContainsIptcValue(clone.Values, IptcTag.CaptionWriter, captionWriter);
+ ContainsIptcValue(clone.Values, IptcTag.Caption, "changed");
+ ContainsIptcValue(profile.Values, IptcTag.Caption, caption);
+ }
+
+ [Fact]
+ public void IptcValue_CloneIsDeep()
+ {
+ // arrange
+ var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test");
+
+ // act
+ IptcValue clone = iptcValue.DeepClone();
+ clone.Value = "changed";
+
+ // assert
+ Assert.NotEqual(iptcValue.Value, clone.Value);
+ }
+
+ private static void ContainsIptcValue(IEnumerable 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))), $"expected iptc value '{value}' was not found for tag '{tag}'");
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 892568803..d006c6682 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -162,6 +162,7 @@ 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 static readonly string[] All =
{
diff --git a/tests/Images/Input/Jpg/baseline/iptc.jpg b/tests/Images/Input/Jpg/baseline/iptc.jpg
new file mode 100644
index 000000000..adb12621f
--- /dev/null
+++ b/tests/Images/Input/Jpg/baseline/iptc.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6c8a0747d9282bfd7e8e7f4a0119c53c702bf600384b786ef9b5263457f38ada
+size 18611