Browse Source

Read only API for XmpProfile

pull/1918/head
Ynse Hoornenborg 4 years ago
parent
commit
ded8b20b5c
  1. 3
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  2. 1
      src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs
  3. 3
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  4. 3
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  5. 3
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  6. 2
      src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs
  7. 2
      src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs
  8. 165
      src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs
  9. 6
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  10. 2
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
  11. 2
      tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs
  12. 67
      tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

3
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -338,8 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Application Extension: XMP Profile.
if (xmpProfile != null)
{
xmpProfile.UpdateData();
var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data);
var xmpExtension = new GifXmpApplicationExtension(xmpProfile.ToByteArray());
this.WriteExtension(xmpExtension, stream);
}
}

1
src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Gif
{

3
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -485,8 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
const int Max = 65533;
const int MaxData = Max - XmpOverheadLength;
xmpProfile.UpdateData();
byte[] data = xmpProfile.Data;
byte[] data = xmpProfile.ToByteArray();
if (data is null || data.Length == 0)
{

3
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -673,8 +673,7 @@ namespace SixLabors.ImageSharp.Formats.Png
return;
}
meta.XmpProfile.UpdateData();
var xmpData = meta.XmpProfile.Data;
var xmpData = meta.XmpProfile.ToByteArray();
if (xmpData.Length == 0)
{

3
src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs

@ -202,10 +202,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (xmpProfile != null)
{
xmpProfile.UpdateData();
var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte)
{
Value = xmpProfile.Data
Value = xmpProfile.ToByteArray()
};
this.Collector.Add(xmp);

2
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

@ -427,7 +427,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{
isVp8X = true;
riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.Data;
xmpBytes = xmpProfile.ToByteArray();
riffSize += this.MetadataChunkSize(xmpBytes);
}

2
src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs

@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter
{
isVp8X = true;
riffSize += ExtendedFileChunkSize;
xmpBytes = xmpProfile.Data;
xmpBytes = xmpProfile.ToByteArray();
riffSize += this.MetadataChunkSize(xmpBytes);
}

165
src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs

@ -3,7 +3,6 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// </summary>
public sealed class XmpProfile : IDeepCloneable<XmpProfile>
{
private XDocument document;
private byte[] data;
/// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class.
@ -29,17 +28,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// Initializes a new instance of the <see cref="XmpProfile"/> class.
/// </summary>
/// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
public XmpProfile(byte[] data) => this.Data = data;
/// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class, based on the speicief <see cref="XDocument"/>.
/// </summary>
/// <param name="doc">The document to base this instance on.</param>
public XmpProfile(XDocument doc)
{
this.document = doc;
this.UpdateData();
}
public XmpProfile(byte[] data) => this.data = data;
/// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class
@ -50,150 +39,48 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
{
Guard.NotNull(other, nameof(other));
if (other.Data != null)
{
this.Data = new byte[other.Data.Length];
other.Data.AsSpan().CopyTo(this.Data);
}
}
/// <summary>
/// Gets the rax XML document containing the XMP profile.
/// </summary>
public XDocument Document
{
get
{
this.InitializeDocument();
return this.document;
}
}
/// <summary>
/// Gets the byte data of the XMP profile.
/// </summary>
public byte[] Data { get; private set; }
/// <summary>
/// Checks whether two <see cref="XmpProfile"/> structures are equal.
/// </summary>
/// <param name="left">The left hand <see cref="XmpProfile"/> operand.</param>
/// <param name="right">The right hand <see cref="XmpProfile"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator ==(XmpProfile left, XmpProfile right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (left is null)
{
return false;
}
return left.Equals(right);
this.data = other.ToByteArray();
}
/// <summary>
/// Checks whether two <see cref="XmpProfile"/> structures are not equal.
/// Gets the raw XML document containing the XMP profile.
/// </summary>
/// <param name="left">The left hand <see cref="XmpProfile"/> operand.</param>
/// <param name="right">The right hand <see cref="XmpProfile"/> operand.</param>
/// <returns>
/// True if the <paramref name="left"/> parameter is not equal to the <paramref name="right"/> parameter;
/// otherwise, false.
/// </returns>
public static bool operator !=(XmpProfile left, XmpProfile right) => !(left == right);
/// <inheritdoc/>
public XmpProfile DeepClone() => new(this);
/// <summary>
/// Updates the data of the profile.
/// </summary>
public void UpdateData()
/// <returns>The <see cref="XDocument"/></returns>
public XDocument GetDocument()
{
if (this.document == null)
byte[] byteArray = this.ToByteArray();
if (byteArray is null)
{
return;
}
int initialLength = 256;
if (this.Data != null)
{
initialLength = this.Data.Length;
}
using var stream = new MemoryStream(initialLength);
using var writer = new StreamWriter(stream, Encoding.UTF8);
this.document.Save(writer);
this.Data = stream.ToArray();
}
/// <inheritdoc />
public override int GetHashCode() => base.GetHashCode();
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj is not XmpProfile other)
{
return false;
}
XElement thisRoot = this.Document.Root;
XElement otherRoot = other.Document.Root;
return this.CompareElements(thisRoot, otherRoot);
}
private bool CompareElements(XElement left, XElement right)
{
var comparer = new XElementEqualityComparer();
bool result = comparer.Equals(left, right);
if (result)
{
result |= !left.Elements().Except(right.Elements(), comparer).Any();
}
return result;
}
private void InitializeDocument()
{
if (!(this.document is null))
{
return;
}
if (this.Data is null)
{
return;
return null;
}
// Strip leading whitespace, as the XmlReader doesn't like them.
int count = this.Data.Length;
int count = byteArray.Length;
for (int i = count - 1; i > 0; i--)
{
if (this.Data[i] is 0 or 0x0f)
if (byteArray[i] is 0 or 0x0f)
{
count--;
}
}
using var stream = new MemoryStream(this.Data, 0, count);
using var stream = new MemoryStream(byteArray, 0, count);
using var reader = new StreamReader(stream, Encoding.UTF8);
this.document = XDocument.Load(reader);
return XDocument.Load(reader);
}
// In case we removed any trailing bytes, update the Data property accordingly.
if (count != this.Data.Length)
{
this.UpdateData();
}
/// <summary>
/// Convert the content of this <see cref="XmpProfile"/> into a byte array.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
public byte[] ToByteArray()
{
byte[] result = new byte[this.data.Length];
this.data.AsSpan().CopyTo(result);
return result;
}
/// <inheritdoc/>
public XmpProfile DeepClone() => new(this);
}
}

