diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
index 87b486ea6..325d7780a 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs
@@ -28,6 +28,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
(byte)'I', (byte)'L', (byte)'E', (byte)'\0'
};
+ ///
+ /// Gets the adobe photoshop APP13 marker which can contain IPTC meta data.
+ ///
+ public static ReadOnlySpan AdobePhotoshopApp13Marker => new[]
+ {
+ (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', (byte)'\0'
+ };
+
+ ///
+ /// Gets the 8BIM marker, which signals the start of a adobe specific image resource block.
+ ///
+ public static ReadOnlySpan AdobeImageResourceBlockMarker => new[]
+ {
+ (byte)'8', (byte)'B', (byte)'I', (byte)'M'
+ };
+
+ ///
+ /// Gets a IPTC Image resource ID.
+ ///
+ public static ReadOnlySpan AdobeIptcMarker => new[]
+ {
+ (byte)4, (byte)4
+ };
+
///
/// Gets the EXIF specific markers.
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 951fec1d4..46f0e694e 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/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];
///
- /// The DC Huffman tables
+ /// The DC Huffman tables.
///
private HuffmanTable[] dcHuffmanTables;
@@ -56,37 +57,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private HuffmanTable[] acHuffmanTables;
///
- /// The reset interval determined by RST markers
+ /// The reset interval determined by RST markers.
///
private ushort resetInterval;
///
- /// Whether the image has an EXIF marker
+ /// Whether the image has an EXIF marker.
///
private bool isExif;
///
- /// Contains exif data
+ /// Contains exif data.
///
private byte[] exifData;
///
- /// Whether the image has an ICC marker
+ /// Whether the image has an ICC marker.
///
private bool isIcc;
///
- /// Contains ICC data
+ /// Contains ICC data.
///
private byte[] iccData;
///
- /// Contains information about the JFIF marker
+ /// Whether the image has a IPTC data.
+ ///
+ private bool isIptc;
+
+ ///
+ /// Contains IPTC data.
+ ///
+ private byte[] iptcData;
+
+ ///
+ /// Contains information about the JFIF marker.
///
private JFifMarker jFif;
///
- /// Contains information about the Adobe marker
+ /// Contains information about the Adobe marker.
///
private AdobeMarker adobe;
@@ -213,6 +224,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ParseStream(stream);
this.InitExifProfile();
this.InitIccProfile();
+ this.InitIptcProfile();
this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage();
}
@@ -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);
@@ -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;
@@ -437,6 +453,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ ///
+ /// Initializes the IPTC profile.
+ ///
+ private void InitIptcProfile()
+ {
+ if (this.isIptc)
+ {
+ var profile = new IptcProfile(this.iptcData);
+ this.Metadata.IptcProfile = profile;
+ }
+ }
+
///
/// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header.
///
@@ -582,6 +610,80 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ ///
+ /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop.
+ /// The content of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks.
+ ///
+ /// The remaining bytes in the segment block.
+ private void ProcessApp13Marker(int remaining)
+ {
+ if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.IgnoreMetadata)
+ {
+ this.InputStream.Skip(remaining);
+ return;
+ }
+
+ var identifier = new byte[ProfileResolver.AdobePhotoshopApp13Marker.Length];
+ this.InputStream.Read(identifier, 0, identifier.Length);
+ remaining -= identifier.Length;
+ if (ProfileResolver.IsProfile(identifier, ProfileResolver.AdobePhotoshopApp13Marker))
+ {
+ var resourceBlockData = new byte[remaining];
+ this.InputStream.Read(resourceBlockData, 0, remaining);
+ Span blockDataSpan = resourceBlockData.AsSpan();
+
+ while (blockDataSpan.Length > 10)
+ {
+ if (!ProfileResolver.IsProfile(blockDataSpan.Slice(0, 4), ProfileResolver.AdobeImageResourceBlockMarker))
+ {
+ return;
+ }
+
+ blockDataSpan = blockDataSpan.Slice(4);
+ Span imageResourceBlockId = blockDataSpan.Slice(0, 2);
+ if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker))
+ {
+ var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
+ var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
+ this.isIptc = true;
+ this.iptcData = blockDataSpan.Slice(2 + resourceBlockNameLength + 4, resourceDataSize).ToArray();
+ break;
+ }
+ else
+ {
+ var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
+ var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
+ blockDataSpan = blockDataSpan.Slice(2 + resourceBlockNameLength + 4 + resourceDataSize);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads the adobe image resource block name: a Pascal string (padded to make size even).
+ ///
+ /// The span holding the block resource data.
+ /// The length of the name.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int ReadImageResourceNameLength(Span blockDataSpan)
+ {
+ byte nameLength = blockDataSpan[2];
+ var nameDataSize = nameLength == 0 ? 2 : nameLength;
+ return nameDataSize;
+ }
+
+ ///
+ /// Reads the length of a adobe image resource data block.
+ ///
+ /// The span holding the block resource data.
+ /// The length of the block name.
+ /// The block length.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength)
+ {
+ return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
+ }
+
///
/// Processes the application header containing the Adobe identifier
/// which stores image encoding information for DCT filters.
diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs
index b3751bfbd..4fa07592e 100644
--- a/src/ImageSharp/Metadata/ImageMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageMetadata.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Metadata
{
@@ -122,6 +123,11 @@ namespace SixLabors.ImageSharp.Metadata
///
public IccProfile IccProfile { get; set; }
+ ///
+ /// Gets or sets the iptc profile.
+ ///
+ public IptcProfile IptcProfile { get; set; }
+
///
/// Gets the metadata value associated with the specified key.
///
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
new file mode 100644
index 000000000..2b0281b3b
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
@@ -0,0 +1,204 @@
+// 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
+{
+ ///
+ /// Class that can be used to access an Iptc profile.
+ ///
+ /// 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
+ {
+ private Collection values;
+
+ private byte[] data;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IptcProfile()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The byte array to read the iptc profile from.
+ public IptcProfile(byte[] data)
+ {
+ this.data = data;
+ }
+
+ ///
+ /// Gets the values of this iptc profile.
+ ///
+ public IEnumerable Values
+ {
+ get
+ {
+ this.Initialize();
+ return this.values;
+ }
+ }
+
+ ///
+ /// Returns the value with the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// The value with the specified tag.
+ public IptcValue GetValue(IptcTag tag)
+ {
+ foreach (IptcValue iptcValue in this.Values)
+ {
+ if (iptcValue.Tag == tag)
+ {
+ return iptcValue;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Removes the value with the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// True when the value was fount and removed.
+ public bool RemoveValue(IptcTag tag)
+ {
+ this.Initialize();
+
+ for (int i = 0; i < this.values.Count; i++)
+ {
+ if (this.values[i].Tag == tag)
+ {
+ this.values.RemoveAt(i);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Changes the encoding for all the values.
+ ///
+ /// The encoding to use when storing the bytes.
+ public void SetEncoding(Encoding encoding)
+ {
+ Guard.NotNull(encoding, nameof(encoding));
+
+ foreach (IptcValue value in this.Values)
+ {
+ value.Encoding = encoding;
+ }
+ }
+
+ ///
+ /// Sets the value of the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// The encoding to use when storing the bytes.
+ /// The value.
+ public void SetValue(IptcTag tag, Encoding encoding, string value)
+ {
+ Guard.NotNull(encoding, nameof(encoding));
+
+ foreach (IptcValue iptcValue in this.Values)
+ {
+ if (iptcValue.Tag == tag)
+ {
+ iptcValue.Encoding = encoding;
+ iptcValue.Value = value;
+ return;
+ }
+ }
+
+ this.values.Add(new IptcValue(tag, encoding, value));
+ }
+
+ ///
+ /// Sets the value of the specified tag.
+ ///
+ /// The tag of the iptc value.
+ /// The value.
+ public void SetValue(IptcTag tag, string value) => this.SetValue(tag, Encoding.UTF8, value);
+
+ ///
+ /// Updates the data of the profile.
+ ///
+ public void UpdateData()
+ {
+ var length = 0;
+ foreach (IptcValue value in this.Values)
+ {
+ length += value.Length + 5;
+ }
+
+ this.data = new byte[length];
+
+ int i = 0;
+ foreach (IptcValue value in this.Values)
+ {
+ this.data[i++] = 28;
+ this.data[i++] = 2;
+ this.data[i++] = (byte)value.Tag;
+ this.data[i++] = (byte)(value.Length >> 8);
+ this.data[i++] = (byte)value.Length;
+ if (value.Length > 0)
+ {
+ Buffer.BlockCopy(value.ToByteArray(), 0, this.data, i, value.Length);
+ i += value.Length;
+ }
+ }
+ }
+
+ private void Initialize()
+ {
+ if (this.values != null)
+ {
+ return;
+ }
+
+ this.values = new Collection();
+
+ if (this.data == null || this.data[0] != 0x1c)
+ {
+ return;
+ }
+
+ int i = 0;
+ while (i + 4 < this.data.Length)
+ {
+ if (this.data[i++] != 28)
+ {
+ continue;
+ }
+
+ i++;
+
+ var tag = (IptcTag)this.data[i++];
+
+ int count = BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(i, 2));
+ i += 2;
+
+ var iptcData = new byte[count];
+ if ((count > 0) && (i + count <= this.data.Length))
+ {
+ Buffer.BlockCopy(this.data, i, iptcData, 0, count);
+ this.values.Add(new IptcValue(tag, iptcData));
+ }
+
+ i += count;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
new file mode 100644
index 000000000..3e6da0e09
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
@@ -0,0 +1,351 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
+{
+ ///
+ /// All iptc tags.
+ ///
+ public enum IptcTag
+ {
+ ///
+ /// Unknown
+ ///
+ Unknown = -1,
+
+ ///
+ /// Record version
+ ///
+ RecordVersion = 0,
+
+ ///
+ /// Object type
+ ///
+ ObjectType = 3,
+
+ ///
+ /// Object attribute
+ ///
+ ObjectAttribute = 4,
+
+ ///
+ /// Title
+ ///
+ Title = 5,
+
+ ///
+ /// Edit status
+ ///
+ EditStatus = 7,
+
+ ///
+ /// Editorial update
+ ///
+ EditorialUpdate = 8,
+
+ ///
+ /// Priority
+ ///
+ Priority = 10,
+
+ ///
+ /// Category
+ ///
+ Category = 15,
+
+ ///
+ /// Supplemental categories
+ ///
+ SupplementalCategories = 20,
+
+ ///
+ /// Fixture identifier
+ ///
+ FixtureIdentifier = 22,
+
+ ///
+ /// Keyword
+ ///
+ Keyword = 25,
+
+ ///
+ /// Location code
+ ///
+ LocationCode = 26,
+
+ ///
+ /// Location name
+ ///
+ LocationName = 27,
+
+ ///
+ /// Release date
+ ///
+ ReleaseDate = 30,
+
+ ///
+ /// Release time
+ ///
+ ReleaseTime = 35,
+
+ ///
+ /// Expiration date
+ ///
+ ExpirationDate = 37,
+
+ ///
+ /// Expiration time
+ ///
+ ExpirationTime = 38,
+
+ ///
+ /// Special instructions
+ ///
+ SpecialInstructions = 40,
+
+ ///
+ /// Action advised
+ ///
+ ActionAdvised = 42,
+
+ ///
+ /// Reference service
+ ///
+ ReferenceService = 45,
+
+ ///
+ /// Reference date
+ ///
+ ReferenceDate = 47,
+
+ ///
+ /// ReferenceNumber
+ ///
+ ReferenceNumber = 50,
+
+ ///
+ /// Created date
+ ///
+ CreatedDate = 55,
+
+ ///
+ /// Created time
+ ///
+ CreatedTime = 60,
+
+ ///
+ /// Digital creation date
+ ///
+ DigitalCreationDate = 62,
+
+ ///
+ /// Digital creation time
+ ///
+ DigitalCreationTime = 63,
+
+ ///
+ /// Originating program
+ ///
+ OriginatingProgram = 65,
+
+ ///
+ /// Program version
+ ///
+ ProgramVersion = 70,
+
+ ///
+ /// Object cycle
+ ///
+ ObjectCycle = 75,
+
+ ///
+ /// Byline
+ ///
+ Byline = 80,
+
+ ///
+ /// Byline title
+ ///
+ BylineTitle = 85,
+
+ ///
+ /// City
+ ///
+ City = 90,
+
+ ///
+ /// Sub location
+ ///
+ SubLocation = 92,
+
+ ///
+ /// Province/State
+ ///
+ ProvinceState = 95,
+
+ ///
+ /// Country code
+ ///
+ CountryCode = 100,
+
+ ///
+ /// Country
+ ///
+ Country = 101,
+
+ ///
+ /// Original transmission reference
+ ///
+ OriginalTransmissionReference = 103,
+
+ ///
+ /// Headline
+ ///
+ Headline = 105,
+
+ ///
+ /// Credit
+ ///
+ Credit = 110,
+
+ ///
+ /// Source
+ ///
+ Source = 115,
+
+ ///
+ /// Copyright notice
+ ///
+ CopyrightNotice = 116,
+
+ ///
+ /// Contact
+ ///
+ Contact = 118,
+
+ ///
+ /// Caption
+ ///
+ Caption = 120,
+
+ ///
+ /// Local caption
+ ///
+ LocalCaption = 121,
+
+ ///
+ /// Caption writer
+ ///
+ CaptionWriter = 122,
+
+ ///
+ /// Image type
+ ///
+ ImageType = 130,
+
+ ///
+ /// Image orientation
+ ///
+ ImageOrientation = 131,
+
+ ///
+ /// Custom field 1
+ ///
+ CustomField1 = 200,
+
+ ///
+ /// Custom field 2
+ ///
+ CustomField2 = 201,
+
+ ///
+ /// Custom field 3
+ ///
+ CustomField3 = 202,
+
+ ///
+ /// Custom field 4
+ ///
+ CustomField4 = 203,
+
+ ///
+ /// Custom field 5
+ ///
+ CustomField5 = 204,
+
+ ///
+ /// Custom field 6
+ ///
+ CustomField6 = 205,
+
+ ///
+ /// Custom field 7
+ ///
+ CustomField7 = 206,
+
+ ///
+ /// Custom field 8
+ ///
+ CustomField8 = 207,
+
+ ///
+ /// Custom field 9
+ ///
+ CustomField9 = 208,
+
+ ///
+ /// Custom field 10
+ ///
+ CustomField10 = 209,
+
+ ///
+ /// Custom field 11
+ ///
+ CustomField11 = 210,
+
+ ///
+ /// Custom field 12
+ ///
+ CustomField12 = 211,
+
+ ///
+ /// Custom field 13
+ ///
+ CustomField13 = 212,
+
+ ///
+ /// Custom field 14
+ ///
+ CustomField14 = 213,
+
+ ///
+ /// Custom field 15
+ ///
+ CustomField15 = 214,
+
+ ///
+ /// Custom field 16
+ ///
+ CustomField16 = 215,
+
+ ///
+ /// Custom field 17
+ ///
+ CustomField17 = 216,
+
+ ///
+ /// Custom field 18
+ ///
+ CustomField18 = 217,
+
+ ///
+ /// Custom field 19
+ ///
+ CustomField19 = 218,
+
+ ///
+ /// Custom field 20
+ ///
+ CustomField20 = 219,
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
new file mode 100644
index 000000000..c23a7793e
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
@@ -0,0 +1,167 @@
+// 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
+{
+ ///
+ /// A value of the iptc profile.
+ ///
+ public sealed class IptcValue
+ {
+ private byte[] data;
+ private Encoding encoding;
+
+ internal IptcValue(IptcTag tag, byte[] value)
+ {
+ Guard.NotNull(value, nameof(value));
+
+ this.Tag = tag;
+ this.data = value;
+ this.encoding = Encoding.UTF8;
+ }
+
+ internal IptcValue(IptcTag tag, Encoding encoding, string value)
+ {
+ this.Tag = tag;
+ this.encoding = encoding;
+ this.Value = value;
+ }
+
+ ///
+ /// Gets or sets the encoding to use for the Value.
+ ///
+ public Encoding Encoding
+ {
+ get => this.encoding;
+ set
+ {
+ if (value != null)
+ {
+ this.encoding = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets the tag of the iptc value.
+ ///
+ public IptcTag Tag { get; }
+
+ ///
+ /// Gets or sets the value.
+ ///
+ public string Value
+ {
+ get => this.encoding.GetString(this.data);
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ this.data = new byte[0];
+ }
+ else
+ {
+ this.data = this.encoding.GetBytes(value);
+ }
+ }
+ }
+
+ ///
+ /// Gets the length of the value.
+ ///
+ public int Length => this.data.Length;
+
+ ///
+ /// Determines whether the specified object is equal to the current .
+ ///
+ /// The object to compare this with.
+ /// True when the specified object is equal to the current .
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ return this.Equals(obj as IptcValue);
+ }
+
+ ///
+ /// Determines whether the specified iptc value is equal to the current .
+ ///
+ /// The iptc value to compare this with.
+ /// True when the specified iptc value is equal to the current .
+ public bool Equals(IptcValue other)
+ {
+ if (other is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ if (this.Tag != other.Tag)
+ {
+ return false;
+ }
+
+ byte[] data = other.ToByteArray();
+
+ if (this.data.Length != data.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.data.Length; i++)
+ {
+ if (this.data[i] != data[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Serves as a hash of this type.
+ ///
+ /// A hash code for the current instance.
+ public override int GetHashCode() => HashCode.Combine(this.data, this.Tag);
+
+ ///
+ /// Converts this instance to a byte array.
+ ///
+ /// A array.
+ public byte[] ToByteArray()
+ {
+ var result = new byte[this.data.Length];
+ this.data.CopyTo(result, 0);
+ return result;
+ }
+
+ ///
+ /// Returns a string that represents the current value.
+ ///
+ /// A string that represents the current value.
+ public override string ToString() => this.Value;
+
+ ///
+ /// Returns a string that represents the current value with the specified encoding.
+ ///
+ /// The encoding to use.
+ /// A string that represents the current value with the specified encoding.
+ public string ToString(Encoding enc)
+ {
+ Guard.NotNull(enc, nameof(enc));
+
+ return enc.GetString(this.data);
+ }
+ }
+}
diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/README.md b/src/ImageSharp/Metadata/Profiles/IPTC/README.md
new file mode 100644
index 000000000..0b0efc967
--- /dev/null
+++ b/src/ImageSharp/Metadata/Profiles/IPTC/README.md
@@ -0,0 +1,9 @@
+IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET)
+
+Information about IPTC can be found here in the folowing 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)
\ No newline at end of file