mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 872 additions and 9 deletions
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// Class that can be used to access an Iptc profile.
|
|||
/// </summary>
|
|||
/// <remarks>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
|
|||
/// </remarks>
|
|||
public sealed class IptcProfile |
|||
{ |
|||
private Collection<IptcValue> values; |
|||
|
|||
private byte[] data; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
|
|||
/// </summary>
|
|||
public IptcProfile() |
|||
{ |
|||
} |
|||
|
|||
/// <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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the values of this iptc profile.
|
|||
/// </summary>
|
|||
public IEnumerable<IptcValue> Values |
|||
{ |
|||
get |
|||
{ |
|||
this.Initialize(); |
|||
return this.values; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the value with the specified tag.
|
|||
/// </summary>
|
|||
/// <param name="tag">The tag of the iptc value.</param>
|
|||
/// <returns>The value with the specified tag.</returns>
|
|||
public IptcValue GetValue(IptcTag tag) |
|||
{ |
|||
foreach (IptcValue iptcValue in this.Values) |
|||
{ |
|||
if (iptcValue.Tag == tag) |
|||
{ |
|||
return iptcValue; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the value with the specified tag.
|
|||
/// </summary>
|
|||
/// <param name="tag">The tag of the iptc value.</param>
|
|||
/// <returns>True when the value was fount and removed.</returns>
|
|||
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; |
|||
} |
|||
|
|||
/// <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 of 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>
|
|||
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)); |
|||
} |
|||
|
|||
/// <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>
|
|||
public void SetValue(IptcTag tag, string value) => this.SetValue(tag, Encoding.UTF8, value); |
|||
|
|||
/// <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)); |
|||
} |
|||
|
|||
i += count; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,351 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc |
|||
{ |
|||
/// <summary>
|
|||
/// All iptc tags.
|
|||
/// </summary>
|
|||
public enum IptcTag |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown
|
|||
/// </summary>
|
|||
Unknown = -1, |
|||
|
|||
/// <summary>
|
|||
/// Record version
|
|||
/// </summary>
|
|||
RecordVersion = 0, |
|||
|
|||
/// <summary>
|
|||
/// Object type
|
|||
/// </summary>
|
|||
ObjectType = 3, |
|||
|
|||
/// <summary>
|
|||
/// Object attribute
|
|||
/// </summary>
|
|||
ObjectAttribute = 4, |
|||
|
|||
/// <summary>
|
|||
/// Title
|
|||
/// </summary>
|
|||
Title = 5, |
|||
|
|||
/// <summary>
|
|||
/// Edit status
|
|||
/// </summary>
|
|||
EditStatus = 7, |
|||
|
|||
/// <summary>
|
|||
/// Editorial update
|
|||
/// </summary>
|
|||
EditorialUpdate = 8, |
|||
|
|||
/// <summary>
|
|||
/// Priority
|
|||
/// </summary>
|
|||
Priority = 10, |
|||
|
|||
/// <summary>
|
|||
/// Category
|
|||
/// </summary>
|
|||
Category = 15, |
|||
|
|||
/// <summary>
|
|||
/// Supplemental categories
|
|||
/// </summary>
|
|||
SupplementalCategories = 20, |
|||
|
|||
/// <summary>
|
|||
/// Fixture identifier
|
|||
/// </summary>
|
|||
FixtureIdentifier = 22, |
|||
|
|||
/// <summary>
|
|||
/// Keyword
|
|||
/// </summary>
|
|||
Keyword = 25, |
|||
|
|||
/// <summary>
|
|||
/// Location code
|
|||
/// </summary>
|
|||
LocationCode = 26, |
|||
|
|||
/// <summary>
|
|||
/// Location name
|
|||
/// </summary>
|
|||
LocationName = 27, |
|||
|
|||
/// <summary>
|
|||
/// Release date
|
|||
/// </summary>
|
|||
ReleaseDate = 30, |
|||
|
|||
/// <summary>
|
|||
/// Release time
|
|||
/// </summary>
|
|||
ReleaseTime = 35, |
|||
|
|||
/// <summary>
|
|||
/// Expiration date
|
|||
/// </summary>
|
|||
ExpirationDate = 37, |
|||
|
|||
/// <summary>
|
|||
/// Expiration time
|
|||
/// </summary>
|
|||
ExpirationTime = 38, |
|||
|
|||
/// <summary>
|
|||
/// Special instructions
|
|||
/// </summary>
|
|||
SpecialInstructions = 40, |
|||
|
|||
/// <summary>
|
|||
/// Action advised
|
|||
/// </summary>
|
|||
ActionAdvised = 42, |
|||
|
|||
/// <summary>
|
|||
/// Reference service
|
|||
/// </summary>
|
|||
ReferenceService = 45, |
|||
|
|||
/// <summary>
|
|||
/// Reference date
|
|||
/// </summary>
|
|||
ReferenceDate = 47, |
|||
|
|||
/// <summary>
|
|||
/// ReferenceNumber
|
|||
/// </summary>
|
|||
ReferenceNumber = 50, |
|||
|
|||
/// <summary>
|
|||
/// Created date
|
|||
/// </summary>
|
|||
CreatedDate = 55, |
|||
|
|||
/// <summary>
|
|||
/// Created time
|
|||
/// </summary>
|
|||
CreatedTime = 60, |
|||
|
|||
/// <summary>
|
|||
/// Digital creation date
|
|||
/// </summary>
|
|||
DigitalCreationDate = 62, |
|||
|
|||
/// <summary>
|
|||
/// Digital creation time
|
|||
/// </summary>
|
|||
DigitalCreationTime = 63, |
|||
|
|||
/// <summary>
|
|||
/// Originating program
|
|||
/// </summary>
|
|||
OriginatingProgram = 65, |
|||
|
|||
/// <summary>
|
|||
/// Program version
|
|||
/// </summary>
|
|||
ProgramVersion = 70, |
|||
|
|||
/// <summary>
|
|||
/// Object cycle
|
|||
/// </summary>
|
|||
ObjectCycle = 75, |
|||
|
|||
/// <summary>
|
|||
/// Byline
|
|||
/// </summary>
|
|||
Byline = 80, |
|||
|
|||
/// <summary>
|
|||
/// Byline title
|
|||
/// </summary>
|
|||
BylineTitle = 85, |
|||
|
|||
/// <summary>
|
|||
/// City
|
|||
/// </summary>
|
|||
City = 90, |
|||
|
|||
/// <summary>
|
|||
/// Sub location
|
|||
/// </summary>
|
|||
SubLocation = 92, |
|||
|
|||
/// <summary>
|
|||
/// Province/State
|
|||
/// </summary>
|
|||
ProvinceState = 95, |
|||
|
|||
/// <summary>
|
|||
/// Country code
|
|||
/// </summary>
|
|||
CountryCode = 100, |
|||
|
|||
/// <summary>
|
|||
/// Country
|
|||
/// </summary>
|
|||
Country = 101, |
|||
|
|||
/// <summary>
|
|||
/// Original transmission reference
|
|||
/// </summary>
|
|||
OriginalTransmissionReference = 103, |
|||
|
|||
/// <summary>
|
|||
/// Headline
|
|||
/// </summary>
|
|||
Headline = 105, |
|||
|
|||
/// <summary>
|
|||
/// Credit
|
|||
/// </summary>
|
|||
Credit = 110, |
|||
|
|||
/// <summary>
|
|||
/// Source
|
|||
/// </summary>
|
|||
Source = 115, |
|||
|
|||
/// <summary>
|
|||
/// Copyright notice
|
|||
/// </summary>
|
|||
CopyrightNotice = 116, |
|||
|
|||
/// <summary>
|
|||
/// Contact
|
|||
/// </summary>
|
|||
Contact = 118, |
|||
|
|||
/// <summary>
|
|||
/// Caption
|
|||
/// </summary>
|
|||
Caption = 120, |
|||
|
|||
/// <summary>
|
|||
/// Local caption
|
|||
/// </summary>
|
|||
LocalCaption = 121, |
|||
|
|||
/// <summary>
|
|||
/// Caption writer
|
|||
/// </summary>
|
|||
CaptionWriter = 122, |
|||
|
|||
/// <summary>
|
|||
/// Image type
|
|||
/// </summary>
|
|||
ImageType = 130, |
|||
|
|||
/// <summary>
|
|||
/// Image orientation
|
|||
/// </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, |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// A value of the iptc profile.
|
|||
/// </summary>
|
|||
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; |
|||
} |
|||
|
|||
/// <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 the value.
|
|||
/// </summary>
|
|||
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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the value.
|
|||
/// </summary>
|
|||
public int Length => this.data.Length; |
|||
|
|||
/// <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; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
|
|||
/// <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="enc">The encoding to use.</param>
|
|||
/// <returns>A string that represents the current value with the specified encoding.</returns>
|
|||
public string ToString(Encoding enc) |
|||
{ |
|||
Guard.NotNull(enc, nameof(enc)); |
|||
|
|||
return enc.GetString(this.data); |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
Loading…
Reference in new issue