6
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
Assert.NotNull(rootFrameMetaData.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile);
Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length);
Assert.Equal(2599, rootFrameMetaData.XmpProfile.ToByteArray().Length);
Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count);
}
}
@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(32, rootFrame.Width);
Assert.Equal(32, rootFrame.Height);
Assert.NotNull(rootFrame.Metadata.XmpProfile);
Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length);
Assert.Equal(2599, rootFrame.Metadata.XmpProfile.ToByteArray().Length);
ExifProfile exifProfile = rootFrame.Metadata.ExifProfile;
TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata();
@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.NotNull(xmpProfileInput);
Assert.NotNull(encodedImageXmpProfile);
Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data);
Assert.Equal(xmpProfileInput.ToByteArray(), encodedImageXmpProfile.ToByteArray());
Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value);
Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value);

2
tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
else
{
Assert.NotNull(image.Metadata.XmpProfile);
Assert.NotEmpty(image.Metadata.XmpProfile.Data);
Assert.NotEmpty(image.Metadata.XmpProfile.ToByteArray());
}
}

2
tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs

@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata
Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile));
Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count);
Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile));
Assert.True(metaData.XmpProfile.Data.Length.Equals(clone.XmpProfile.Data.Length));
Assert.True(metaData.XmpProfile.ToByteArray().Length.Equals(clone.XmpProfile.ToByteArray().Length));
Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata()));
Assert.False(metaData.IccProfile.Equals(clone.IccProfile));
Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile));

67
tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs

@ -92,53 +92,17 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
}
[Fact]
public void XmpProfile_ToAndFromByteArray_Works()
public void XmpProfile_ToFromByteArray_ReturnsClone()
{
// arrange
XmpProfile profile = CreateMinimalXmlProfile();
profile.Document.Root.AddFirst(new XElement(XName.Get("written")));
byte[] original = profile.ToByteArray();
// act
profile.UpdateData();
byte[] profileBytes = profile.Data;
var profileFromBytes = new XmpProfile(profileBytes);
byte[] actual = profile.ToByteArray();
// assert
XmpProfileContainsExpectedValues(profileFromBytes);
Assert.Equal("written", ((XElement)profileFromBytes.Document.Root.FirstNode).Name);
}
[Fact]
public void XmpProfile_EqualityIsByValue()
{
// arrange
XmpProfile original = CreateMinimalXmlProfile();
var other = new XmpProfile(original.Data);
// act
bool equals = original.Equals(other);
bool equality = original == other;
bool inequality = original != other;
// assert
Assert.True(equals);
Assert.True(equality);
Assert.False(inequality);
Assert.Equal(original, other);
}
[Fact]
public void XmpProfile_DocumentConstructor()
{
// arrange
XmpProfile original = CreateMinimalXmlProfile();
// act
var actual = new XmpProfile(original.Document);
// assert
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.False(ReferenceEquals(original, actual));
}
[Fact]
@ -146,15 +110,14 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{
// arrange
XmpProfile profile = CreateMinimalXmlProfile();
profile.Document.Root.AddFirst(new XElement(XName.Get("written")));
byte[] original = profile.ToByteArray();
// act
XmpProfile clone = profile.DeepClone();
clone.Document.Root.AddFirst(new XElement(XName.Get("onlyonclone")));
byte[] actual = clone.ToByteArray();
// assert
XmpProfileContainsExpectedValues(clone);
Assert.Equal("onlyonclone", ((XElement)clone.Document.Root.FirstNode).Name);
Assert.False(ReferenceEquals(original, actual));
}
[Fact]
@ -172,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray());
}
[Fact]
@ -190,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray());
}
[Fact]
@ -208,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray());
}
[Fact]
@ -226,7 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray());
}
[Fact]
@ -244,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray());
}
[Fact]
@ -262,13 +225,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual);
Assert.Equal(original.ToByteArray(), actual.ToByteArray());
}
private static void XmpProfileContainsExpectedValues(XmpProfile xmp)
{
Assert.NotNull(xmp);
XDocument document = xmp.Document;
XDocument document = xmp.GetDocument();
Assert.NotNull(document);
Assert.Equal("xmpmeta", document.Root.Name.LocalName);
Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName);
@ -276,7 +239,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
private static XmpProfile CreateMinimalXmlProfile()
{
string content = $"<?xpacket begin='' id='{Guid.NewGuid()}'?><x:xmpmeta xmlns:x='adobe:ns:meta/'></x:xmpmeta><?xpacket end='w'?>";
string content = $"<?xpacket begin='' id='{Guid.NewGuid()}'?><x:xmpmeta xmlns:x='adobe:ns:meta/'></x:xmpmeta><?xpacket end='w'?> ";
byte[] data = Encoding.UTF8.GetBytes(content);
var profile = new XmpProfile(data);
return profile;

Loading…
Cancel
Save