Browse Source

Limit length of iptc values according to the spec

pull/1574/head
Brian Popow 6 years ago
parent
commit
6a54eef87f
  1. 77
      src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs
  2. 119
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs
  3. 121
      src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs
  4. 37
      src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs
  5. 6
      src/ImageSharp/Metadata/Profiles/IPTC/README.md
  6. 56
      tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs

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

@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
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));
@ -85,16 +90,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// <returns>The values found with the specified tag.</returns>
public List<IptcValue> GetValues(IptcTag tag)
{
var values = new List<IptcValue>();
var iptcValues = new List<IptcValue>();
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
values.Add(iptcValue);
iptcValues.Add(iptcValue);
}
}
return values;
return iptcValues;
}
/// <summary>
@ -157,21 +162,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
/// <summary>
/// Sets the value of the specified tag.
/// 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>
public void SetValue(IptcTag tag, Encoding encoding, string value)
/// <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));
if (!this.IsRepeatable(tag))
if (!tag.IsRepeatable())
{
foreach (IptcValue iptcValue in this.Values)
{
if (iptcValue.Tag == tag)
{
iptcValue.Strict = strict;
iptcValue.Encoding = encoding;
iptcValue.Value = value;
return;
@ -179,7 +189,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
}
this.values.Add(new IptcValue(tag, encoding, value));
this.values.Add(new IptcValue(tag, encoding, value, strict));
}
/// <summary>
@ -187,7 +197,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// </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);
/// <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.
@ -251,56 +265,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
if ((count > 0) && (i + count <= this.Data.Length))
{
Buffer.BlockCopy(this.Data, i, iptcData, 0, count);
this.values.Add(new IptcValue(tag, iptcData));
this.values.Add(new IptcValue(tag, iptcData, false));
}
i += count;
}
}
private bool IsRepeatable(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;
}
}
}
}

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

