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/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 3b34719a8..4f71d15b0 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.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;
using SixLabors.ImageSharp.Common.Helpers;
@@ -69,10 +70,25 @@ 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;
+ ///
+ /// Contains exif data
+ ///
+ 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
///
@@ -201,6 +217,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
where TPixel : struct, IPixel
{
this.ParseStream(stream);
+ this.InitExifProfile();
+ this.InitIccProfile();
this.InitDerivedMetaDataProperties();
return this.PostProcessIntoImage();
}
@@ -212,6 +230,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public IImageInfo Identify(Stream stream)
{
this.ParseStream(stream, true);
+ this.InitExifProfile();
+ this.InitIccProfile();
this.InitDerivedMetaDataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@@ -403,6 +423,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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);
+ }
+ }
+
+ ///
+ /// 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.
///
@@ -431,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);
}
///
@@ -469,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);
@@ -482,7 +537,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
{
this.isExif = true;
- this.MetaData.ExifProfile = new ExifProfile(profile);
+ if (this.exifData == null)
+ {
+ // 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.ExtendProfile(ref this.exifData, profile.Skip(Exif00).ToArray());
+ }
}
}
@@ -506,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
@@ -630,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/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index ada33f2b8..1a3bb7723 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -4,9 +4,11 @@
using System;
using System.Buffers.Binary;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
+using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
@@ -214,6 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Start Of Image marker.
this.WriteApplicationHeader(image.MetaData);
+ // Write Exif and ICC profiles
this.WriteProfiles(image);
// Write the quantization tables.
@@ -430,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The image meta data.
private void WriteApplicationHeader(ImageMetaData meta)
{
- // 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;
@@ -620,27 +623,59 @@ 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();
+
if (data == null || data.Length == 0)
{
return;
}
- if (data.Length > Max)
+ data = ProfileResolver.ExifMarker.Concat(data).ToArray();
+
+ int remaining = data.Length;
+ int bytesToWrite = remaining > MaxBytesApp1 ? MaxBytesApp1 : remaining;
+ int app1Length = bytesToWrite + 2;
+
+ 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. nameof{Max}");
- }
+ bytesToWrite = remaining > MaxBytesWithExifId ? MaxBytesWithExifId : remaining;
+ app1Length = bytesToWrite + 2 + 6;
- int length = data.Length + 2;
+ this.WriteApp1Header(app1Length);
+ // write Exif00 marker
+ ProfileResolver.ExifMarker.AsSpan().CopyTo(this.buffer.AsSpan());
+ this.outputStream.Write(this.buffer, 0, 6);
+
+ // 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);
}
///
@@ -652,7 +687,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
private void WriteIccProfile(IccProfile iccProfile)
{
- // Just incase someone set the value to null by accident.
if (iccProfile == null)
{
return;
@@ -908,7 +942,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/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 83c195eec..779a41999 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -14,6 +14,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.MetaData;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
@@ -259,6 +260,15 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Text:
this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
+ break;
+ case PngChunkType.Exif:
+ 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;
@@ -1170,7 +1180,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)
@@ -1217,7 +1227,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
{
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index e696e1f68..b420834fb 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -231,6 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
+ this.WriteExifChunk(stream, image);
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream);
stream.Flush();
@@ -490,7 +491,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)
@@ -648,6 +649,22 @@ namespace SixLabors.ImageSharp.Formats.Png
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9);
}
+ ///
+ /// 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)
+ {
+ image.MetaData.SyncProfiles();
+ this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray());
+ }
+ }
+
///
/// Writes the gamma information to the stream.
///
diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs
index cca53ba43..555cadafe 100644
--- a/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs
+++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifConstants.cs
@@ -5,17 +5,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
{
internal static class ExifConstants
{
- public static readonly byte[] Header = {
- (byte)'E',
- (byte)'x',
- (byte)'i',
- (byte)'f',
- 0x00,
- 0x00,
+ public static readonly byte[] LittleEndianByteOrderMarker = {
(byte)'I',
(byte)'I',
0x2A,
0x00,
};
+
+ public static readonly byte[] BigEndianByteOrderMarker = {
+ (byte)'M',
+ (byte)'M',
+ 0x00,
+ 0x2A
+ };
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs
index 0f19083e5..6f5af8ffc 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;
@@ -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
@@ -86,7 +85,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);
}
}
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)
{
diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifWriter.cs
index 8749c0755..dc75697e2 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.
///
@@ -51,6 +46,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
///
public byte[] GetData()
{
+ uint startIndex = 0;
uint length;
int exifIndex = -1;
int gpsIndex = -1;
@@ -86,14 +82,20 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return null;
}
- length += 10 + 4 + 2;
+ // 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];
- ExifConstants.Header.AsSpan().CopyTo(result); // 0-9
+ int i = 0;
+
+ // the byte order marker for little-endian, followed by the number 42 and a 0
+ ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i));
+ i += ExifConstants.LittleEndianByteOrderMarker.Length;
- int i = 10;
- uint ifdOffset = ((uint)i - StartIndex) + 4;
+ uint ifdOffset = ((uint)i - startIndex) + 4;
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength;
if (exifLength > 0)
@@ -109,18 +111,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);
@@ -257,7 +259,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return newOffset;
}
- private int WriteData(List indexes, Span destination, int offset)
+ private int WriteData(uint startIndex, List indexes, Span destination, int offset)
{
if (this.dataOffsets.Count == 0)
{
@@ -272,7 +274,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/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.
///
diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
index 80cf162c5..0ca3cffa1 100644
--- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
+++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
@@ -21,7 +21,7 @@
-
+
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
index 016c932dd..35652dd6b 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs
@@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
Assert.Equal(PngChunkType.Text, GetType("tEXt"));
Assert.Equal(PngChunkType.Gamma, GetType("gAMA"));
Assert.Equal(PngChunkType.Physical, GetType("pHYs"));
+ Assert.Equal(PngChunkType.Exif, GetType("eXIf"));
}
private static PngChunkType GetType(string text)
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index 9e15b6aba..d153ecf50 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -36,7 +36,7 @@
-
+
diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
index 255451e0e..8934ebc36 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;
diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
index 3c69b57fd..3deb382ea 100644
--- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
+++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs
@@ -3,9 +3,11 @@
using System;
using System.Collections;
+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;
@@ -17,8 +19,27 @@ namespace SixLabors.ImageSharp.Tests
{
public class ExifProfileTests
{
- [Fact]
- public void Constructor()
+ public enum TestImageWriteFormat
+ {
+ Jpeg,
+ Png
+ }
+
+ private static readonly Dictionary TestProfileValues = new Dictionary()
+ {
+ { ExifTag.Software, "Software" },
+ { 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]
+ [InlineData(TestImageWriteFormat.Jpeg)]
+ [InlineData(TestImageWriteFormat.Png)]
+ public void Constructor(TestImageWriteFormat imageFormat)
{
Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateImage();
@@ -27,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests
image.MetaData.ExifProfile = new ExifProfile();
image.MetaData.ExifProfile.SetValue(ExifTag.Copyright, "Dirk Lemstra");
- image = WriteAndRead(image);
+ image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(1, image.MetaData.ExifProfile.Values.Count());
@@ -50,7 +71,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);
@@ -59,10 +80,12 @@ namespace SixLabors.ImageSharp.Tests
TestProfile(clone);
}
- [Fact]
- public void WriteFraction()
+ [Theory]
+ [InlineData(TestImageWriteFormat.Jpeg)]
+ [InlineData(TestImageWriteFormat.Png)]
+ public void WriteFraction(TestImageWriteFormat imageFormat)
{
- using (MemoryStream memStream = new MemoryStream())
+ using (var memStream = new MemoryStream())
{
double exposureTime = 1.0 / 1600;
@@ -70,13 +93,10 @@ 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);
-
- memStream.Position = 0;
- image = Image.Load(memStream);
+ image = WriteAndRead(image, imageFormat);
profile = image.MetaData.ExifProfile;
Assert.NotNull(profile);
@@ -91,10 +111,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);
@@ -104,36 +121,40 @@ 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));
- 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 = 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 = WriteAndRead(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)
{
- 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 +192,7 @@ namespace SixLabors.ImageSharp.Tests
value = image.MetaData.ExifProfile.GetValue(ExifTag.GPSLatitude);
TestValue(value, latitude);
- image = WriteAndRead(image);
+ image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(17, image.MetaData.ExifProfile.Values.Count());
@@ -193,7 +214,7 @@ namespace SixLabors.ImageSharp.Tests
image.MetaData.ExifProfile.Parts = ExifParts.ExifTags;
- image = WriteAndRead(image);
+ image = WriteAndRead(image, imageFormat);
Assert.NotNull(image.MetaData.ExifProfile);
Assert.Equal(8, image.MetaData.ExifProfile.Values.Count());
@@ -252,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());
-
- using (var memStream = new MemoryStream())
+ ExifProfile expectedProfile = CreateExifProfile();
+ var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList();
+ expectedProfile.SetValue(exifValueToChange, junk.ToString());
+ image.MetaData.ExifProfile = expectedProfile;
+
+ // 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);
}
}
@@ -305,10 +340,71 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(24, profile.Values.Count);
byte[] bytes = profile.ToByteArray();
- Assert.Equal(495, bytes.Length);
+ Assert.Equal(489, bytes.Length);
+ }
+
+ [Theory]
+ [InlineData(TestImageWriteFormat.Jpeg)]
+ [InlineData(TestImageWriteFormat.Png)]
+ public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat)
+ {
+ // arrange
+ var image = new Image(1, 1);
+ ExifProfile expected = CreateExifProfile();
+ image.MetaData.ExifProfile = expected;
+
+ // act
+ Image reloadedImage = WriteAndRead(image, imageFormat);
+
+ // assert
+ ExifProfile actual = reloadedImage.MetaData.ExifProfile;
+ Assert.NotNull(actual);
+ foreach(KeyValuePair expectedProfileValue in TestProfileValues)
+ {
+ ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key);
+ Assert.NotNull(actualProfileValue);
+ Assert.Equal(expectedProfileValue.Value, actualProfileValue.Value);
+ }
+ }
+
+ [Fact]
+ public void ProfileToByteArray()
+ {
+ // arrange
+ byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray();
+ byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker;
+ ExifProfile expectedProfile = CreateExifProfile();
+ var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList();
+
+ // act
+ byte[] actualBytes = expectedProfile.ToByteArray();
+ var actualProfile = new ExifProfile(actualBytes);
+
+ // assert
+ Assert.NotNull(actualBytes);
+ Assert.NotEmpty(actualBytes);
+ 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);
+ }
}
- private static ExifProfile GetExifProfile()
+ private static ExifProfile CreateExifProfile()
+ {
+ var profile = new ExifProfile();
+
+ foreach(KeyValuePair exifProfileValue in TestProfileValues)
+ {
+ profile.SetValue(exifProfileValue.Key, exifProfileValue.Value);
+ }
+
+ return profile;
+ }
+
+ internal static ExifProfile GetExifProfile()
{
Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage();
@@ -318,9 +414,22 @@ namespace SixLabors.ImageSharp.Tests
return profile;
}
- private static Image WriteAndRead(Image image)
+ private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat)
{
- using (MemoryStream memStream = new MemoryStream())
+ 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())
{
image.SaveAsJpeg(memStream);
image.Dispose();
@@ -330,6 +439,18 @@ namespace SixLabors.ImageSharp.Tests
}
}
+ private static Image WriteAndReadPng(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);
@@ -341,13 +462,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);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs
index d31f999d0..9b37fb266 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs
@@ -5,12 +5,11 @@ using System;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
- using SixLabors.ImageSharp.Processing;
-
public class AutoOrientTests : FileTestBase
{
public static readonly string[] FlipFiles = { TestImages.Bmp.F };