From e5bf5cf198b51b44935f89f01dce04f2956982c7 Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 12 Jun 2018 20:27:56 +0200 Subject: [PATCH 01/28] added EXIF chunk type and setting ExifProfile, if eXIf chunk data is present (#611) --- src/ImageSharp/Formats/Png/PngChunkType.cs | 9 +++++++-- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs index 51adc162b..e0844ca6b 100644 --- a/src/ImageSharp/Formats/Png/PngChunkType.cs +++ b/src/ImageSharp/Formats/Png/PngChunkType.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Png { /// - /// Contains a list of of chunk types. + /// Contains a list of chunk types. /// internal enum PngChunkType : uint { @@ -55,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. /// - Physical = 0x70485973U // pHYs + Physical = 0x70485973U, // pHYs + + /// + /// The data chunk which contains the Exif profile. + /// + Exif = 0x65584966U // eXIf } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cc98b8450..9f0c59780 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -250,6 +251,11 @@ namespace SixLabors.ImageSharp.Formats.Png case PngChunkType.Text: this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); break; + case PngChunkType.Exif: + byte[] exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metadata.ExifProfile = new ExifProfile(exifData); + break; case PngChunkType.End: this.isEndChunkReached = true; break; From a5864ac9e770f1f4f7bc85bbf07f1620fd98cafb Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 12 Jun 2018 21:14:34 +0200 Subject: [PATCH 02/28] writes the eXIf chunk to the stream during encoding (#611) --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 16 ++++++++++++++++ .../MetaData/Profiles/Exif/ExifProfile.cs | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index f17c9009a..8bd3d3eb6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -221,6 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); + this.WriteExifChunk(stream, image); this.WriteDataChunks(image.Frames.RootFrame, stream); this.WriteEndChunk(stream); stream.Flush(); @@ -523,6 +524,21 @@ namespace SixLabors.ImageSharp.Formats.Png } } + /// + /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data. + /// + /// The pixel format. + /// The containing image data. + /// The image. + private void WriteExifChunk(Stream stream, Image image) + where TPixel : struct, IPixel + { + if (image.MetaData.ExifProfile.Values.Count > 0) + { + this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.RawData); + } + } + /// /// Writes the gamma information to the stream. /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 0f19083e5..d3f97c46c 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -95,6 +94,11 @@ 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. /// From a51ec3c21ebe5abd5737860343aa970a402cee0c Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 12 Jun 2018 21:20:24 +0200 Subject: [PATCH 03/28] if ignoreMetadata is set, EXIF chunk will be ignored --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 10 +++++++--- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 9f0c59780..004b4f304 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -252,9 +252,13 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); break; case PngChunkType.Exif: - byte[] exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); - metadata.ExifProfile = new ExifProfile(exifData); + if (!this.ignoreMetadata) + { + byte[] exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metadata.ExifProfile = new ExifProfile(exifData); + } + break; case PngChunkType.End: this.isEndChunkReached = true; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 8bd3d3eb6..f9ca8ebc6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -533,7 +533,7 @@ namespace SixLabors.ImageSharp.Formats.Png private void WriteExifChunk(Stream stream, Image image) where TPixel : struct, IPixel { - if (image.MetaData.ExifProfile.Values.Count > 0) + if (image.MetaData.ExifProfile?.Values.Count > 0) { this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.RawData); } From 47ad00447b968ebb7cc862317849e0ec26242874 Mon Sep 17 00:00:00 2001 From: popow Date: Wed, 13 Jun 2018 19:42:41 +0200 Subject: [PATCH 04/28] added unit test which writes a png with a Exif Profile and checks after reloading it, if the profile is still there (#611) --- .../Profiles/Exif/ExifProfileTests.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 3c69b57fd..3dd38b6d2 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -308,6 +308,23 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(495, bytes.Length); } + [Fact] + public void TestWritingPngPreservesExifProfile() + { + // arrange + Image image = TestFile.Create(TestImages.Png.Bike).CreateImage(); + ExifProfile expected = GetExifProfile(); + image.MetaData.ExifProfile = expected; + + // act + Image reloadedImage = WritePngAndRead(image); + + // assert + ExifProfile actual = reloadedImage.MetaData.ExifProfile; + Assert.NotNull(actual); + TestProfile(actual); + } + private static ExifProfile GetExifProfile() { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); @@ -320,7 +337,7 @@ namespace SixLabors.ImageSharp.Tests private static Image WriteAndRead(Image image) { - using (MemoryStream memStream = new MemoryStream()) + using (var memStream = new MemoryStream()) { image.SaveAsJpeg(memStream); image.Dispose(); @@ -330,6 +347,18 @@ namespace SixLabors.ImageSharp.Tests } } + private static Image WritePngAndRead(Image image) + { + using (var memStream = new MemoryStream()) + { + image.SaveAsPng(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + private static void TestProfile(ExifProfile profile) { Assert.NotNull(profile); From 08797b103137485e697186e0cc79294ff07f9cc1 Mon Sep 17 00:00:00 2001 From: popow Date: Thu, 14 Jun 2018 20:40:32 +0200 Subject: [PATCH 05/28] - no longer exposing raw exif data byte's, using ToByteArray to write the ExifChunk - changed the ExifWriter GetData, to support the special case for PNG's not inlcluding the 'Exif' Code (#611) --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 4 +- .../MetaData/Profiles/Exif/ExifProfile.cs | 11 +-- .../MetaData/Profiles/Exif/ExifReader.cs | 1 + .../MetaData/Profiles/Exif/ExifWriter.cs | 76 +++++++++++++------ .../Profiles/Exif/ExifProfileTests.cs | 68 ++++++++++++----- 5 files changed, 109 insertions(+), 51 deletions(-) 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); + } } } From 7841776e2fa385db329c1e81b7835289e58ba003 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 16 Jun 2018 15:57:22 +0200 Subject: [PATCH 06/28] all Exif tests which write and load images are now testing png and jpg --- .../Profiles/Exif/ExifProfileTests.cs | 73 ++++++++++++------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index ddd852295..6d3eb5631 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -18,6 +18,12 @@ namespace SixLabors.ImageSharp.Tests { public class ExifProfileTests { + public enum TestImageWriteFormat + { + Jpeg, + Png + } + private static readonly Dictionary TestProfileValues = new Dictionary() { { ExifTag.Software, "Software" }, @@ -28,8 +34,10 @@ namespace SixLabors.ImageSharp.Tests { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, }; - [Fact] - public void Constructor() + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void Constructor(TestImageWriteFormat imageFormat) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage(); @@ -38,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests image.MetaData.ExifProfile = new ExifProfile(); image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); - image = WriteAndReadJpeg(image); + image = WriteAndRead(image, imageFormat); Assert.NotNull(image.MetaData.ExifProfile); Assert.Equal(1, image.MetaData.ExifProfile.Values.Count()); @@ -70,8 +78,10 @@ namespace SixLabors.ImageSharp.Tests TestProfile(clone); } - [Fact] - public void WriteFraction() + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void WriteFraction(TestImageWriteFormat imageFormat) { using (var memStream = new MemoryStream()) { @@ -84,10 +94,7 @@ namespace SixLabors.ImageSharp.Tests var image = new Image(1, 1); image.MetaData.ExifProfile = profile; - image.SaveAsJpeg(memStream); - - memStream.Position = 0; - image = Image.Load(memStream); + image = WriteAndRead(image, imageFormat); profile = image.MetaData.ExifProfile; Assert.NotNull(profile); @@ -102,10 +109,7 @@ namespace SixLabors.ImageSharp.Tests profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); image.MetaData.ExifProfile = profile; - image.SaveAsJpeg(memStream); - - memStream.Position = 0; - image = Image.Load(memStream); + image = WriteAndRead(image, imageFormat); profile = image.MetaData.ExifProfile; Assert.NotNull(profile); @@ -115,8 +119,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Fact] - public void ReadWriteInfinity() + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void ReadWriteInfinity(TestImageWriteFormat imageFormat) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); @@ -128,21 +134,23 @@ namespace SixLabors.ImageSharp.Tests image.MetaData.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); - image = WriteAndReadJpeg(image); + image = WriteAndRead(image, imageFormat); 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 = WriteAndReadJpeg(image); + image = WriteAndRead(image, imageFormat); value = image.MetaData.ExifProfile.GetValue(ExifTag.FlashEnergy); Assert.NotNull(value); Assert.Equal(new Rational(double.PositiveInfinity), value.Value); } - [Fact] - public void SetValue() + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void SetValue(TestImageWriteFormat imageFormat) { var latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; @@ -182,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude); TestValue(value, latitude); - image = WriteAndReadJpeg(image); + image = WriteAndRead(image, imageFormat); Assert.NotNull(image.MetaData.ExifProfile); Assert.Equal(17, image.MetaData.ExifProfile.Values.Count()); @@ -204,7 +212,7 @@ namespace SixLabors.ImageSharp.Tests image.MetaData.ExifProfile.Parts = ExifParts.ExifTags; - image = WriteAndReadJpeg(image); + image = WriteAndRead(image, imageFormat); Assert.NotNull(image.MetaData.ExifProfile); Assert.Equal(8, image.MetaData.ExifProfile.Values.Count()); @@ -319,8 +327,10 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(495, bytes.Length); } - [Fact] - public void TestWritingPngPreservesExifProfile() + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + public void TestWritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) { // arrange var image = new Image(1, 1); @@ -328,7 +338,7 @@ namespace SixLabors.ImageSharp.Tests image.MetaData.ExifProfile = expected; // act - Image reloadedImage = WriteAndReadPng(image); + Image reloadedImage = WriteAndRead(image, imageFormat); // assert ExifProfile actual = reloadedImage.MetaData.ExifProfile; @@ -353,7 +363,7 @@ namespace SixLabors.ImageSharp.Tests return profile; } - private static ExifProfile GetExifProfile() + internal static ExifProfile GetExifProfile() { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage(); @@ -363,6 +373,19 @@ namespace SixLabors.ImageSharp.Tests return profile; } + private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) + { + switch(imageFormat) + { + case TestImageWriteFormat.Jpeg: + return WriteAndReadJpeg(image); + case TestImageWriteFormat.Png: + return WriteAndReadPng(image); + default: + throw new ArgumentException("unexpected test image format, only Jpeg and Png are allowed"); + } + } + private static Image WriteAndReadJpeg(Image image) { using (var memStream = new MemoryStream()) From 43c85bbce65381493e3e4acdce4427679e7c6e24 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 17 Jun 2018 13:05:40 +0200 Subject: [PATCH 07/28] removed code duplication setting the byte order marker --- .../MetaData/Profiles/Exif/ExifWriter.cs | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs index 64e4442c3..914cd4b36 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -106,28 +106,20 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif 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; + result[i++] = (byte)'E'; + result[i++] = (byte)'x'; + result[i++] = (byte)'i'; + result[i++] = (byte)'f'; + result[i++] = 0x00; + result[i++] = 0x00; } + // the byte order marker for little-endian, followed by the number 42 and a 0 + result[i++] = (byte)'I'; + result[i++] = (byte)'I'; + result[i++] = 0x2A; + result[i++] = 0x00; + uint ifdOffset = ((uint)i - startIndex) + 4; uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; From 3aa00199f1d9aba96da203347a4d2277b1e64ec7 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 17 Jun 2018 14:46:28 +0200 Subject: [PATCH 08/28] ToByteArray skips the first 6 bytes if includeExifCode is false, added unit test for that --- .../MetaData/Profiles/Exif/ExifProfile.cs | 33 +++++++++++++++++++ .../Profiles/Exif/ExifProfileTests.cs | 26 +++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 5ba194634..4a73e8a4b 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -239,6 +240,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { if (this.values == null) { + if (!includeExifIdCode && this.StartsWithExifIdCode(this.data)) + { + // skip the first 6 bytes (the Exif Code) + return this.data.Skip(6).ToArray(); + } + return this.data; } @@ -251,6 +258,32 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return writer.GetData(includeExifIdCode); } + /// + /// Checks if a byte array start with the Exif Code: ASCII "Exif" followed by two zeros. + /// + /// The byte array to check for the Exif Code. + /// True, if the byte array starts with the Exif Code + private bool StartsWithExifIdCode(byte[] exifBytes) + { + if (exifBytes.Length < 6) + { + return false; + } + + // The EXIF ID code: ASCII "Exif" followed by two zeros. + byte[] exifCode = { 69, 120, 105, 102, 0, 0 }; + int exifLength = exifCode.Length; + for (int i = 0; i < exifCode.Length; i++) + { + if (exifBytes[i] != exifCode[i]) + { + return false; + } + } + + return true; + } + /// /// Synchronizes the profiles with the specified meta data. /// diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 6d3eb5631..98113f392 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -351,6 +351,32 @@ namespace SixLabors.ImageSharp.Tests } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestProfileToByteArrayWorks(bool includeExifIdCode) + { + // arrange + byte[] exifBytesWithExifCode = new byte[] { 69, 120, 105, 102, 0, 0, 73, 73, 42, 0}; + byte[] exifBytesWithoutExifCode = new byte[] { 73, 73, 42, 0 }; + var profile = new ExifProfile(exifBytesWithExifCode); + + // act + byte[] actual = profile.ToByteArray(includeExifIdCode); + + // assert + Assert.NotNull(actual); + Assert.NotEmpty(actual); + if (includeExifIdCode) + { + Assert.Equal(exifBytesWithExifCode, actual); + } + else + { + Assert.Equal(exifBytesWithoutExifCode, actual); + } + } + private static ExifProfile CreateExifProfile() { var profile = new ExifProfile(); From 6201c3278ca2256cbe08ccf940f9f918823600d8 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 17 Jun 2018 20:27:00 +0200 Subject: [PATCH 09/28] avoiding unnecessary allocation of the exif id byte array in StartsWithExifIdCode() --- .../MetaData/Profiles/Exif/ExifProfile.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 4a73e8a4b..505ea582f 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public sealed class ExifProfile { + /// + /// The EXIF ID code: ASCII "Exif" followed by two zeros. + /// + private static readonly byte[] ExifCode = { 69, 120, 105, 102, 0, 0 }; + /// /// The byte array to read the EXIF profile from. /// @@ -270,12 +275,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return false; } - // The EXIF ID code: ASCII "Exif" followed by two zeros. - byte[] exifCode = { 69, 120, 105, 102, 0, 0 }; - int exifLength = exifCode.Length; - for (int i = 0; i < exifCode.Length; i++) + int exifLength = ExifCode.Length; + for (int i = 0; i < ExifCode.Length; i++) { - if (exifBytes[i] != exifCode[i]) + if (exifBytes[i] != ExifCode[i]) { return false; } From a7208dee7d2b02b4521fcff42e180da9cf438754 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 23 Jun 2018 14:05:04 +0200 Subject: [PATCH 10/28] removed ExifIdCode from ExifConstants and ExifProfile, using ExifMarker defined in Jpeg ProfileResolver --- .../Components/Decoder/ProfileResolver.cs | 2 +- .../MetaData/Profiles/Exif/ExifConstants.cs | 9 --------- .../MetaData/Profiles/Exif/ExifProfile.cs | 15 ++++++-------- .../MetaData/Profiles/Exif/ExifWriter.cs | 20 ++++++++++--------- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index 8273f20ea..a6d5faaea 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public static readonly byte[] AdobeMarker = Encoding.UTF8.GetBytes("Adobe"); /// - /// Returns a value indicating whether the passed bytes are a match to the profile identifer + /// Returns a value indicating whether the passed bytes are a match to the profile identifier /// /// The bytes to check /// The profile identifier diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs index a76737b69..c96cc41b6 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs @@ -5,15 +5,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { internal static class ExifConstants { - public static readonly byte[] ExifIdCode = { - (byte)'E', - (byte)'x', - (byte)'i', - (byte)'f', - 0x00, - 0x00 - }; - public static readonly byte[] LittleEndianByteOrderMarker = { (byte)'I', (byte)'I', diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 505ea582f..40c489d3a 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -15,11 +16,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public sealed class ExifProfile { - /// - /// The EXIF ID code: ASCII "Exif" followed by two zeros. - /// - private static readonly byte[] ExifCode = { 69, 120, 105, 102, 0, 0 }; - /// /// The byte array to read the EXIF profile from. /// @@ -239,7 +235,8 @@ 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 Exif Id Code is part of the JPEG APP1 segment. This Exif ID code should not be included in case of PNG's. + /// Defaults to true. /// The public byte[] ToByteArray(bool includeExifIdCode = true) { @@ -275,10 +272,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return false; } - int exifLength = ExifCode.Length; - for (int i = 0; i < ExifCode.Length; i++) + int exifLength = ProfileResolver.ExifMarker.Length; + for (int i = 0; i < ProfileResolver.ExifMarker.Length; i++) { - if (exifBytes[i] != ExifCode[i]) + if (exifBytes[i] != ProfileResolver.ExifMarker[i]) { return false; } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs index 14d180c05..ff0c6bf5c 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Text; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.MetaData.Profiles.Exif @@ -42,13 +43,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 Exif Id Code is part of the JPEG APP1 segment. This Exif ID code should not be included in case of PNG's. + /// Defaults to true. /// /// The . /// public byte[] GetData(bool includeExifIdCode = true) { - uint startIndex = 6; + uint startIndex = (uint)ProfileResolver.ExifMarker.Length; uint length; int exifIndex = -1; int gpsIndex = -1; @@ -86,19 +88,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif if (includeExifIdCode) { - // Exif Code (6 bytes) + byte order marker (4 bytes) - length += 10; + // Exif Id Code "Exif00" (6 bytes) + length += (uint)ProfileResolver.ExifMarker.Length; } 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; } + // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total + length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; + length += 4 + 2; byte[] result = new byte[length]; @@ -106,8 +108,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif int i = 0; if (includeExifIdCode) { - ExifConstants.ExifIdCode.AsSpan().CopyTo(result); // 0-5 - i += ExifConstants.ExifIdCode.Length; + ProfileResolver.ExifMarker.AsSpan().CopyTo(result); // 0-5 + i += ProfileResolver.ExifMarker.Length; } // the byte order marker for little-endian, followed by the number 42 and a 0 From 215224e6cd30bc6c95d8ce21ac4c8d191bf72386 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 23 Jun 2018 15:43:45 +0200 Subject: [PATCH 11/28] using Span CopyTo() in the ExifProfile Constructor to copy the data from one exifprofile to another --- src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 40c489d3a..78fa63ae7 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif if (other.data != null) { this.data = new byte[other.data.Length]; - Buffer.BlockCopy(other.data, 0, this.data, 0, other.data.Length); + other.data.AsSpan().CopyTo(this.data); } } From cac1bea9847cbf2efe45e838fe70194f335bdea4 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 23 Jun 2018 15:58:23 +0200 Subject: [PATCH 12/28] fixed submodule merge mistake, setting it to "add reference output for FontShapesAreRenderedCorrectlyAlongACirclePath" --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index 802ffbae9..0e6407be7 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 802ffbae9af22d986226bc1c20d7d96aaf25d6b9 +Subproject commit 0e6407be7081341526f815a4d70e7912db276a98 From 108cb02106ed46c68f4d68482635955872583cf1 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 23 Jun 2018 16:59:51 +0200 Subject: [PATCH 13/28] corrected big endian byte order marker --- src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs index c96cc41b6..555cadafe 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs @@ -15,8 +15,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif public static readonly byte[] BigEndianByteOrderMarker = { (byte)'M', (byte)'M', - 0x2A, 0x00, + 0x2A }; } } \ No newline at end of file From 01aa5303e1e5cb1321f9933a1444d9cab6c9628c Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 23 Jun 2018 17:17:56 +0200 Subject: [PATCH 14/28] using defined exif constants in the TestProfileToByteArrayWorks Test --- .../MetaData/Profiles/Exif/ExifProfileTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 98113f392..2192d134e 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -357,8 +358,8 @@ namespace SixLabors.ImageSharp.Tests public void TestProfileToByteArrayWorks(bool includeExifIdCode) { // arrange - byte[] exifBytesWithExifCode = new byte[] { 69, 120, 105, 102, 0, 0, 73, 73, 42, 0}; - byte[] exifBytesWithoutExifCode = new byte[] { 73, 73, 42, 0 }; + byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); + byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; var profile = new ExifProfile(exifBytesWithExifCode); // act From cc4a2031308fb499eae01be0220d3c9a607c5bb8 Mon Sep 17 00:00:00 2001 From: popow Date: Sun, 24 Jun 2018 13:53:51 +0200 Subject: [PATCH 15/28] to make ExifProfile format agnostic again: passing ExifIdCode as ReadonlySpan to the ToByteArray method --- .../Formats/Jpeg/JpegEncoderCore.cs | 3 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../MetaData/Profiles/Exif/ExifProfile.cs | 40 ++----------------- .../MetaData/Profiles/Exif/ExifWriter.cs | 29 +++++--------- .../MetaData/ImageMetaDataTests.cs | 2 + .../Profiles/Exif/ExifProfileTests.cs | 10 ++--- .../Processors/Transforms/AutoOrientTests.cs | 3 +- 7 files changed, 25 insertions(+), 64 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 1310d90d2..bd5a9e10f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Icc; @@ -608,7 +609,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void WriteExifProfile(ExifProfile exifProfile) { const int Max = 65533; - byte[] data = exifProfile?.ToByteArray(); + byte[] data = exifProfile?.ToByteArray(ProfileResolver.ExifMarker); if (data == null || data.Length == 0) { return; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 9e0f5f877..fedd46063 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -536,7 +536,7 @@ namespace SixLabors.ImageSharp.Formats.Png { if (image.MetaData.ExifProfile?.Values.Count > 0) { - this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray(includeExifIdCode: false)); + this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray()); } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 78fa63ae7..b38097060 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -234,20 +233,13 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Converts this instance to a byte array. /// - /// Indicates, if the Exif ID code should be included. - /// The Exif Id Code is part of the JPEG APP1 segment. This Exif ID code should not be included in case of PNG's. - /// Defaults to true. + /// The Exif Id Code is part of the JPEG APP1 segment (Exif00). Those bytes will be written at + /// the beginning of the array. This Exif ID code should not be included in case of PNG's. /// The - public byte[] ToByteArray(bool includeExifIdCode = true) + public byte[] ToByteArray(ReadOnlySpan exifIdCode = default) { if (this.values == null) { - if (!includeExifIdCode && this.StartsWithExifIdCode(this.data)) - { - // skip the first 6 bytes (the Exif Code) - return this.data.Skip(6).ToArray(); - } - return this.data; } @@ -257,31 +249,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } var writer = new ExifWriter(this.values, this.Parts); - return writer.GetData(includeExifIdCode); - } - - /// - /// Checks if a byte array start with the Exif Code: ASCII "Exif" followed by two zeros. - /// - /// The byte array to check for the Exif Code. - /// True, if the byte array starts with the Exif Code - private bool StartsWithExifIdCode(byte[] exifBytes) - { - if (exifBytes.Length < 6) - { - return false; - } - - int exifLength = ProfileResolver.ExifMarker.Length; - for (int i = 0; i < ProfileResolver.ExifMarker.Length; i++) - { - if (exifBytes[i] != ProfileResolver.ExifMarker[i]) - { - return false; - } - } - - return true; + return writer.GetData(exifIdCode); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs index ff0c6bf5c..1de5fbd5c 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -5,7 +5,6 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Text; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.MetaData.Profiles.Exif @@ -42,15 +41,15 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Returns the EXIF data. /// - /// Indicates, if the Exif ID code should be included. - /// The Exif Id Code is part of the JPEG APP1 segment. This Exif ID code should not be included in case of PNG's. - /// Defaults to true. + /// The Exif Id Code is part of the JPEG APP1 segment (Exif00). Those bytes will be written at + /// the beginning of the array. This Exif ID code should not be included in case of PNG's. /// /// The . /// - public byte[] GetData(bool includeExifIdCode = true) + public byte[] GetData(ReadOnlySpan exifIdCode) { - uint startIndex = (uint)ProfileResolver.ExifMarker.Length; + uint exifIdCodeLength = exifIdCode.IsEmpty ? 0 : (uint)exifIdCode.Length; + uint startIndex = exifIdCodeLength; uint length; int exifIndex = -1; int gpsIndex = -1; @@ -86,17 +85,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return null; } - if (includeExifIdCode) - { - // Exif Id Code "Exif00" (6 bytes) - length += (uint)ProfileResolver.ExifMarker.Length; - } - else - { - // special case for PNG eXIf Chunk: - // if the Exif Code ("Exif00") is not included, the start index is 0 instead of 6 - startIndex = 0; - } + length += exifIdCodeLength; // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; @@ -106,10 +95,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif byte[] result = new byte[length]; int i = 0; - if (includeExifIdCode) + if (!exifIdCode.IsEmpty) { - ProfileResolver.ExifMarker.AsSpan().CopyTo(result); // 0-5 - i += ProfileResolver.ExifMarker.Length; + exifIdCode.CopyTo(result); // 0-5 + i += exifIdCode.Length; } // the byte order marker for little-endian, followed by the number 42 and a 0 diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index 255451e0e..7d0686aa7 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -32,6 +33,7 @@ namespace SixLabors.ImageSharp.Tests ImageMetaData clone = metaData.Clone(); Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); + Assert.Equal(exifProfile.ToByteArray(ProfileResolver.ExifMarker), clone.ExifProfile.ToByteArray(ProfileResolver.ExifMarker)); Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(2, clone.VerticalResolution); Assert.Equal(imageProperty, clone.Properties[0]); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 2192d134e..7d4ddd322 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Tests // Force parsing of the profile. Assert.Equal(24, profile.Values.Count); - byte[] bytes = profile.ToByteArray(); + byte[] bytes = profile.ToByteArray(ProfileResolver.ExifMarker); Assert.Equal(495, bytes.Length); } @@ -360,21 +360,21 @@ namespace SixLabors.ImageSharp.Tests // arrange byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; - var profile = new ExifProfile(exifBytesWithExifCode); + ExifProfile profile = CreateExifProfile(); // act - byte[] actual = profile.ToByteArray(includeExifIdCode); + byte[] actual = profile.ToByteArray(includeExifIdCode ? ProfileResolver.ExifMarker : default(ReadOnlySpan)); // assert Assert.NotNull(actual); Assert.NotEmpty(actual); if (includeExifIdCode) { - Assert.Equal(exifBytesWithExifCode, actual); + Assert.Equal(exifBytesWithExifCode, actual.Take(exifBytesWithExifCode.Length).ToArray()); } else { - Assert.Equal(exifBytesWithoutExifCode, actual); + Assert.Equal(exifBytesWithoutExifCode, actual.Take(exifBytesWithoutExifCode.Length).ToArray()); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index bae22e7a9..9f353f813 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -9,6 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Processing.Transforms; public class AutoOrientTests : FileTestBase @@ -65,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var profile = new ExifProfile(); profile.SetValue(ExifTag.JPEGTables, orientation); - byte[] bytes = profile.ToByteArray(); + byte[] bytes = profile.ToByteArray(ProfileResolver.ExifMarker); // Change the tag into ExifTag.Orientation bytes[16] = 18; bytes[17] = 1; From c5f040e82c49a137783831db25a7f3d8aa8d67fc Mon Sep 17 00:00:00 2001 From: popow Date: Wed, 27 Jun 2018 19:49:41 +0200 Subject: [PATCH 16/28] commented out not needed references --- tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index c1d9ff24d..0ca3cffa1 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -22,8 +22,8 @@ - - + + From 5e5701575854ca5f15f08ad9e978f87fc96b9764 Mon Sep 17 00:00:00 2001 From: popow Date: Wed, 27 Jun 2018 20:55:29 +0200 Subject: [PATCH 17/28] - fixed error message in WriteExifProfile, when Exif data exceeds the limits - fixed some typo's --- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 8 ++++---- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index bd5a9e10f..4560af05a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -430,7 +430,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The resolution of the image in the y- direction. private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) { - // Write the start of image marker. Markers are always prefixed with with 0xff. + // Write the start of image marker. Markers are always prefixed with 0xff. this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[1] = JpegConstants.Markers.SOI; @@ -617,7 +617,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (data.Length > Max) { - throw new ImageFormatException($"Exif profile size exceeds limit. nameof{Max}"); + throw new ImageFormatException($"Exif profile size exceeds limit of {Max} bytes."); } int length = data.Length + 2; @@ -640,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void WriteIccProfile(IccProfile iccProfile) { - // Just incase someone set the value to null by accident. + // Just in-case someone set the value to null by accident. if (iccProfile == null) { return; @@ -896,7 +896,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The marker length. private void WriteMarkerHeader(byte marker, int length) { - // Markers are always prefixed with with 0xff. + // Markers are always prefixed with 0xff. this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[1] = marker; this.buffer[2] = (byte)(length >> 8); diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e590d6499..e319dc161 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1162,7 +1162,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Decodes and assigns marker colors that identify transparent pixels in non indexed images /// - /// The aplha tRNS array + /// The alpha tRNS array private void AssignTransparentMarkers(ReadOnlySpan alpha) { if (this.pngColorType == PngColorType.Rgb) @@ -1209,7 +1209,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The type of pixel we are expanding to /// The defiltered scanline - /// Thecurrent output image row + /// The current output image row private void ProcessScanlineFromPalette(ReadOnlySpan scanline, Span row) where TPixel : struct, IPixel { From b3212d8c020d7d4e636dcacf50e60db327316431 Mon Sep 17 00:00:00 2001 From: popow Date: Thu, 28 Jun 2018 19:39:39 +0200 Subject: [PATCH 18/28] added SyncProfiles() before writing the Exif Chunk --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 911cb797c..ceb2d6e12 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -625,6 +625,7 @@ namespace SixLabors.ImageSharp.Formats.Png { if (image.MetaData.ExifProfile?.Values.Count > 0) { + image.MetaData.SyncProfiles(); this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray()); } } From 3ea2978b7b939304e6473920db4a51026cc7dc43 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 7 Jul 2018 13:30:19 +0200 Subject: [PATCH 19/28] if Exif data exceeds 64K and is split over multiple App1 marker, it will be extended like the ICC profile does --- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 10 +++++++++- .../MetaData/Profiles/Exif/ExifProfile.cs | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index bd1d84ecc..8b5b7b151 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -472,7 +472,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) { this.isExif = true; - this.MetaData.ExifProfile = new ExifProfile(profile); + if (this.MetaData.ExifProfile == null) + { + this.MetaData.ExifProfile = new ExifProfile(profile); + } + else + { + // if the exif information exceeds 64K, it will be split over multiple APP1 marker + this.MetaData.ExifProfile.Extend(profile); + } } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index b38097060..1f2695c5e 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; @@ -18,7 +17,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// The byte array to read the EXIF profile from. /// - private readonly byte[] data; + private byte[] data; /// /// The collection of EXIF values @@ -230,6 +229,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif this.values.Add(newExifValue); } + /// + /// Extends the profile with additional data. + /// + /// The array containing addition profile data. + public void Extend(byte[] bytes) + { + int currentLength = this.data.Length; + + // the first 6 bytes are Exif00 and will be skipped + Array.Resize(ref this.data, currentLength + bytes.Length - 6); + Buffer.BlockCopy(bytes, 6, this.data, currentLength, bytes.Length - 6); + } + /// /// Converts this instance to a byte array. /// From b644adecdb1a21f6aef2bd45167f9cb62b4bc4f6 Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 7 Jul 2018 15:08:38 +0200 Subject: [PATCH 20/28] if exif data exceeds 64k, it will be written in multiple APP1 markers --- .../Formats/Jpeg/JpegEncoderCore.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4560af05a..a143723e1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -213,6 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Write the Start Of Image marker. this.WriteApplicationHeader((short)image.MetaData.HorizontalResolution, (short)image.MetaData.VerticalResolution); + // Write Exif and ICC profiles this.WriteProfiles(image); // Write the quantization tables. @@ -608,27 +609,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void WriteExifProfile(ExifProfile exifProfile) { - const int Max = 65533; + const int MaxBytesApp1 = 65533; + const int MaxBytesWithExifId = 65527; byte[] data = exifProfile?.ToByteArray(ProfileResolver.ExifMarker); if (data == null || data.Length == 0) { return; } - if (data.Length > Max) + int remaining = data.Length; + int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; + int app1Length = bytesToWrite + 2; + + // write the app1 header + this.WriteApp1Header(app1Length); + + // write the exif data + this.outputStream.Write(data, 0, bytesToWrite); + remaining -= bytesToWrite; + + // if the exif data exceeds 64K, write it in multiple APP1 Markers + for (int idx = MaxBytesApp1; idx < data.Length; idx += MaxBytesWithExifId) { - throw new ImageFormatException($"Exif profile size exceeds limit of {Max} bytes."); - } + bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining; + app1Length = bytesToWrite + 2 + 6; + + // write the app1 header + this.WriteApp1Header(app1Length); + + // write Exif00 marker + ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan()); + this.outputStream.Write(this.buffer, 0, 6); - int length = data.Length + 2; + // write the exif data + this.outputStream.Write(data, idx, bytesToWrite); + remaining -= bytesToWrite; + } + } + + /// + /// Writes the App1 header. + /// + /// The length of the data the app1 marker contains + private void WriteApp1Header(int app1Length) + { this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker - this.buffer[2] = (byte)((length >> 8) & 0xFF); - this.buffer[3] = (byte)(length & 0xFF); + this.buffer[2] = (byte)((app1Length >> 8) & 0xFF); + this.buffer[3] = (byte)(app1Length & 0xFF); this.outputStream.Write(this.buffer, 0, 4); - this.outputStream.Write(data, 0, data.Length); } /// From f8db637e4963859b1176db3a5cc6caa28d87d44c Mon Sep 17 00:00:00 2001 From: popow Date: Sat, 7 Jul 2018 21:57:48 +0200 Subject: [PATCH 21/28] added unit test for writing large exif profiles --- .../Profiles/Exif/ExifProfileTests.cs | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 7d4ddd322..7190fea44 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -28,11 +28,12 @@ namespace SixLabors.ImageSharp.Tests 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.ImageDescription, "ImageDescription" }, { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, + { ExifTag.Model, "Model" }, }; [Theory] @@ -272,22 +273,36 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(170, thumbnail.Height); } - [Fact] - public void WriteTooLargeProfile() + [Theory] + [InlineData(ExifTag.Software)] + [InlineData(ExifTag.Copyright)] + [InlineData(ExifTag.Model)] + [InlineData(ExifTag.ImageDescription)] + public void ReadWriteLargeProfileJpg(ExifTag exifValueToChange) { + // arrange var junk = new StringBuilder(); - for (int i = 0; i < 65500; i++) + for (int i = 0; i < 65600; i++) { - junk.Append("I"); + junk.Append("a"); } - var image = new Image(100, 100); - image.MetaData.ExifProfile = new ExifProfile(); - image.MetaData.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + expectedProfile.SetValue(exifValueToChange, junk.ToString()); + image.MetaData.ExifProfile = expectedProfile; - using (var memStream = new MemoryStream()) + // act + Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); + + // assert + ExifProfile actualProfile = reloadedImage.MetaData.ExifProfile; + Assert.NotNull(actualProfile); + foreach (ExifTag expectedProfileTag in expectedProfileTags) { - Assert.Throws(() => image.SaveAsJpeg(memStream)); + ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); + ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); } } @@ -331,7 +346,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(TestImageWriteFormat.Jpeg)] [InlineData(TestImageWriteFormat.Png)] - public void TestWritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) + public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) { // arrange var image = new Image(1, 1); @@ -355,26 +370,34 @@ namespace SixLabors.ImageSharp.Tests [Theory] [InlineData(false)] [InlineData(true)] - public void TestProfileToByteArrayWorks(bool includeExifIdCode) + public void ProfileToByteArray(bool includeExifIdCode) { // arrange byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; - ExifProfile profile = CreateExifProfile(); + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); // act - byte[] actual = profile.ToByteArray(includeExifIdCode ? ProfileResolver.ExifMarker : default(ReadOnlySpan)); + byte[] actualBytes = expectedProfile.ToByteArray(includeExifIdCode ? ProfileResolver.ExifMarker : default(ReadOnlySpan)); + var actualProfile = new ExifProfile(actualBytes); // assert - Assert.NotNull(actual); - Assert.NotEmpty(actual); + Assert.NotNull(actualBytes); + Assert.NotEmpty(actualBytes); if (includeExifIdCode) { - Assert.Equal(exifBytesWithExifCode, actual.Take(exifBytesWithExifCode.Length).ToArray()); + Assert.Equal(exifBytesWithExifCode, actualBytes.Take(exifBytesWithExifCode.Length).ToArray()); } else { - Assert.Equal(exifBytesWithoutExifCode, actual.Take(exifBytesWithoutExifCode.Length).ToArray()); + Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); + } + foreach(ExifTag expectedProfileTag in expectedProfileTags) + { + ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); + ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value); } } From f30389f7d5fcfe6b7787c67cf610c4f3492fbf93 Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 9 Jul 2018 19:22:09 +0200 Subject: [PATCH 22/28] removed unnecessary comments --- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 3 --- src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs | 1 - 2 files changed, 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index a143723e1..77a7243c4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -621,7 +621,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; int app1Length = bytesToWrite + 2; - // write the app1 header this.WriteApp1Header(app1Length); // write the exif data @@ -634,7 +633,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining; app1Length = bytesToWrite + 2 + 6; - // write the app1 header this.WriteApp1Header(app1Length); // write Exif00 marker @@ -671,7 +669,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void WriteIccProfile(IccProfile iccProfile) { - // Just in-case someone set the value to null by accident. if (iccProfile == null) { return; diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs index 32b1abf21..6d473fd4b 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -79,7 +79,6 @@ 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; From c91fb4a260188f184184523ac9d4b792cb7cc501 Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 9 Jul 2018 20:26:46 +0200 Subject: [PATCH 23/28] removed ExifIdCode parameter from ExifProfile.ToByteArray, because this is jpeg specific and should be handled by the jpeg encoder --- .../Formats/Jpeg/JpegEncoderCore.cs | 7 ++++++- .../MetaData/Profiles/Exif/ExifProfile.cs | 6 ++---- .../MetaData/Profiles/Exif/ExifWriter.cs | 14 ++----------- .../MetaData/ImageMetaDataTests.cs | 1 - .../Profiles/Exif/ExifProfileTests.cs | 21 ++++++------------- .../Processors/Transforms/AutoOrientTests.cs | 2 +- 6 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 77a7243c4..904fa5e6c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -611,12 +612,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { const int MaxBytesApp1 = 65533; const int MaxBytesWithExifId = 65527; - byte[] data = exifProfile?.ToByteArray(ProfileResolver.ExifMarker); + + byte[] data = exifProfile?.ToByteArray(); + if (data == null || data.Length == 0) { return; } + data = ProfileResolver.ExifMarker.Concat(data).ToArray(); + int remaining = data.Length; int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining; int app1Length = bytesToWrite + 2; diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 1f2695c5e..77ce13535 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -245,10 +245,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Converts this instance to a byte array. /// - /// The Exif Id Code is part of the JPEG APP1 segment (Exif00). Those bytes will be written at - /// the beginning of the array. This Exif ID code should not be included in case of PNG's. /// The - public byte[] ToByteArray(ReadOnlySpan exifIdCode = default) + public byte[] ToByteArray() { if (this.values == null) { @@ -261,7 +259,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } var writer = new ExifWriter(this.values, this.Parts); - return writer.GetData(exifIdCode); + return writer.GetData(); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs index 1de5fbd5c..dc75697e2 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs @@ -41,15 +41,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Returns the EXIF data. /// - /// The Exif Id Code is part of the JPEG APP1 segment (Exif00). Those bytes will be written at - /// the beginning of the array. This Exif ID code should not be included in case of PNG's. /// /// The . /// - public byte[] GetData(ReadOnlySpan exifIdCode) + public byte[] GetData() { - uint exifIdCodeLength = exifIdCode.IsEmpty ? 0 : (uint)exifIdCode.Length; - uint startIndex = exifIdCodeLength; + uint startIndex = 0; uint length; int exifIndex = -1; int gpsIndex = -1; @@ -85,8 +82,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return null; } - length += exifIdCodeLength; - // two bytes for the byte Order marker 'II', followed by the number 42 (0x2A) and a 0, making 4 bytes total length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; @@ -95,11 +90,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif byte[] result = new byte[length]; int i = 0; - if (!exifIdCode.IsEmpty) - { - exifIdCode.CopyTo(result); // 0-5 - i += exifIdCode.Length; - } // the byte order marker for little-endian, followed by the number 42 and a 0 ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i)); diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index 7d0686aa7..8934ebc36 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -33,7 +33,6 @@ namespace SixLabors.ImageSharp.Tests ImageMetaData clone = metaData.Clone(); Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); - Assert.Equal(exifProfile.ToByteArray(ProfileResolver.ExifMarker), clone.ExifProfile.ToByteArray(ProfileResolver.ExifMarker)); Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(2, clone.VerticalResolution); Assert.Equal(imageProperty, clone.Properties[0]); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 7190fea44..3deb382ea 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -339,8 +339,8 @@ namespace SixLabors.ImageSharp.Tests // Force parsing of the profile. Assert.Equal(24, profile.Values.Count); - byte[] bytes = profile.ToByteArray(ProfileResolver.ExifMarker); - Assert.Equal(495, bytes.Length); + byte[] bytes = profile.ToByteArray(); + Assert.Equal(489, bytes.Length); } [Theory] @@ -367,10 +367,8 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ProfileToByteArray(bool includeExifIdCode) + [Fact] + public void ProfileToByteArray() { // arrange byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); @@ -379,20 +377,13 @@ namespace SixLabors.ImageSharp.Tests var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); // act - byte[] actualBytes = expectedProfile.ToByteArray(includeExifIdCode ? ProfileResolver.ExifMarker : default(ReadOnlySpan)); + byte[] actualBytes = expectedProfile.ToByteArray(); var actualProfile = new ExifProfile(actualBytes); // assert Assert.NotNull(actualBytes); Assert.NotEmpty(actualBytes); - if (includeExifIdCode) - { - Assert.Equal(exifBytesWithExifCode, actualBytes.Take(exifBytesWithExifCode.Length).ToArray()); - } - else - { - Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); - } + Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); foreach(ExifTag expectedProfileTag in expectedProfileTags) { ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index 85c803ae6..9b37fb266 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms var profile = new ExifProfile(); profile.SetValue(ExifTag.JPEGTables, orientation); - byte[] bytes = profile.ToByteArray(ProfileResolver.ExifMarker); + byte[] bytes = profile.ToByteArray(); // Change the tag into ExifTag.Orientation bytes[16] = 18; bytes[17] = 1; From 675c0a671a4f9c2ddc3080849b8291802806568d Mon Sep 17 00:00:00 2001 From: popow Date: Mon, 9 Jul 2018 21:07:35 +0200 Subject: [PATCH 24/28] to keep the ExifReader free from jpeg specific stuff, the Exif Id Code will be skipped when setting the ExifProfile --- .../Jpeg/GolangPort/GolangJpegDecoderCore.cs | 4 ++-- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 4 +++- .../MetaData/Profiles/Exif/ExifReader.cs | 21 +++---------------- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs index 46cdcddb4..bd0c98c82 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; - +using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; @@ -500,7 +500,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) { this.isExif = true; - this.MetaData.ExifProfile = new ExifProfile(profile); + this.MetaData.ExifProfile = new ExifProfile(profile.Skip(6).ToArray()); } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 1336b8a89..72b5698c5 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -482,7 +483,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort this.isExif = true; if (this.MetaData.ExifProfile == null) { - this.MetaData.ExifProfile = new ExifProfile(profile); + // the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific + this.MetaData.ExifProfile = new ExifProfile(profile.Skip(6).ToArray()); } else { diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs index 6d473fd4b..db1d0c622 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif private Endianness endianness = Endianness.BigEndian; private uint exifOffset; private uint gpsOffset; - private int startIndex; public ExifReader(byte[] exifData) { @@ -77,20 +76,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { var values = new List(); - if (this.ReadString(4) == "Exif") - { - if (this.ReadUInt16() != 0) - { - return values; - } - - this.startIndex = 6; - } - else - { - this.position = 0; - } - if (this.ReadString(2) == "II") { this.endianness = Endianness.LittleEndian; @@ -169,7 +154,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The index. private void AddValues(List values, int index) { - this.position = this.startIndex + index; + this.position = index; int count = this.ReadUInt16(); for (int i = 0; i < count; i++) @@ -353,7 +338,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { int oldIndex = this.position; - uint newIndex = this.ConvertToUInt32(offsetBuffer) + (uint)this.startIndex; + uint newIndex = this.ConvertToUInt32(offsetBuffer); // Ensure that the new index does not overrun the data if (newIndex > int.MaxValue) @@ -454,7 +439,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) { - this.ThumbnailOffset = (uint)value.Value + (uint)this.startIndex; + this.ThumbnailOffset = (uint)value.Value; } else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) { From 8b0b7ab1b46ff3c8559be593c433e24b650c26f7 Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 10 Jul 2018 19:48:42 +0200 Subject: [PATCH 25/28] skipping exif id code before extending the exif profile --- .../Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 2 +- src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 72b5698c5..d539cf37c 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -489,7 +489,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort else { // if the exif information exceeds 64K, it will be split over multiple APP1 marker - this.MetaData.ExifProfile.Extend(profile); + this.MetaData.ExifProfile.Extend(profile.Skip(6).ToArray()); } } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 77ce13535..5ab6c59ac 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -237,9 +237,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { int currentLength = this.data.Length; - // the first 6 bytes are Exif00 and will be skipped - Array.Resize(ref this.data, currentLength + bytes.Length - 6); - Buffer.BlockCopy(bytes, 6, this.data, currentLength, bytes.Length - 6); + Array.Resize(ref this.data, currentLength + bytes.Length); + Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length); } /// From f488d5a33d947a192fedc3afb261fbf675579af0 Mon Sep 17 00:00:00 2001 From: popow Date: Tue, 10 Jul 2018 20:02:42 +0200 Subject: [PATCH 26/28] added support for large exif profiles for the old GolangJpegDecoderCore --- .../Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs index bd0c98c82..6a4042ba0 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs @@ -500,7 +500,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) { this.isExif = true; - this.MetaData.ExifProfile = new ExifProfile(profile.Skip(6).ToArray()); + if (this.MetaData.ExifProfile == null) + { + // the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific + this.MetaData.ExifProfile = new ExifProfile(profile.Skip(6).ToArray()); + } + else + { + // if the exif information exceeds 64K, it will be split over multiple APP1 marker + this.MetaData.ExifProfile.Extend(profile.Skip(6).ToArray()); + } } } From 481b52f42cc844e8d95caf258e91ff0364f16c06 Mon Sep 17 00:00:00 2001 From: popow Date: Fri, 13 Jul 2018 20:08:26 +0200 Subject: [PATCH 27/28] moved extending the ExifProfile to the jpeg decoder --- .../Jpeg/GolangPort/GolangJpegDecoderCore.cs | 38 +++++++++++++++++-- .../Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs | 38 +++++++++++++++++-- .../MetaData/Profiles/Exif/ExifProfile.cs | 12 ------ 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs index 6a4042ba0..f2cd24411 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/GolangJpegDecoderCore.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -68,6 +69,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// private bool isExif; + /// + /// Contains exif data + /// + private byte[] exifData; + /// /// Whether the image has an Adobe marker. /// It's faster to check this than to use the equality operator on the struct @@ -413,6 +419,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } } + this.InitExifProfile(); this.InitDerivedMetaDataProperties(); } @@ -425,6 +432,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort && mcuCounter < this.TotalMCUCount; } + /// + /// Initializes the exif profile. + /// + private void InitExifProfile() + { + if (this.isExif) + { + this.MetaData.ExifProfile = new ExifProfile(this.exifData); + } + } + /// /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// @@ -500,19 +518,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) { this.isExif = true; - if (this.MetaData.ExifProfile == null) + if (this.exifData == null) { // the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific - this.MetaData.ExifProfile = new ExifProfile(profile.Skip(6).ToArray()); + this.exifData = profile.Skip(6).ToArray(); } else { - // if the exif information exceeds 64K, it will be split over multiple APP1 marker - this.MetaData.ExifProfile.Extend(profile.Skip(6).ToArray()); + // if the exif information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendExif(profile.Skip(6).ToArray()); } } } + /// + /// Extends the exif profile with additional data. + /// + /// The array containing addition profile data. + private void ExtendExif(byte[] bytes) + { + int currentLength = this.exifData.Length; + + Array.Resize(ref this.exifData, currentLength + bytes.Length); + Buffer.BlockCopy(bytes, 0, this.exifData, currentLength, bytes.Length); + } + /// /// Processes the App2 marker retrieving any stored ICC profile information /// diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index d539cf37c..c143812c6 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -74,6 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// private bool isExif; + /// + /// Contains exif data + /// + private byte[] exifData; + /// /// Contains information about the JFIF marker /// @@ -202,6 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort where TPixel : struct, IPixel { this.ParseStream(stream); + this.InitExifProfile(); this.InitDerivedMetaDataProperties(); return this.PostProcessIntoImage(); } @@ -213,6 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort public IImageInfo Identify(Stream stream) { this.ParseStream(stream, true); + this.InitExifProfile(); this.InitDerivedMetaDataProperties(); return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); } @@ -404,6 +411,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.ComponentCount}"); } + /// + /// Initializes the exif profile. + /// + private void InitExifProfile() + { + if (this.isExif) + { + this.MetaData.ExifProfile = new ExifProfile(this.exifData); + } + } + /// /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// @@ -481,19 +499,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) { this.isExif = true; - if (this.MetaData.ExifProfile == null) + if (this.exifData == null) { // the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific - this.MetaData.ExifProfile = new ExifProfile(profile.Skip(6).ToArray()); + this.exifData = profile.Skip(6).ToArray(); } else { - // if the exif information exceeds 64K, it will be split over multiple APP1 marker - this.MetaData.ExifProfile.Extend(profile.Skip(6).ToArray()); + // if the exif information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendExif(profile.Skip(6).ToArray()); } } } + /// + /// Extends the exif profile with additional data. + /// + /// The array containing addition profile data. + private void ExtendExif(byte[] bytes) + { + int currentLength = this.exifData.Length; + + Array.Resize(ref this.exifData, currentLength + bytes.Length); + Buffer.BlockCopy(bytes, 0, this.exifData, currentLength, bytes.Length); + } + /// /// Processes the App2 marker retrieving any stored ICC profile information /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 5ab6c59ac..6f5af8ffc 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -229,18 +229,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif this.values.Add(newExifValue); } - /// - /// Extends the profile with additional data. - /// - /// The array containing addition profile data. - public void Extend(byte[] bytes) - { - int currentLength = this.data.Length; - - Array.Resize(ref this.data, currentLength + bytes.Length); - Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length); - } - /// /// Converts this instance to a byte array. /// From d7f6badc19010ce02c6312a74453480f6a2ae362 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 4 Aug 2018 09:56:39 +0100 Subject: [PATCH 28/28] Align ICC and EXIF API. --- .../Formats/Jpeg/JpegDecoderCore.cs | 80 ++++++++++++------- .../MetaData/Profiles/ICC/IccProfile.cs | 11 --- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4eb770994..4f71d15b0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private ushort resetInterval; /// - /// Whether the image has a EXIF header + /// Whether the image has an EXIF marker /// private bool isExif; @@ -79,6 +79,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private byte[] exifData; + /// + /// Whether the image has an ICC marker + /// + private bool isIcc; + + /// + /// Contains ICC data + /// + private byte[] iccData; + /// /// Contains information about the JFIF marker /// @@ -208,6 +218,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { this.ParseStream(stream); this.InitExifProfile(); + this.InitIccProfile(); this.InitDerivedMetaDataProperties(); return this.PostProcessIntoImage(); } @@ -220,6 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { this.ParseStream(stream, true); this.InitExifProfile(); + this.InitIccProfile(); this.InitDerivedMetaDataProperties(); return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); } @@ -412,7 +424,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } /// - /// Initializes the exif profile. + /// Initializes the EXIF profile. /// private void InitExifProfile() { @@ -422,6 +434,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + /// + /// Initializes the ICC profile. + /// + private void InitIccProfile() + { + if (this.isIcc) + { + var profile = new IccProfile(this.iccData); + if (profile.CheckIsValid()) + { + this.MetaData.IccProfile = profile; + } + } + } + /// /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// @@ -450,11 +477,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.MetaData.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.MetaData.ExifProfile); } } + } - if (this.MetaData.IccProfile?.CheckIsValid() == false) - { - this.MetaData.IccProfile = null; - } + /// + /// Extends the profile with additional data. + /// + /// The profile data array. + /// The array containing addition profile data. + private void ExtendProfile(ref byte[] profile, byte[] extension) + { + int currentLength = profile.Length; + + Array.Resize(ref profile, currentLength + extension.Length); + Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length); } /// @@ -488,7 +523,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessApp1Marker(int remaining) { - if (remaining < 6 || this.IgnoreMetadata) + const int Exif00 = 6; + if (remaining < Exif00 || this.IgnoreMetadata) { // Skip the application header length this.InputStream.Skip(remaining); @@ -503,29 +539,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.isExif = true; if (this.exifData == null) { - // the first 6 bytes (Exif00) will be skipped, because this is Jpeg specific - this.exifData = profile.Skip(6).ToArray(); + // The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific + this.exifData = profile.Skip(Exif00).ToArray(); } else { - // if the exif information exceeds 64K, it will be split over multiple APP1 markers - this.ExtendExif(profile.Skip(6).ToArray()); + // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendProfile(ref this.exifData, profile.Skip(Exif00).ToArray()); } } } - /// - /// Extends the exif profile with additional data. - /// - /// The array containing addition profile data. - private void ExtendExif(byte[] bytes) - { - int currentLength = this.exifData.Length; - - Array.Resize(ref this.exifData, currentLength + bytes.Length); - Buffer.BlockCopy(bytes, 0, this.exifData, currentLength, bytes.Length); - } - /// /// Processes the App2 marker retrieving any stored ICC profile information /// @@ -546,16 +570,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) { + this.isIcc = true; byte[] profile = new byte[remaining]; this.InputStream.Read(profile, 0, remaining); - if (this.MetaData.IccProfile == null) + if (this.iccData == null) { - this.MetaData.IccProfile = new IccProfile(profile); + this.iccData = profile; } else { - this.MetaData.IccProfile.Extend(profile); + // If the ICC information exceeds 64K, it will be split over multiple APP2 markers + this.ExtendProfile(ref this.iccData, profile); } } else @@ -670,7 +696,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. /// The current frame marker. /// Whether to parse metadata only - private void ProcessStartOfFrameMarker(int remaining, JpegFileMarker frameMarker, bool metadataOnly) + private void ProcessStartOfFrameMarker(int remaining, in JpegFileMarker frameMarker, bool metadataOnly) { if (this.Frame != null) { diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index db1d96d7e..2b2fe1e4e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -149,17 +149,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc #endif - /// - /// Extends the profile with additional data. - /// - /// The array containing addition profile data. - public void Extend(byte[] bytes) - { - int currentLength = this.data.Length; - Array.Resize(ref this.data, currentLength + bytes.Length); - Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length); - } - /// /// Checks for signs of a corrupt profile. ///