Browse Source

Read only API for XmpProfile

pull/1918/head
Ynse Hoornenborg 5 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. // Application Extension: XMP Profile.
if (xmpProfile != null) if (xmpProfile != null)
{ {
xmpProfile.UpdateData(); var xmpExtension = new GifXmpApplicationExtension(xmpProfile.ToByteArray());
var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data);
this.WriteExtension(xmpExtension, stream); this.WriteExtension(xmpExtension, stream);
} }
} }

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

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Xmp;
namespace SixLabors.ImageSharp.Formats.Gif 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 Max = 65533;
const int MaxData = Max - XmpOverheadLength; const int MaxData = Max - XmpOverheadLength;
xmpProfile.UpdateData(); byte[] data = xmpProfile.ToByteArray();
byte[] data = xmpProfile.Data;
if (data is null || data.Length == 0) if (data is null || data.Length == 0)
{ {

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

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

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

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

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

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

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

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

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

@ -3,7 +3,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp
/// </summary> /// </summary>
public sealed class XmpProfile : IDeepCloneable<XmpProfile> public sealed class XmpProfile : IDeepCloneable<XmpProfile>
{ {
private XDocument document; private byte[] data;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class. /// 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. /// Initializes a new instance of the <see cref="XmpProfile"/> class.
/// </summary> /// </summary>
/// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param> /// <param name="data">The UTF8 encoded byte array to read the XMP profile from.</param>
public XmpProfile(byte[] data) => this.Data = data; 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();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="XmpProfile"/> class /// 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)); Guard.NotNull(other, nameof(other));
if (other.Data != null) this.data = other.ToByteArray();
{
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);
} }
/// <summary> /// <summary>
/// Checks whether two <see cref="XmpProfile"/> structures are not equal. /// Gets the raw XML document containing the XMP profile.
/// </summary> /// </summary>
/// <param name="left">The left hand <see cref="XmpProfile"/> operand.</param> /// <returns>The <see cref="XDocument"/></returns>
/// <param name="right">The right hand <see cref="XmpProfile"/> operand.</param> public XDocument GetDocument()
/// <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()
{ {
if (this.document == null) byte[] byteArray = this.ToByteArray();
if (byteArray is null)
{ {
return; return null;
}
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;
} }
// Strip leading whitespace, as the XmlReader doesn't like them. // 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--) for (int i = count - 1; i > 0; i--)
{ {
if (this.Data[i] is 0 or 0x0f) if (byteArray[i] is 0 or 0x0f)
{ {
count--; 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); 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. /// <summary>
if (count != this.Data.Length) /// Convert the content of this <see cref="XmpProfile"/> into a byte array.
{ /// </summary>
this.UpdateData(); /// <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.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile); 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); 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.Width);
Assert.Equal(32, rootFrame.Height); Assert.Equal(32, rootFrame.Height);
Assert.NotNull(rootFrame.Metadata.XmpProfile); 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; ExifProfile exifProfile = rootFrame.Metadata.ExifProfile;
TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata();
@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.NotNull(xmpProfileInput); Assert.NotNull(xmpProfileInput);
Assert.NotNull(encodedImageXmpProfile); 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("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value);
Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).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 else
{ {
Assert.NotNull(image.Metadata.XmpProfile); 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.False(metaData.ExifProfile.Equals(clone.ExifProfile));
Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count);
Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); 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.GetGifMetadata().Equals(clone.GetGifMetadata()));
Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IccProfile.Equals(clone.IccProfile));
Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); 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] [Fact]
public void XmpProfile_ToAndFromByteArray_Works() public void XmpProfile_ToFromByteArray_ReturnsClone()
{ {
// arrange // arrange
XmpProfile profile = CreateMinimalXmlProfile(); XmpProfile profile = CreateMinimalXmlProfile();
profile.Document.Root.AddFirst(new XElement(XName.Get("written"))); byte[] original = profile.ToByteArray();
// act // act
profile.UpdateData(); byte[] actual = profile.ToByteArray();
byte[] profileBytes = profile.Data;
var profileFromBytes = new XmpProfile(profileBytes);
// assert // assert
XmpProfileContainsExpectedValues(profileFromBytes); Assert.False(ReferenceEquals(original, actual));
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);
} }
[Fact] [Fact]
@ -146,15 +110,14 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
{ {
// arrange // arrange
XmpProfile profile = CreateMinimalXmlProfile(); XmpProfile profile = CreateMinimalXmlProfile();
profile.Document.Root.AddFirst(new XElement(XName.Get("written"))); byte[] original = profile.ToByteArray();
// act // act
XmpProfile clone = profile.DeepClone(); XmpProfile clone = profile.DeepClone();
clone.Document.Root.AddFirst(new XElement(XName.Get("onlyonclone"))); byte[] actual = clone.ToByteArray();
// assert // assert
XmpProfileContainsExpectedValues(clone); Assert.False(ReferenceEquals(original, actual));
Assert.Equal("onlyonclone", ((XElement)clone.Document.Root.FirstNode).Name);
} }
[Fact] [Fact]
@ -172,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual); Assert.Equal(original.ToByteArray(), actual.ToByteArray());
} }
[Fact] [Fact]
@ -190,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual); Assert.Equal(original.ToByteArray(), actual.ToByteArray());
} }
[Fact] [Fact]
@ -208,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual); Assert.Equal(original.ToByteArray(), actual.ToByteArray());
} }
[Fact] [Fact]
@ -226,7 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual); Assert.Equal(original.ToByteArray(), actual.ToByteArray());
} }
[Fact] [Fact]
@ -244,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual); Assert.Equal(original.ToByteArray(), actual.ToByteArray());
} }
[Fact] [Fact]
@ -262,13 +225,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
// assert // assert
XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile;
XmpProfileContainsExpectedValues(actual); XmpProfileContainsExpectedValues(actual);
Assert.Equal(original, actual); Assert.Equal(original.ToByteArray(), actual.ToByteArray());
} }
private static void XmpProfileContainsExpectedValues(XmpProfile xmp) private static void XmpProfileContainsExpectedValues(XmpProfile xmp)
{ {
Assert.NotNull(xmp); Assert.NotNull(xmp);
XDocument document = xmp.Document; XDocument document = xmp.GetDocument();
Assert.NotNull(document); Assert.NotNull(document);
Assert.Equal("xmpmeta", document.Root.Name.LocalName); Assert.Equal("xmpmeta", document.Root.Name.LocalName);
Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName);
@ -276,7 +239,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp
private static XmpProfile CreateMinimalXmlProfile() 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); byte[] data = Encoding.UTF8.GetBytes(content);
var profile = new XmpProfile(data); var profile = new XmpProfile(data);
return profile; return profile;

Loading…
Cancel
Save