Browse Source

Add unit tests for reading IPTC profile

pull/1574/head
Brian Popow 6 years ago
parent
commit
9b5aa5d5cf
  1. 1
      src/ImageSharp/Metadata/ImageMetadata.cs
  2. 3
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  3. 64
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  4. 21
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  5. 88
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs
  6. 1
      tests/ImageSharp.Tests/TestImages.cs
  7. 3
      tests/Images/Input/Jpg/baseline/iptc.jpg

1
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();
}
/// <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;

64
src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs

@ -15,16 +15,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// <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
public sealed class IptcProfile : IDeepCloneable<IptcProfile>
{
private Collection<IptcValue> values;
private byte[] data;
/// <summary>
/// Initializes a new instance of the <see cref="IptcProfile"/> class.
/// </summary>
public IptcProfile()
: this((byte[])null)
{
}
@ -34,9 +33,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// <param name="data">The byte array to read the iptc profile from.</param>
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<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>
@ -49,6 +74,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
}
/// <inheritdoc/>
public IptcProfile DeepClone() => new IptcProfile(this);
/// <summary>
/// Returns the value with the specified tag.
/// </summary>
@ -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<IptcValue>();
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));
}

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

@ -9,11 +9,27 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// <summary>
/// A value of the iptc profile.
/// </summary>
public sealed class IptcValue
public sealed class IptcValue : IDeepCloneable<IptcValue>
{
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
/// </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>

88
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
Assert.NotNull(image.Metadata.IptcProfile);
IEnumerable<IptcValue> 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<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))), $"expected iptc value '{value}' was not found for tag '{tag}'");
}
}
}

1
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 =
{

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
Loading…
Cancel
Save