diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index f9ca8ebc6..de7e51e09 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Calculates the correct number of bytes per pixel for the given color type. /// - /// The + /// Bytes per pixel private int CalculateBytesPerPixel() { switch (this.pngColorType) @@ -535,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Png { if (image.MetaData.ExifProfile?.Values.Count > 0) { - this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.RawData); + this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray(includeExifIdCode: false)); } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index d3f97c46c..5ba194634 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -94,11 +94,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public ExifParts Parts { get; set; } - /// - /// Gets the byte data array containing the exif data. - /// - public byte[] RawData => this.data; - /// /// Gets the tags that where found but contained an invalid value. /// @@ -237,8 +232,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Converts this instance to a byte array. /// + /// Indicates, if the Exif ID code should be included. + /// This Exif ID code should not be included in case of PNG's. Defaults to true. /// The - public byte[] ToByteArray() + public byte[] ToByteArray(bool includeExifIdCode = true) { if (this.values == null) { @@ -251,7 +248,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } var writer = new ExifWriter(this.values, this.Parts); - return writer.GetData(); + return writer.GetData(includeExifIdCode); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs index 4f28449d6..755d92939 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -78,6 +78,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif if (this.ReadString(4) == "Exif") { + // two zeros are expected to follow the Exif Id code if (this.ReadUInt16() != 0) { return values; diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs index f7363ef31..64e4442c3 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// internal sealed class ExifWriter { - /// - /// The start index. - /// - private const int StartIndex = 6; - /// /// Which parts will be written. /// @@ -46,11 +41,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Returns the EXIF data. /// + /// Indicates, if the Exif ID code should be included. + /// This Exif ID code should not be included in case of PNG's. Defaults to true. /// /// The . /// - public byte[] GetData() + public byte[] GetData(bool includeExifIdCode = true) { + uint startIndex = 6; uint length; int exifIndex = -1; int gpsIndex = -1; @@ -86,23 +84,51 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return null; } - length += 10 + 4 + 2; + if (includeExifIdCode) + { + // Exif Code (6 bytes) + byte order marker (4 bytes) + length += 10; + } + else + { + // special case for PNG eXIf Chunk: + // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total + length += 4; + + // if the Exif Code ("Exif00") is not included, the start index is 0 instead of 6 + startIndex = 0; + } + + length += 4 + 2; byte[] result = new byte[length]; - result[0] = (byte)'E'; - result[1] = (byte)'x'; - result[2] = (byte)'i'; - result[3] = (byte)'f'; - result[4] = 0x00; - result[5] = 0x00; - result[6] = (byte)'I'; - result[7] = (byte)'I'; - result[8] = 0x2A; - result[9] = 0x00; - - int i = 10; - uint ifdOffset = ((uint)i - StartIndex) + 4; + int i = 0; + if (includeExifIdCode) + { + result[0] = (byte)'E'; + result[1] = (byte)'x'; + result[2] = (byte)'i'; + result[3] = (byte)'f'; + result[4] = 0x00; + result[5] = 0x00; + result[6] = (byte)'I'; + result[7] = (byte)'I'; + result[8] = 0x2A; + result[9] = 0x00; + i = 10; + } + else + { + // the byte order marker followed by the number 42 and a 0 + result[0] = (byte)'I'; + result[1] = (byte)'I'; + result[2] = 0x2A; + result[3] = 0x00; + i = 4; + } + + uint ifdOffset = ((uint)i - startIndex) + 4; uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; if (exifLength > 0) @@ -118,18 +144,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif i = WriteUInt32(ifdOffset, result, i); i = this.WriteHeaders(this.ifdIndexes, result, i); i = WriteUInt32(thumbnailOffset, result, i); - i = this.WriteData(this.ifdIndexes, result, i); + i = this.WriteData(startIndex, this.ifdIndexes, result, i); if (exifLength > 0) { i = this.WriteHeaders(this.exifIndexes, result, i); - i = this.WriteData(this.exifIndexes, result, i); + i = this.WriteData(startIndex, this.exifIndexes, result, i); } if (gpsLength > 0) { i = this.WriteHeaders(this.gpsIndexes, result, i); - i = this.WriteData(this.gpsIndexes, result, i); + i = this.WriteData(startIndex, this.gpsIndexes, result, i); } WriteUInt16((ushort)0, result, i); @@ -266,7 +292,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return newOffset; } - private int WriteData(List indexes, byte[] destination, int offset) + private int WriteData(uint startIndex, List indexes, byte[] destination, int offset) { if (this.dataOffsets.Count == 0) { @@ -281,7 +307,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif ExifValue value = this.values[index]; if (value.Length > 4) { - WriteUInt32((uint)(newOffset - StartIndex), destination, this.dataOffsets[i++]); + WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); newOffset = this.WriteValue(value, destination, newOffset); } } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 3dd38b6d2..ddd852295 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -17,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests { public class ExifProfileTests { + private static readonly Dictionary TestProfileValues = new Dictionary() + { + { ExifTag.Software, "Software" }, + { ExifTag.Model, "Model" }, + { ExifTag.Copyright, "Copyright" }, + { ExifTag.Orientation, (ushort)5 }, + { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, + { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, + }; + [Fact] public void Constructor() { @@ -27,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests image.MetaData.ExifProfile = new ExifProfile(); image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); - image = WriteAndRead(image); + image = WriteAndReadJpeg(image); Assert.NotNull(image.MetaData.ExifProfile); Assert.Equal(1, image.MetaData.ExifProfile.Values.Count()); @@ -50,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests ExifProfile profile = GetExifProfile(); - ExifProfile clone = new ExifProfile(profile); + var clone = new ExifProfile(profile); TestProfile(clone); profile.SetValue(ExifTag.ColorSpace, (ushort)2); @@ -62,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WriteFraction() { - using (MemoryStream memStream = new MemoryStream()) + using (var memStream = new MemoryStream()) { double exposureTime = 1.0 / 1600; @@ -70,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); - Image image = new Image(1, 1); + var image = new Image(1, 1); image.MetaData.ExifProfile = profile; image.SaveAsJpeg(memStream); @@ -110,21 +121,21 @@ namespace SixLabors.ImageSharp.Tests Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); - image = WriteAndRead(image); + image = WriteAndReadJpeg(image); ExifValue value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); Assert.NotNull(value); Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); - image = WriteAndRead(image); + image = WriteAndReadJpeg(image); value = image.MetaData.ExifProfile.GetValue(ExifTag.ExposureBiasValue); Assert.NotNull(value); Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); image.MetaData.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); - image = WriteAndRead(image); + image = WriteAndReadJpeg(image); value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy); Assert.NotNull(value); Assert.Equal(new Rational(double.PositiveInfinity), value.Value); @@ -133,7 +144,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void SetValue() { - Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; + var latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); image.MetaData.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); @@ -171,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); - image = WriteAndRead(image); + image = WriteAndReadJpeg(image); Assert.NotNull(image.MetaData.ExifProfile); Assert.Equal(17, image.MetaData.ExifProfile.Values.Count()); @@ -193,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests image.MetaData.ExifProfile.Parts = ExifParts.ExifTags; - image = WriteAndRead(image); + image = WriteAndReadJpeg(image); Assert.NotNull(image.MetaData.ExifProfile); Assert.Equal(8, image.MetaData.ExifProfile.Values.Count()); @@ -312,17 +323,34 @@ namespace SixLabors.ImageSharp.Tests public void TestWritingPngPreservesExifProfile() { // arrange - Image image = TestFile.Create(TestImages.Png.Bike).CreateImage(); - ExifProfile expected = GetExifProfile(); + var image = new Image(1, 1); + ExifProfile expected = CreateExifProfile(); image.MetaData.ExifProfile = expected; // act - Image reloadedImage = WritePngAndRead(image); - + Image reloadedImage = WriteAndReadPng(image); + // assert ExifProfile actual = reloadedImage.MetaData.ExifProfile; Assert.NotNull(actual); - TestProfile(actual); + foreach(KeyValuePair expectedProfileValue in TestProfileValues) + { + ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key); + Assert.NotNull(actualProfileValue); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); + } + } + + private static ExifProfile CreateExifProfile() + { + var profile = new ExifProfile(); + + foreach(KeyValuePair exifProfileValue in TestProfileValues) + { + profile.SetValue(exifProfileValue.Key, exifProfileValue.Value); + } + + return profile; } private static ExifProfile GetExifProfile() @@ -335,7 +363,7 @@ namespace SixLabors.ImageSharp.Tests return profile; } - private static Image WriteAndRead(Image image) + private static Image WriteAndReadJpeg(Image image) { using (var memStream = new MemoryStream()) { @@ -347,7 +375,7 @@ namespace SixLabors.ImageSharp.Tests } } - private static Image WritePngAndRead(Image image) + private static Image WriteAndReadPng(Image image) { using (var memStream = new MemoryStream()) { @@ -370,13 +398,19 @@ namespace SixLabors.ImageSharp.Tests Assert.NotNull(value.Value); if (value.Tag == ExifTag.Software) + { Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); + } if (value.Tag == ExifTag.XResolution) + { Assert.Equal(new Rational(300.0), value.Value); + } if (value.Tag == ExifTag.PixelXDimension) + { Assert.Equal(2338U, value.Value); + } } }