diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index fc2a9130e..13c51db24 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1000,7 +1000,14 @@ namespace ImageSharp.Formats byte[] profile = new byte[remaining]; this.InputProcessor.ReadFull(profile, 0, remaining); - metadata.IccProfiles.Add(new IccProfile(profile)); + if (metadata.IccProfile == null) + { + metadata.IccProfile = new IccProfile(profile); + } + else + { + metadata.IccProfile.Extend(profile); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 4dddd25ed..b65a56e73 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -700,42 +700,60 @@ namespace ImageSharp.Formats } /// - /// Writes the ICC profiles. + /// Writes the ICC profile. /// - /// The list of ICC profiles. + /// The ICC profile to write. /// /// Thrown if any of the ICC profiles size exceeds the limit /// - private void WriteICCProfiles(IList iccProfiles) + private void WriteIccProfile(IccProfile iccProfile) { // Just incase someone set the value to null by accident. - if (iccProfiles == null || !iccProfiles.Any()) + if (iccProfile == null) { return; } + const int IccOverheadLength = 14; const int Max = 65533; - int count = iccProfiles.Count; + const int MaxData = Max - IccOverheadLength; - for (int i = 1; i <= count; i++) + byte[] data = iccProfile.ToByteArray(); + + if (data == null || data.Length == 0) { - byte[] data = iccProfiles[i - 1]?.ToByteArray(); + return; + } - if (data == null || data.Length == 0) - { - continue; - } + // Calculate the number of markers we'll need, rounding up of course + int dataLength = data.Length; + int count = dataLength / MaxData; - if (data.Length > Max) + if (count * MaxData != dataLength) + { + count++; + } + + // Per spec, counting starts at 1. + int current = 1; + int offset = 0; + + while (dataLength > 0) + { + int length = dataLength; // Number of bytes to write. + + if (length > MaxData) { - throw new ImageFormatException($"ICC profile size exceeds limit. nameof{Max}"); + length = MaxData; } + dataLength -= length; + this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker - int length = data.Length + 16; - this.buffer[2] = (byte)((length >> 8) & 0xFF); - this.buffer[3] = (byte)(length & 0xFF); + int markerLength = length + 16; + this.buffer[2] = (byte)((markerLength >> 8) & 0xFF); + this.buffer[3] = (byte)(markerLength & 0xFF); this.outputStream.Write(this.buffer, 0, 4); @@ -751,11 +769,14 @@ namespace ImageSharp.Formats this.buffer[9] = (byte)'L'; this.buffer[10] = (byte)'E'; this.buffer[11] = 0x00; - this.buffer[12] = (byte)i; // The position within the collection. + this.buffer[12] = (byte)current; // The position within the collection. this.buffer[13] = (byte)count; // The total number of profiles. - this.outputStream.Write(this.buffer, 0, 14); - this.outputStream.Write(data, 0, data.Length); + this.outputStream.Write(this.buffer, 0, IccOverheadLength); + this.outputStream.Write(data, offset, length); + + current++; + offset += length; } } @@ -774,7 +795,7 @@ namespace ImageSharp.Formats image.MetaData.SyncProfiles(); this.WriteExifProfile(image.MetaData.ExifProfile); - this.WriteICCProfiles(image.MetaData.IccProfiles); + this.WriteIccProfile(image.MetaData.IccProfile); } /// diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index b02dc6270..91ea9ac4b 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -6,7 +6,6 @@ namespace ImageSharp { using System.Collections.Generic; - using System.Linq; using ImageSharp.Formats; /// @@ -61,36 +60,23 @@ namespace ImageSharp this.Properties.Add(new ImageProperty(property)); } - if (other.ExifProfile != null) - { - this.ExifProfile = new ExifProfile(other.ExifProfile); - } - else - { - this.ExifProfile = null; - } + this.ExifProfile = other.ExifProfile != null + ? new ExifProfile(other.ExifProfile) + : null; - if (other.IccProfiles != null && other.IccProfiles.Any()) - { - this.IccProfiles = new List(other.IccProfiles); - } - else - { - this.IccProfiles = new List(); - } + this.IccProfile = other.IccProfile != null + ? new IccProfile(other.IccProfile) + : null; } /// - /// Gets or sets the resolution of the image in x- direction. It is defined as - /// number of dots per inch and should be an positive value. + /// Gets or sets the resolution of the image in x- direction. + /// It is defined as the number of dots per inch and should be an positive value. /// /// The density of the image in x- direction. public double HorizontalResolution { - get - { - return this.horizontalResolution; - } + get => this.horizontalResolution; set { @@ -102,16 +88,13 @@ namespace ImageSharp } /// - /// Gets or sets the resolution of the image in y- direction. It is defined as - /// number of dots per inch and should be an positive value. + /// Gets or sets the resolution of the image in y- direction. + /// It is defined as the number of dots per inch and should be an positive value. /// /// The density of the image in y- direction. public double VerticalResolution { - get - { - return this.verticalResolution; - } + get => this.verticalResolution; set { @@ -130,7 +113,7 @@ namespace ImageSharp /// /// Gets or sets the list of ICC profiles. /// - public IList IccProfiles { get; set; } = new List(); + public IccProfile IccProfile { get; set; } /// public int FrameDelay { get; set; } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs index e00604416..13a15b483 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -7,6 +7,8 @@ namespace ImageSharp { using System.Numerics; + using ImageSharp.Memory; + /// /// Provides methods to write ICC data types /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index 2701ffcb1..978d5bc24 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -5,9 +5,9 @@ namespace ImageSharp { + using System; using System.Collections.Generic; #if !NETSTANDARD1_1 - using System; using System.Security.Cryptography; #endif @@ -19,7 +19,7 @@ namespace ImageSharp /// /// The byte array to read the ICC profile from /// - private readonly byte[] data; + private byte[] data; /// /// The backing file for the property @@ -35,7 +35,7 @@ namespace ImageSharp /// Initializes a new instance of the class. /// public IccProfile() - : this(null) + : this((byte[])null) { } @@ -48,6 +48,20 @@ namespace ImageSharp this.data = data; } + /// + /// Initializes a new instance of the class + /// by making a copy from another ICC profile. + /// + /// The other ICC profile, where the clone should be made from. + /// is null.> + public IccProfile(IccProfile other) + { + Guard.NotNull(other, nameof(other)); + + // TODO: Do we need to copy anything else? + this.data = other.data; + } + /// /// Initializes a new instance of the class. /// @@ -73,10 +87,7 @@ namespace ImageSharp return this.header; } - set - { - this.header = value; - } + set => this.header = value; } /// @@ -106,7 +117,7 @@ namespace ImageSharp byte[] header = new byte[128]; Buffer.BlockCopy(data, 0, header, 0, 128); - using (MD5 md5 = MD5.Create()) + using (var md5 = MD5.Create()) { // Zero out some values Array.Clear(header, 44, 4); // Profile flags @@ -117,20 +128,31 @@ namespace ImageSharp byte[] hash = md5.ComputeHash(data); // Read values from hash - IccDataReader reader = new IccDataReader(hash); + var reader = new IccDataReader(hash); return reader.ReadProfileId(); } } #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); + } + /// /// Converts this instance to a byte array. /// /// The public byte[] ToByteArray() { - IccWriter writer = new IccWriter(); + var writer = new IccWriter(); return writer.Write(this); } @@ -147,7 +169,7 @@ namespace ImageSharp return; } - IccReader reader = new IccReader(); + var reader = new IccReader(); this.header = reader.ReadHeader(this.data); } @@ -164,7 +186,7 @@ namespace ImageSharp return; } - IccReader reader = new IccReader(); + var reader = new IccReader(); this.entries = new List(reader.ReadTagData(this.data)); } } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs index e4f5362b4..259f71489 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -8,6 +8,8 @@ namespace ImageSharp using System; using System.Linq; + using ImageSharp.Memory; + /// /// A matrix element to process data /// diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs index b254f3c2c..61b5d57ff 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs @@ -6,6 +6,9 @@ namespace ImageSharp.Tests.Icc { using System.Numerics; + + using ImageSharp.Memory; + using Xunit; public class IccDataWriterMatrixTests diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs index 0a1be5154..78e493829 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -7,6 +7,8 @@ using System.Numerics; namespace ImageSharp.Tests { + using ImageSharp.Memory; + internal static class IccTestDataMatrix { #region 2D