diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 98dc80a07b..55335945bc 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Formats { using System; + using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -88,11 +89,6 @@ namespace ImageSharp.Formats /// private bool isExif; - /// - /// Whether the image has an ICC header - /// - private bool isIcc; - /// /// The vertical resolution. Calculated if the image has a JFIF header. /// @@ -997,11 +993,11 @@ namespace ImageSharp.Formats identifier[10] == 'E' && identifier[11] == '\0') { - this.isIcc = true; remaining -= Icclength; byte[] profile = new byte[remaining]; this.InputProcessor.ReadFull(profile, 0, remaining); - metadata.IccProfile = new IccProfile(profile); + + metadata.IccProfiles.Add(new IccProfile(profile)); } } @@ -1021,8 +1017,11 @@ namespace ImageSharp.Formats remaining -= 13; // TODO: We should be using constants for this. - this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F' - && this.Temp[4] == '\x00'; + this.isJfif = this.Temp[0] == 'J' && + this.Temp[1] == 'F' && + this.Temp[2] == 'I' && + this.Temp[3] == 'F' && + this.Temp[4] == '\x00'; if (this.isJfif) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index 0ce59c6dec..4dddd25ed7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -6,7 +6,9 @@ namespace ImageSharp.Formats { using System.Buffers; + using System.Collections.Generic; using System.IO; + using System.Linq; using System.Runtime.CompilerServices; using ImageSharp.Formats.Jpg; using ImageSharp.Formats.Jpg.Components; @@ -109,7 +111,7 @@ namespace ImageSharp.Formats /// /// A scratch buffer to reduce allocations. /// - private readonly byte[] buffer = new byte[16]; + private readonly byte[] buffer = new byte[20]; /// /// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. @@ -514,19 +516,17 @@ namespace ImageSharp.Formats this.buffer[12] = 0x01; // versionlo this.buffer[13] = 0x01; // xyunits as dpi - // No thumbnail - this.buffer[14] = 0x00; // Thumbnail width - this.buffer[15] = 0x00; // Thumbnail height - - this.outputStream.Write(this.buffer, 0, 16); - // Resolution. Big Endian - this.buffer[0] = (byte)(horizontalResolution >> 8); - this.buffer[1] = (byte)horizontalResolution; - this.buffer[2] = (byte)(verticalResolution >> 8); - this.buffer[3] = (byte)verticalResolution; + this.buffer[14] = (byte)(horizontalResolution >> 8); + this.buffer[15] = (byte)horizontalResolution; + this.buffer[16] = (byte)(verticalResolution >> 8); + this.buffer[17] = (byte)verticalResolution; - this.outputStream.Write(this.buffer, 0, 4); + // No thumbnail + this.buffer[18] = 0x00; // Thumbnail width + this.buffer[19] = 0x00; // Thumbnail height + + this.outputStream.Write(this.buffer, 0, 20); } /// @@ -674,7 +674,7 @@ namespace ImageSharp.Formats /// /// Thrown if the EXIF profile size exceeds the limit /// - private void WriteProfile(ExifProfile exifProfile) + private void WriteExifProfile(ExifProfile exifProfile) { const int Max = 65533; byte[] data = exifProfile?.ToByteArray(); @@ -699,6 +699,66 @@ namespace ImageSharp.Formats this.outputStream.Write(data, 0, data.Length); } + /// + /// Writes the ICC profiles. + /// + /// The list of ICC profiles. + /// + /// Thrown if any of the ICC profiles size exceeds the limit + /// + private void WriteICCProfiles(IList iccProfiles) + { + // Just incase someone set the value to null by accident. + if (iccProfiles == null || !iccProfiles.Any()) + { + return; + } + + const int Max = 65533; + int count = iccProfiles.Count; + + for (int i = 1; i <= count; i++) + { + byte[] data = iccProfiles[i - 1]?.ToByteArray(); + + if (data == null || data.Length == 0) + { + continue; + } + + if (data.Length > Max) + { + throw new ImageFormatException($"ICC profile size exceeds limit. nameof{Max}"); + } + + 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); + + this.outputStream.Write(this.buffer, 0, 4); + + this.buffer[0] = (byte)'I'; + this.buffer[1] = (byte)'C'; + this.buffer[2] = (byte)'C'; + this.buffer[3] = (byte)'_'; + this.buffer[4] = (byte)'P'; + this.buffer[5] = (byte)'R'; + this.buffer[6] = (byte)'O'; + this.buffer[7] = (byte)'F'; + this.buffer[8] = (byte)'I'; + 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[13] = (byte)count; // The total number of profiles. + + this.outputStream.Write(this.buffer, 0, 14); + this.outputStream.Write(data, 0, data.Length); + } + } + /// /// Writes the metadata profiles to the image. /// @@ -713,7 +773,8 @@ namespace ImageSharp.Formats } image.MetaData.SyncProfiles(); - this.WriteProfile(image.MetaData.ExifProfile); + this.WriteExifProfile(image.MetaData.ExifProfile); + this.WriteICCProfiles(image.MetaData.IccProfiles); } /// diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index c8f3ccc6c7..08ccc59fa8 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Collections.Generic; + using System.Linq; /// /// Encapsulates the metadata of an image. @@ -67,6 +68,15 @@ namespace ImageSharp { this.ExifProfile = null; } + + if (other.IccProfiles != null && other.IccProfiles.Any()) + { + this.IccProfiles = new List(other.IccProfiles); + } + else + { + this.ExifProfile = null; + } } /// @@ -83,10 +93,10 @@ namespace ImageSharp set { - if (value > 0) - { - this.horizontalResolution = value; - } + if (value > 0) + { + this.horizontalResolution = value; + } } } @@ -117,9 +127,9 @@ namespace ImageSharp public ExifProfile ExifProfile { get; set; } /// - /// Gets or sets the ICC profile. + /// Gets or sets the list of ICC profiles. /// - public IccProfile IccProfile { get; set; } + public IList IccProfiles { get; set; } = new List(); /// /// Gets or sets the frame delay for animated images. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs index abe91e4817..d515ee7263 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// /// Provides methods to write ICC data types /// - internal sealed partial class IccDataWriter + internal sealed partial class IccDataWriter : IDisposable { private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); @@ -22,6 +22,11 @@ namespace ImageSharp /// private readonly MemoryStream dataStream; + /// + /// To detect redundant calls + /// + private bool isDisposed = false; + /// /// Initializes a new instance of the class. /// @@ -167,6 +172,12 @@ namespace ImageSharp return this.WriteEmpty(p >= 4 ? 0 : p); } + /// + public void Dispose() + { + this.Dispose(true); + } + /// /// Writes given bytes from pointer /// @@ -228,5 +239,18 @@ namespace ImageSharp return count; } + + private void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) + { + this.dataStream?.Dispose(); + } + + this.isDisposed = true; + } + } } } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index b23c8b0689..2701ffcb11 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -124,6 +124,16 @@ namespace ImageSharp #endif + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + IccWriter writer = new IccWriter(); + return writer.Write(this); + } + private void InitializeHeader() { if (this.header != null) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs index 54a1cb21ac..dcf2f056fe 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs @@ -22,11 +22,13 @@ namespace ImageSharp { Guard.NotNull(profile, nameof(profile)); - IccDataWriter writer = new IccDataWriter(); - IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries); - this.WriteTagTable(writer, tagTable); - this.WriteHeader(writer, profile.Header); - return writer.GetData(); + using (IccDataWriter writer = new IccDataWriter()) + { + IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries); + this.WriteTagTable(writer, tagTable); + this.WriteHeader(writer, profile.Header); + return writer.GetData(); + } } private void WriteHeader(IccDataWriter writer, IccProfileHeader header)