@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
/// <summary>
/// All iptc tags.
/// All iptc tags relevant for images.
/// </summary>
public enum IptcTag
{
@ -14,222 +14,245 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
Unknown = -1,
/// <summary>
/// Record version, not repeatable.
/// Record version identifying the version of the Information Interchange Model.
/// Not repeatable. Max length is 2.
/// </summary>
RecordVersion = 0,
/// <summary>
/// Object type, not repeatable.
/// Object type, not repeatable. Max Length is 67.
/// </summary>
ObjectType = 3,
/// <summary>
/// Object attribute.
/// Object attribute. Max length is 68.
/// </summary>
ObjectAttribute = 4,
/// <summary>
/// Object Name, not repeatable.
/// Object Name, not repeatable. Max length is 64.
/// </summary>
Name = 5,
/// <summary>
/// Edit status, not repeatable.
/// Edit status, not repeatable. Max length is 64.
/// </summary>
EditStatus = 7,
/// <summary>
/// Editorial update, not repeatable.
/// Editorial update, not repeatable. Max length is 2.
/// </summary>
EditorialUpdate = 8,
/// <summary>
/// Urgency, not repeatable.
/// Urgency, not repeatable. Max length is 2.
/// </summary>
Urgency = 10,
/// <summary>
/// Subject Reference.
/// Subject Reference. Max length is 236.
/// </summary>
SubjectReference = 12,
/// <summary>
/// Category, not repeatable.
/// Category, not repeatable. Max length is 3.
/// </summary>
Category = 15,
/// <summary>
/// Supplemental categories.
/// Supplemental categories. Max length is 32.
/// </summary>
SupplementalCategories = 20,
/// <summary>
/// Fixture identifier, not repeatable.
/// Fixture identifier, not repeatable. Max length is 32.
/// </summary>
FixtureIdentifier = 22,
/// <summary>
/// Keywords.
/// Keywords. Max length is 64.
/// </summary>
Keywords = 25,
/// <summary>
/// Location code.
/// Location code. Max length is 3.
/// </summary>
LocationCode = 26,
/// <summary>
/// Location name.
/// Location name. Max length is 64.
/// </summary>
LocationName = 27,
/// <summary>
/// Release date, not repeatable.
/// Release date. Format should be CCYYMMDD,
/// e.g. "19890317" indicates data for release on 17 March 1989.
/// Not repeatable, max length is 8.
/// </summary>
ReleaseDate = 30,
/// <summary>
/// Release time, not repeatable.
/// Release time. Format should be HHMMSS±HHMM,
/// e.g. "090000-0500" indicates object for use after 0900 in
/// New York (five hours behind UTC)
/// Not repeatable, max length is 11.
/// </summary>
ReleaseTime = 35,
/// <summary>
/// Expiration date, not repeatable.
/// Expiration date. Format should be CCYYMMDD,
/// e.g. "19890317" indicates data for release on 17 March 1989.
/// Not repeatable, max length is 8.
/// </summary>
ExpirationDate = 37,
/// <summary>
/// Expiration time, not repeatable.
/// Expiration time. Format should be HHMMSS±HHMM,
/// e.g. "090000-0500" indicates object for use after 0900 in
/// New York (five hours behind UTC)
/// Not repeatable, max length is 11.
/// </summary>
ExpirationTime = 38,
/// <summary>
/// Special instructions, not repeatable.
/// Special instructions, not repeatable. Max length is 256.
/// </summary>
SpecialInstructions = 40,
/// <summary>
/// Action advised, not repeatable.
/// Action advised, not repeatable. Max length is 2.
/// </summary>
ActionAdvised = 42,
/// <summary>
/// Reference service.
/// Reference service. Max length is 10.
/// </summary>
ReferenceService = 45,
/// <summary>
/// Reference date.
/// Reference date. Format should be CCYYMMDD,
/// e.g. "19890317" indicates data for release on 17 March 1989.
/// Not repeatable, max length is 8.
/// </summary>
ReferenceDate = 47,
/// <summary>
/// ReferenceNumber.
/// ReferenceNumber. Max length is 8.
/// </summary>
ReferenceNumber = 50,
/// <summary>
/// Created date, not repeatable.
/// Created date. Format should be CCYYMMDD,
/// e.g. "19890317" indicates data for release on 17 March 1989.
/// Not repeatable, max length is 8.
/// </summary>
CreatedDate = 55,
/// <summary>
/// Created time, not repeatable.
/// Created time. Format should be HHMMSS±HHMM,
/// e.g. "090000-0500" indicates object for use after 0900 in
/// New York (five hours behind UTC)
/// Not repeatable, max length is 11.
/// </summary>
CreatedTime = 60,
/// <summary>
/// Digital creation date, not repeatable.
/// Digital creation date. Format should be CCYYMMDD,
/// e.g. "19890317" indicates data for release on 17 March 1989.
/// Not repeatable, max length is 8.
/// </summary>
DigitalCreationDate = 62,
/// <summary>
/// Digital creation time, not repeatable.
/// Digital creation time. Format should be HHMMSS±HHMM,
/// e.g. "090000-0500" indicates object for use after 0900 in
/// New York (five hours behind UTC)
/// Not repeatable, max length is 11.
/// </summary>
DigitalCreationTime = 63,
/// <summary>
/// Originating program, not repeatable.
/// Originating program, not repeatable. Max length is 32.
/// </summary>
OriginatingProgram = 65,
/// <summary>
/// Program version, not repeatable.
/// Program version, not repeatable. Max length is 10.
/// </summary>
ProgramVersion = 70,
/// <summary>
/// Object cycle, not repeatable.
/// Object cycle, not repeatable. Max length is 1.
/// </summary>
ObjectCycle = 75,
/// <summary>
/// Byline.
/// Byline. Max length is 32.
/// </summary>
Byline = 80,
/// <summary>
/// Byline title.
/// Byline title. Max length is 32.
/// </summary>
BylineTitle = 85,
/// <summary>
/// City, not repeatable.
/// City, not repeatable. Max length is 32.
/// </summary>
City = 90,
/// <summary>
/// Sub location, not repeatable.
/// Sub location, not repeatable. Max length is 32.
/// </summary>
SubLocation = 92,
/// <summary>
/// Province/State, not repeatable.
/// Province/State, not repeatable. Max length is 32.
/// </summary>
ProvinceState = 95,
/// <summary>
/// Country code, not repeatable.
/// Country code, not repeatable. Max length is 3.
/// </summary>
CountryCode = 100,
/// <summary>
/// Country, not repeatable.
/// Country, not repeatable. Max length is 64.
/// </summary>
Country = 101,
/// <summary>
/// Original transmission reference, not repeatable.
/// Original transmission reference, not repeatable. Max length is 32.
/// </summary>
OriginalTransmissionReference = 103,
/// <summary>
/// Headline, not repeatable.
/// Headline, not repeatable. Max length is 256.
/// </summary>
Headline = 105,
/// <summary>
/// Credit, not repeatable.
/// Credit, not repeatable. Max length is 32.
/// </summary>
Credit = 110,
/// <summary>
/// Source, not repeatable.
/// Source, not repeatable. Max length is 32.
/// </summary>
Source = 115,
/// <summary>
/// Copyright notice, not repeatable.
/// Copyright notice, not repeatable. Max length is 128.
/// </summary>
CopyrightNotice = 116,
/// <summary>
/// Contact.
/// Contact. Max length 128.
/// </summary>
Contact = 118,
/// <summary>
/// Caption, not repeatable.
/// Caption, not repeatable. Max length is 2000.
/// </summary>
Caption = 120,
@ -239,17 +262,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
LocalCaption = 121,
/// <summary>
/// Caption writer.
/// Caption writer. Max length is 32.
/// </summary>
CaptionWriter = 122,
/// <summary>
/// Image type, not repeatable.
/// Image type, not repeatable. Max length is 2.
/// </summary>
ImageType = 130,
/// <summary>
/// Image orientation, not repeatable.
/// Image orientation, not repeatable. Max length is 1.
/// </summary>
ImageOrientation = 131,

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

@ -0,0 +1,121 @@
// 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;
}
}
}
}

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

