diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index fd1556eb34..c359653cbc 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/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); } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs index c41ec5894a..236508fe99 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs +++ b/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 { diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index e90c6473ed..b1125c8e4f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/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) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 80c81fda21..3bb63a24be 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/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) { diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index c5fe395a09..8be029788a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/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); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 4e91bedb0b..26942472fb 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/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); } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index d41224f908..d4d4f196c2 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/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); } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 7261b45213..144d937740 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/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 /// public sealed class XmpProfile : IDeepCloneable { - private XDocument document; + private byte[] data; /// /// Initializes a new instance of the class. @@ -29,17 +28,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// Initializes a new instance of the class. /// /// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[] data) => this.Data = data; - - /// - /// Initializes a new instance of the class, based on the speicief . - /// - /// The document to base this instance on. - public XmpProfile(XDocument doc) - { - this.document = doc; - this.UpdateData(); - } + public XmpProfile(byte[] data) => this.data = data; /// /// Initializes a new instance of the 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); - } - } - - /// - /// Gets the rax XML document containing the XMP profile. - /// - public XDocument Document - { - get - { - this.InitializeDocument(); - return this.document; - } - } - - /// - /// Gets the byte data of the XMP profile. - /// - public byte[] Data { get; private set; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - 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(); } /// - /// Checks whether two structures are not equal. + /// Gets the raw XML document containing the XMP profile. /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(XmpProfile left, XmpProfile right) => !(left == right); - - /// - public XmpProfile DeepClone() => new(this); - - /// - /// Updates the data of the profile. - /// - public void UpdateData() + /// The + 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(); - } - - /// - public override int GetHashCode() => base.GetHashCode(); - - /// - 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(); - } + /// + /// Convert the content of this into a byte array. + /// + /// The + public byte[] ToByteArray() + { + byte[] result = new byte[this.data.Length]; + this.data.AsSpan().CopyTo(result); + return result; } + + /// + public XmpProfile DeepClone() => new(this); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 6a47a95771..8f5a5f76f5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/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); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 7fba86b4fe..79c4ee57a9 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/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()); } } diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index 17be08f132..e5918a9ea4 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/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)); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 4ef5fd774d..f1c5edc548 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/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 = $""; + string content = $" "; byte[] data = Encoding.UTF8.GetBytes(content); var profile = new XmpProfile(data); return profile;