diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index d5d55bcd2..ae3ffc69d 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -25b2ab2a0acfb874ca8ac8ae979fc6b1bc9bf466 \ No newline at end of file +09f4618eaade2900f7174b9ab400deb0a25bd813 \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index 48c65caad..2368a498d 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -488,10 +488,9 @@ namespace ImageProcessorCore.Formats int componentCount = 3; // Write the Start Of Image marker. - double densityX = image.HorizontalResolution; - double densityY = image.VerticalResolution; + WriteApplicationHeader((short)image.HorizontalResolution, (short)image.VerticalResolution); - WriteApplicationHeader((short)densityX, (short)densityY); + WriteProfiles(image); // Write the quantization tables. this.WriteDQT(); @@ -571,6 +570,40 @@ namespace ImageProcessorCore.Formats this.outputStream.Write(this.buffer, 0, 4); } + private void WriteProfiles(Image image) + where T : IPackedVector + where TP : struct + { + WriteProfile(image.ExifProfile); + } + + private void WriteProfile(ExifProfile exifProfile) + { + if (exifProfile == null) + { + return; + } + + byte[] data = exifProfile.ToByteArray(); + if (data == null || data.Length == 0) + { + return; + } + + if (data.Length > 65533) + { + throw new ImageFormatException("Exif profile size exceeds limit."); + } + + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP1; // Application Marker + this.buffer[2] = (byte)((data.Length >> 8) & 0xFF); + this.buffer[3] = (byte)(data.Length & 0xFF); + + this.outputStream.Write(this.buffer, 0, 4); + this.outputStream.Write(data, 0, data.Length); + } + /// /// Writes the Define Quantization Marker and tables. /// diff --git a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs new file mode 100644 index 000000000..908d49b59 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs @@ -0,0 +1,306 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System; + using System.Collections; + using System.IO; + using System.Linq; + using System.Text; + using Xunit; + + public class ExifProfileTests + { + [Fact] + public void Constructor() + { + using (FileStream stream = File.OpenRead(TestImages.Jpg.Calliphora)) + { + Image image = new Image(stream); + + Assert.Null(image.ExifProfile); + + image.ExifProfile = new ExifProfile(); + image.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra"); + + image = WriteAndRead(image); + + Assert.NotNull(image.ExifProfile); + Assert.Equal(1, image.ExifProfile.Values.Count()); + + ExifValue value = image.ExifProfile.Values.FirstOrDefault(val => val.Tag == ExifTag.Copyright); + TestValue(value, "Dirk Lemstra"); + } + } + + [Fact] + public void ConstructorEmpty() + { + new ExifProfile((byte[])null); + new ExifProfile(new byte[] { }); + } + + [Fact] + public void ConstructorCopy() + { + Assert.Throws(() => { new ExifProfile((ExifProfile)null); }); + + ExifProfile profile = GetExifProfile(); + + ExifProfile clone = new ExifProfile(profile); + TestProfile(clone); + + profile.SetValue(ExifTag.ColorSpace, (ushort)2); + + clone = new ExifProfile(profile); + TestProfile(clone); + } + + [Fact] + public void WriteFraction() + { + using (MemoryStream memStream = new MemoryStream()) + { + double exposureTime = 1.0 / 1600; + + ExifProfile profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, exposureTime); + + Image image = new Image(1, 1); + image.ExifProfile = profile; + + image.SaveAsJpeg(memStream); + + memStream.Position = 0; + image = new Image(memStream); + + profile = image.ExifProfile; + Assert.NotNull(profile); + + ExifValue value = profile.GetValue(ExifTag.ExposureTime); + Assert.NotNull(value); + Assert.NotEqual(exposureTime, value.Value); + + memStream.Position = 0; + profile = GetExifProfile(); + + profile.SetValue(ExifTag.ExposureTime, exposureTime); + profile.BestPrecision = true; + image.ExifProfile = profile; + + image.SaveAsJpeg(memStream); + + memStream.Position = 0; + image = new Image(memStream); + + profile = image.ExifProfile; + Assert.NotNull(profile); + + value = profile.GetValue(ExifTag.ExposureTime); + TestValue(value, exposureTime); + } + } + + [Fact] + public void ReadWriteInfinity() + { + using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) + { + Image image = new Image(stream); + image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.PositiveInfinity); + + image = WriteAndRead(image); + ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(double.PositiveInfinity, value.Value); + + image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.NegativeInfinity); + + image = WriteAndRead(image); + value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(double.NegativeInfinity, value.Value); + + image.ExifProfile.SetValue(ExifTag.FlashEnergy, double.NegativeInfinity); + + image = WriteAndRead(image); + value = image.ExifProfile.GetValue(ExifTag.FlashEnergy); + Assert.NotNull(value); + Assert.Equal(double.PositiveInfinity, value.Value); + } + } + + [Fact] + public void SetValue() + { + double[] latitude = new double[] { 12.3, 4.56, 789.0 }; + + using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) + { + Image image = new Image(stream); + image.ExifProfile.SetValue(ExifTag.Software, "ImageProcessorCore"); + + ExifValue value = image.ExifProfile.GetValue(ExifTag.Software); + TestValue(value, "ImageProcessorCore"); + + Assert.Throws(() => { value.Value = 15; }); + + image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, 75.55); + + value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + TestValue(value, 75.55); + + Assert.Throws(() => { value.Value = 75; }); + + image.ExifProfile.SetValue(ExifTag.XResolution, 150.0); + + value = image.ExifProfile.GetValue(ExifTag.XResolution); + TestValue(value, 150.0); + + Assert.Throws(() => { value.Value = "ImageProcessorCore"; }); + + image.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + + value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + TestValue(value, (string)null); + + image.ExifProfile.SetValue(ExifTag.GPSLatitude, latitude); + + value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + TestValue(value, latitude); + + image = WriteAndRead(image); + + Assert.NotNull(image.ExifProfile); + Assert.Equal(17, image.ExifProfile.Values.Count()); + + value = image.ExifProfile.GetValue(ExifTag.Software); + TestValue(value, "ImageProcessorCore"); + + value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + TestValue(value, 75.55); + + value = image.ExifProfile.GetValue(ExifTag.XResolution); + TestValue(value, 150.0); + + value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(value); + + value = image.ExifProfile.GetValue(ExifTag.GPSLatitude); + TestValue(value, latitude); + + image.ExifProfile.Parts = ExifParts.ExifTags; + + image = WriteAndRead(image); + + Assert.NotNull(image.ExifProfile); + Assert.Equal(8, image.ExifProfile.Values.Count()); + + Assert.NotNull(image.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.True(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.False(image.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.Null(image.ExifProfile.GetValue(ExifTag.ColorSpace)); + + Assert.Equal(7, image.ExifProfile.Values.Count()); + } + } + + [Fact] + public void Values() + { + ExifProfile profile = GetExifProfile(); + + TestProfile(profile); + + var thumbnail = profile.CreateThumbnail(); + Assert.NotNull(thumbnail); + Assert.Equal(256, thumbnail.Width); + Assert.Equal(170, thumbnail.Height); + } + + [Fact] + public void WriteTooLargeProfile() + { + StringBuilder junk = new StringBuilder(); + for (int i = 0; i < 65500; i++) + junk.Append("I"); + + Image image = new Image(100, 100); + image.ExifProfile = new ExifProfile(); + image.ExifProfile.SetValue(ExifTag.ImageDescription, junk.ToString()); + + using (MemoryStream memStream = new MemoryStream()) + { + Assert.Throws(() => image.SaveAsJpeg(memStream)); + } + } + + private static ExifProfile GetExifProfile() + { + using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) + { + Image image = new Image(stream); + + ExifProfile profile = image.ExifProfile; + Assert.NotNull(profile); + + return profile; + } + } + + private static Image WriteAndRead(Image image) + { + using (MemoryStream memStream = new MemoryStream()) + { + image.SaveAsJpeg(memStream); + + memStream.Position = 0; + return new Image(memStream); + } + } + + private static void TestProfile(ExifProfile profile) + { + Assert.NotNull(profile); + + Assert.Equal(16, profile.Values.Count()); + + foreach (ExifValue value in profile.Values) + { + 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(300.0, value.Value); + + if (value.Tag == ExifTag.PixelXDimension) + Assert.Equal(2338U, value.Value); + } + } + + private static void TestValue(ExifValue value, string expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, double expected) + { + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + } + + private static void TestValue(ExifValue value, double[] expected) + { + Assert.NotNull(value); + + Assert.Equal(expected, (ICollection)value.Value); + } + } +} diff --git a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifValueTests.cs b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifValueTests.cs new file mode 100644 index 000000000..3ee09c23d --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifValueTests.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + using System.Linq; + using Xunit; + + public class ExifValueTests + { + private static ExifValue GetExifValue() + { + using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan)) + { + Image image = new Image(stream); + + ExifProfile profile = image.ExifProfile; + Assert.NotNull(profile); + + return profile.Values.First(); + } + } + + [Fact] + public void IEquatable() + { + ExifValue first = GetExifValue(); + ExifValue second = GetExifValue(); + + Assert.True(first == second); + Assert.True(first.Equals(second)); + Assert.True(first.Equals((object)second)); + } + + [Fact] + public void Properties() + { + ExifValue value = GetExifValue(); + + Assert.Equal(ExifDataType.Ascii, value.DataType); + Assert.Equal(ExifTag.GPSDOP, value.Tag); + Assert.Equal(false, value.IsArray); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString()); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.Value); + } + } +}