@ -28,24 +28,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
}
this.Tag = other.Tag;
this.Strict = other.Strict;
}
internal IptcValue(IptcTag tag, byte[] value)
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)
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>
@ -66,6 +77,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
/// </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>
@ -76,11 +93,23 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc
{
if (string.IsNullOrEmpty(value))
{
this.data = new byte[0];
this.data = Array.Empty<byte>();
}
else
{
this.data = this.encoding.GetBytes(value);
int maxLength = this.Tag.MaxLength();
byte[] valueBytes;
if (this.Strict && value.Length > maxLength)
{
var cappedValue = value.Substring(0, maxLength);
valueBytes = this.encoding.GetBytes(cappedValue);
}
else
{
valueBytes = this.encoding.GetBytes(value);
}
this.data = valueBytes;
}
}
}

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

@ -1,9 +1,11 @@
IPTC source code is from [Magick.NET](https://github.com/dlemstra/Magick.NET)
Information about IPTC can be found here in the folowing sources:
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)
- [Adobe File Formats Specification](http://oldschoolprg.x10.mx/downloads/ps6ffspecsv2.pdf)
- [Tag Overview](https://exiftool.org/TagNames/IPTC.html)

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

@ -1,6 +1,7 @@
// 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;
@ -15,6 +16,31 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
{
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]
[WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)]
public void ReadIptcMetadata_Works<TPixel>(TestImageProvider<TPixel> provider)
@ -91,16 +117,17 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
// 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);
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");
var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true);
// act
IptcValue clone = iptcValue.DeepClone();
@ -132,6 +159,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption);
}
[Fact]
public void IptcProfile_SetNewValue_RespectsMaxLength()
{
// arrange
var profile = new IptcProfile();
}
[Theory]
[InlineData(IptcTag.ObjectAttribute)]
[InlineData(IptcTag.SubjectReference)]
@ -153,10 +187,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
var profile = new IptcProfile();
var expectedValue1 = "test";
var expectedValue2 = "another one";
profile.SetValue(tag, expectedValue1);
profile.SetValue(tag, expectedValue1, false);
// act
profile.SetValue(tag, expectedValue2);
profile.SetValue(tag, expectedValue2, false);
// assert
var values = profile.Values.ToList();
@ -204,10 +238,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
// arrange
var profile = new IptcProfile();
var expectedValue = "another one";
profile.SetValue(tag, "test");
profile.SetValue(tag, "test", false);
// act
profile.SetValue(tag, expectedValue);
profile.SetValue(tag, expectedValue, false);
// assert
var values = profile.Values.ToList();
@ -244,7 +278,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
// assert
Assert.True(result, "removed result should be true");
ContainsIptcValue(profile.Values, IptcTag.Byline, "test");
ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test");
}
[Fact]
@ -264,10 +298,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC
Assert.Equal(2, result.Count);
}
private static void ContainsIptcValue(IEnumerable<IptcValue> values, IptcTag tag, string value)
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))), $"expected iptc value '{value}' was not found for 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)

Loading…
Cancel
Save