Browse Source

Can now save some ICC profiles

CMYK Image throws an error when trying to parse/write entries
af/merge-core
James Jackson-South 9 years ago
parent
commit
a7df079d81
  1. 17
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  2. 89
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  3. 22
      src/ImageSharp/MetaData/ImageMetaData.cs
  4. 26
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs
  5. 10
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  6. 12
      src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs

17
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -88,11 +89,6 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private bool isExif; private bool isExif;
/// <summary>
/// Whether the image has an ICC header
/// </summary>
private bool isIcc;
/// <summary> /// <summary>
/// The vertical resolution. Calculated if the image has a JFIF header. /// The vertical resolution. Calculated if the image has a JFIF header.
/// </summary> /// </summary>
@ -997,11 +993,11 @@ namespace ImageSharp.Formats
identifier[10] == 'E' && identifier[10] == 'E' &&
identifier[11] == '\0') identifier[11] == '\0')
{ {
this.isIcc = true;
remaining -= Icclength; remaining -= Icclength;
byte[] profile = new byte[remaining]; byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, 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; remaining -= 13;
// TODO: We should be using constants for this. // 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.isJfif = this.Temp[0] == 'J' &&
&& this.Temp[4] == '\x00'; this.Temp[1] == 'F' &&
this.Temp[2] == 'I' &&
this.Temp[3] == 'F' &&
this.Temp[4] == '\x00';
if (this.isJfif) if (this.isJfif)
{ {

89
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -6,7 +6,9 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ImageSharp.Formats.Jpg; using ImageSharp.Formats.Jpg;
using ImageSharp.Formats.Jpg.Components; using ImageSharp.Formats.Jpg.Components;
@ -109,7 +111,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// A scratch buffer to reduce allocations. /// A scratch buffer to reduce allocations.
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[20];
/// <summary> /// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough. /// 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[12] = 0x01; // versionlo
this.buffer[13] = 0x01; // xyunits as dpi 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 // Resolution. Big Endian
this.buffer[0] = (byte)(horizontalResolution >> 8); this.buffer[14] = (byte)(horizontalResolution >> 8);
this.buffer[1] = (byte)horizontalResolution; this.buffer[15] = (byte)horizontalResolution;
this.buffer[2] = (byte)(verticalResolution >> 8); this.buffer[16] = (byte)(verticalResolution >> 8);
this.buffer[3] = (byte)verticalResolution; 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);
} }
/// <summary> /// <summary>
@ -674,7 +674,7 @@ namespace ImageSharp.Formats
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the EXIF profile size exceeds the limit /// Thrown if the EXIF profile size exceeds the limit
/// </exception> /// </exception>
private void WriteProfile(ExifProfile exifProfile) private void WriteExifProfile(ExifProfile exifProfile)
{ {
const int Max = 65533; const int Max = 65533;
byte[] data = exifProfile?.ToByteArray(); byte[] data = exifProfile?.ToByteArray();
@ -699,6 +699,66 @@ namespace ImageSharp.Formats
this.outputStream.Write(data, 0, data.Length); this.outputStream.Write(data, 0, data.Length);
} }
/// <summary>
/// Writes the ICC profiles.
/// </summary>
/// <param name="iccProfiles">The list of ICC profiles.</param>
/// <exception cref="ImageFormatException">
/// Thrown if any of the ICC profiles size exceeds the limit
/// </exception>
private void WriteICCProfiles(IList<IccProfile> 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);
}
}
/// <summary> /// <summary>
/// Writes the metadata profiles to the image. /// Writes the metadata profiles to the image.
/// </summary> /// </summary>
@ -713,7 +773,8 @@ namespace ImageSharp.Formats
} }
image.MetaData.SyncProfiles(); image.MetaData.SyncProfiles();
this.WriteProfile(image.MetaData.ExifProfile); this.WriteExifProfile(image.MetaData.ExifProfile);
this.WriteICCProfiles(image.MetaData.IccProfiles);
} }
/// <summary> /// <summary>

22
src/ImageSharp/MetaData/ImageMetaData.cs

@ -7,6 +7,7 @@ namespace ImageSharp
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
/// <summary> /// <summary>
/// Encapsulates the metadata of an image. /// Encapsulates the metadata of an image.
@ -67,6 +68,15 @@ namespace ImageSharp
{ {
this.ExifProfile = null; this.ExifProfile = null;
} }
if (other.IccProfiles != null && other.IccProfiles.Any())
{
this.IccProfiles = new List<IccProfile>(other.IccProfiles);
}
else
{
this.ExifProfile = null;
}
} }
/// <summary> /// <summary>
@ -83,10 +93,10 @@ namespace ImageSharp
set set
{ {
if (value > 0) if (value > 0)
{ {
this.horizontalResolution = value; this.horizontalResolution = value;
} }
} }
} }
@ -117,9 +127,9 @@ namespace ImageSharp
public ExifProfile ExifProfile { get; set; } public ExifProfile ExifProfile { get; set; }
/// <summary> /// <summary>
/// Gets or sets the ICC profile. /// Gets or sets the list of ICC profiles.
/// </summary> /// </summary>
public IccProfile IccProfile { get; set; } public IList<IccProfile> IccProfiles { get; set; } = new List<IccProfile>();
/// <summary> /// <summary>
/// Gets or sets the frame delay for animated images. /// Gets or sets the frame delay for animated images.

26
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs

@ -12,7 +12,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Provides methods to write ICC data types /// Provides methods to write ICC data types
/// </summary> /// </summary>
internal sealed partial class IccDataWriter internal sealed partial class IccDataWriter : IDisposable
{ {
private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
@ -22,6 +22,11 @@ namespace ImageSharp
/// </summary> /// </summary>
private readonly MemoryStream dataStream; private readonly MemoryStream dataStream;
/// <summary>
/// To detect redundant calls
/// </summary>
private bool isDisposed = false;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IccDataWriter"/> class. /// Initializes a new instance of the <see cref="IccDataWriter"/> class.
/// </summary> /// </summary>
@ -167,6 +172,12 @@ namespace ImageSharp
return this.WriteEmpty(p >= 4 ? 0 : p); return this.WriteEmpty(p >= 4 ? 0 : p);
} }
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
}
/// <summary> /// <summary>
/// Writes given bytes from pointer /// Writes given bytes from pointer
/// </summary> /// </summary>
@ -228,5 +239,18 @@ namespace ImageSharp
return count; return count;
} }
private void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
this.dataStream?.Dispose();
}
this.isDisposed = true;
}
}
} }
} }

10
src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs

@ -124,6 +124,16 @@ namespace ImageSharp
#endif #endif
/// <summary>
/// Converts this instance to a byte array.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
public byte[] ToByteArray()
{
IccWriter writer = new IccWriter();
return writer.Write(this);
}
private void InitializeHeader() private void InitializeHeader()
{ {
if (this.header != null) if (this.header != null)

12
src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs

@ -22,11 +22,13 @@ namespace ImageSharp
{ {
Guard.NotNull(profile, nameof(profile)); Guard.NotNull(profile, nameof(profile));
IccDataWriter writer = new IccDataWriter(); using (IccDataWriter writer = new IccDataWriter())
IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries); {
this.WriteTagTable(writer, tagTable); IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries);
this.WriteHeader(writer, profile.Header); this.WriteTagTable(writer, tagTable);
return writer.GetData(); this.WriteHeader(writer, profile.Header);
return writer.GetData();
}
} }
private void WriteHeader(IccDataWriter writer, IccProfileHeader header) private void WriteHeader(IccDataWriter writer, IccProfileHeader header)

Loading…
Cancel
Save