diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 46f0e694e..023bbaac7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -623,17 +623,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return; } - var identifier = new byte[ProfileResolver.AdobePhotoshopApp13Marker.Length]; - this.InputStream.Read(identifier, 0, identifier.Length); - remaining -= identifier.Length; - if (ProfileResolver.IsProfile(identifier, ProfileResolver.AdobePhotoshopApp13Marker)) + this.InputStream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length); + remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) { var resourceBlockData = new byte[remaining]; this.InputStream.Read(resourceBlockData, 0, remaining); Span blockDataSpan = resourceBlockData.AsSpan(); - while (blockDataSpan.Length > 10) - { + while (blockDataSpan.Length > 12) + { if (!ProfileResolver.IsProfile(blockDataSpan.Slice(0, 4), ProfileResolver.AdobeImageResourceBlockMarker)) { return; diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4791a04a0..a3786ae1c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -233,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the Start Of Image marker. this.WriteApplicationHeader(metadata); - // Write Exif and ICC profiles + // Write Exif, ICC and IPTC profiles this.WriteProfiles(metadata); // Write the quantization tables. @@ -708,18 +708,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } iptcProfile.UpdateData(); - var data = iptcProfile.Data; + byte[] data = iptcProfile.Data; if (data.Length == 0) { return; } - var app13length = data.Length + ProfileResolver.AdobeImageResourceBlockMarker.Length + ProfileResolver.AdobeIptcMarker.Length + 2 + 4; - this.WriteAppHeader(app13length, JpegConstants.Markers.APP13); - this.outputStream.Write(this.buffer, 0, 4); + var app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length + + ProfileResolver.AdobeImageResourceBlockMarker.Length + + ProfileResolver.AdobeIptcMarker.Length + + 2 + 4 + data.Length; + this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); + this.outputStream.Write(ProfileResolver.AdobePhotoshopApp13Marker); this.outputStream.Write(ProfileResolver.AdobeImageResourceBlockMarker); this.outputStream.Write(ProfileResolver.AdobeIptcMarker); - this.outputStream.WriteByte(0); // a null name consists of two bytes of 0. + this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) this.outputStream.WriteByte(0); BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); this.outputStream.Write(this.buffer, 0, 4); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index 045859c36..40dd76836 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; +using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; @@ -16,13 +17,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC [Theory] [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] - public void ReadIptcProfile(TestImageProvider provider) + public void ReadIptcMetadata_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(JpegDecoder)) { Assert.NotNull(image.Metadata.IptcProfile); - IEnumerable iptcValues = image.Metadata.IptcProfile.Values; + var iptcValues = image.Metadata.IptcProfile.Values.ToList(); ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); @@ -44,6 +45,27 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC } } + [Fact] + public void IptcProfile_ToAndFromByteArray_Works() + { + // arrange + var profile = new IptcProfile(); + var expectedCaptionWriter = "unittest"; + var expectedCaption = "test"; + profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + profile.SetValue(IptcTag.Caption, expectedCaption); + + // act + profile.UpdateData(); + byte[] profileBytes = profile.Data; + var profileFromBytes = new IptcProfile(profileBytes); + + // assert + var iptcValues = profileFromBytes.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } + [Fact] public void IptcProfile_CloneIsDeep() { @@ -79,10 +101,44 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC Assert.NotEqual(iptcValue.Value, clone.Value); } + [Fact] + public void WritingImage_PreservesIptcProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.IptcProfile = new IptcProfile(); + var expectedCaptionWriter = "unittest"; + var expectedCaption = "test"; + image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); + + // act + Image reloadedImage = WriteAndReadJpeg(image); + + // assert + IptcProfile actual = reloadedImage.Metadata.IptcProfile; + Assert.NotNull(actual); + var iptcValues = actual.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } + private static void ContainsIptcValue(IEnumerable 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}'"); } + + private static Image WriteAndReadJpeg(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } } }