Browse Source

Can now read/write all ICC Profiles

af/merge-core
James Jackson-South 9 years ago
parent
commit
b413b6166a
  1. 9
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  2. 61
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  3. 43
      src/ImageSharp/MetaData/ImageMetaData.cs
  4. 2
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
  5. 46
      src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
  6. 2
      src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs
  7. 3
      tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs
  8. 2
      tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs

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

@ -1000,7 +1000,14 @@ namespace ImageSharp.Formats
byte[] profile = new byte[remaining]; byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, 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);
}
} }
} }

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

@ -700,42 +700,60 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// Writes the ICC profiles. /// Writes the ICC profile.
/// </summary> /// </summary>
/// <param name="iccProfiles">The list of ICC profiles.</param> /// <param name="iccProfile">The ICC profile to write.</param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if any of the ICC profiles size exceeds the limit /// Thrown if any of the ICC profiles size exceeds the limit
/// </exception> /// </exception>
private void WriteICCProfiles(IList<IccProfile> iccProfiles) private void WriteIccProfile(IccProfile iccProfile)
{ {
// Just incase someone set the value to null by accident. // Just incase someone set the value to null by accident.
if (iccProfiles == null || !iccProfiles.Any()) if (iccProfile == null)
{ {
return; return;
} }
const int IccOverheadLength = 14;
const int Max = 65533; 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) // Calculate the number of markers we'll need, rounding up of course
{ int dataLength = data.Length;
continue; 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[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker
int length = data.Length + 16; int markerLength = length + 16;
this.buffer[2] = (byte)((length >> 8) & 0xFF); this.buffer[2] = (byte)((markerLength >> 8) & 0xFF);
this.buffer[3] = (byte)(length & 0xFF); this.buffer[3] = (byte)(markerLength & 0xFF);
this.outputStream.Write(this.buffer, 0, 4); this.outputStream.Write(this.buffer, 0, 4);
@ -751,11 +769,14 @@ namespace ImageSharp.Formats
this.buffer[9] = (byte)'L'; this.buffer[9] = (byte)'L';
this.buffer[10] = (byte)'E'; this.buffer[10] = (byte)'E';
this.buffer[11] = 0x00; 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.buffer[13] = (byte)count; // The total number of profiles.
this.outputStream.Write(this.buffer, 0, 14); this.outputStream.Write(this.buffer, 0, IccOverheadLength);
this.outputStream.Write(data, 0, data.Length); this.outputStream.Write(data, offset, length);
current++;
offset += length;
} }
} }
@ -774,7 +795,7 @@ namespace ImageSharp.Formats
image.MetaData.SyncProfiles(); image.MetaData.SyncProfiles();
this.WriteExifProfile(image.MetaData.ExifProfile); this.WriteExifProfile(image.MetaData.ExifProfile);
this.WriteICCProfiles(image.MetaData.IccProfiles); this.WriteIccProfile(image.MetaData.IccProfile);
} }
/// <summary> /// <summary>

43
src/ImageSharp/MetaData/ImageMetaData.cs

@ -6,7 +6,6 @@
namespace ImageSharp namespace ImageSharp
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using ImageSharp.Formats; using ImageSharp.Formats;
/// <summary> /// <summary>
@ -61,36 +60,23 @@ namespace ImageSharp
this.Properties.Add(new ImageProperty(property)); this.Properties.Add(new ImageProperty(property));
} }
if (other.ExifProfile != null) this.ExifProfile = other.ExifProfile != null
{ ? new ExifProfile(other.ExifProfile)
this.ExifProfile = new ExifProfile(other.ExifProfile); : null;
}
else
{
this.ExifProfile = null;
}
if (other.IccProfiles != null && other.IccProfiles.Any()) this.IccProfile = other.IccProfile != null
{ ? new IccProfile(other.IccProfile)
this.IccProfiles = new List<IccProfile>(other.IccProfiles); : null;
}
else
{
this.IccProfiles = new List<IccProfile>();
}
} }
/// <summary> /// <summary>
/// Gets or sets the resolution of the image in x- direction. It is defined as /// Gets or sets the resolution of the image in x- direction.
/// number of dots per inch and should be an positive value. /// It is defined as the number of dots per inch and should be an positive value.
/// </summary> /// </summary>
/// <value>The density of the image in x- direction.</value> /// <value>The density of the image in x- direction.</value>
public double HorizontalResolution public double HorizontalResolution
{ {
get get => this.horizontalResolution;
{
return this.horizontalResolution;
}
set set
{ {
@ -102,16 +88,13 @@ namespace ImageSharp
} }
/// <summary> /// <summary>
/// Gets or sets the resolution of the image in y- direction. It is defined as /// Gets or sets the resolution of the image in y- direction.
/// number of dots per inch and should be an positive value. /// It is defined as the number of dots per inch and should be an positive value.
/// </summary> /// </summary>
/// <value>The density of the image in y- direction.</value> /// <value>The density of the image in y- direction.</value>
public double VerticalResolution public double VerticalResolution
{ {
get get => this.verticalResolution;
{
return this.verticalResolution;
}
set set
{ {
@ -130,7 +113,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Gets or sets the list of ICC profiles. /// Gets or sets the list of ICC profiles.
/// </summary> /// </summary>
public IList<IccProfile> IccProfiles { get; set; } = new List<IccProfile>(); public IccProfile IccProfile { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public int FrameDelay { get; set; } public int FrameDelay { get; set; }

2
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs

@ -7,6 +7,8 @@ namespace ImageSharp
{ {
using System.Numerics; using System.Numerics;
using ImageSharp.Memory;
/// <summary> /// <summary>
/// Provides methods to write ICC data types /// Provides methods to write ICC data types
/// </summary> /// </summary>

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

@ -5,9 +5,9 @@
namespace ImageSharp namespace ImageSharp
{ {
using System;
using System.Collections.Generic; using System.Collections.Generic;
#if !NETSTANDARD1_1 #if !NETSTANDARD1_1
using System;
using System.Security.Cryptography; using System.Security.Cryptography;
#endif #endif
@ -19,7 +19,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// The byte array to read the ICC profile from /// The byte array to read the ICC profile from
/// </summary> /// </summary>
private readonly byte[] data; private byte[] data;
/// <summary> /// <summary>
/// The backing file for the <see cref="Entries"/> property /// The backing file for the <see cref="Entries"/> property
@ -35,7 +35,7 @@ namespace ImageSharp
/// Initializes a new instance of the <see cref="IccProfile"/> class. /// Initializes a new instance of the <see cref="IccProfile"/> class.
/// </summary> /// </summary>
public IccProfile() public IccProfile()
: this(null) : this((byte[])null)
{ {
} }
@ -48,6 +48,20 @@ namespace ImageSharp
this.data = data; this.data = data;
} }
/// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class
/// by making a copy from another ICC profile.
/// </summary>
/// <param name="other">The other ICC profile, where the clone should be made from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="other"/> is null.</exception>>
public IccProfile(IccProfile other)
{
Guard.NotNull(other, nameof(other));
// TODO: Do we need to copy anything else?
this.data = other.data;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="IccProfile"/> class. /// Initializes a new instance of the <see cref="IccProfile"/> class.
/// </summary> /// </summary>
@ -73,10 +87,7 @@ namespace ImageSharp
return this.header; return this.header;
} }
set set => this.header = value;
{
this.header = value;
}
} }
/// <summary> /// <summary>
@ -106,7 +117,7 @@ namespace ImageSharp
byte[] header = new byte[128]; byte[] header = new byte[128];
Buffer.BlockCopy(data, 0, header, 0, 128); Buffer.BlockCopy(data, 0, header, 0, 128);
using (MD5 md5 = MD5.Create()) using (var md5 = MD5.Create())
{ {
// Zero out some values // Zero out some values
Array.Clear(header, 44, 4); // Profile flags Array.Clear(header, 44, 4); // Profile flags
@ -117,20 +128,31 @@ namespace ImageSharp
byte[] hash = md5.ComputeHash(data); byte[] hash = md5.ComputeHash(data);
// Read values from hash // Read values from hash
IccDataReader reader = new IccDataReader(hash); var reader = new IccDataReader(hash);
return reader.ReadProfileId(); return reader.ReadProfileId();
} }
} }
#endif #endif
/// <summary>
/// Extends the profile with additional data.
/// </summary>
/// <param name="bytes">The array containing addition profile data.</param>
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);
}
/// <summary> /// <summary>
/// Converts this instance to a byte array. /// Converts this instance to a byte array.
/// </summary> /// </summary>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
public byte[] ToByteArray() public byte[] ToByteArray()
{ {
IccWriter writer = new IccWriter(); var writer = new IccWriter();
return writer.Write(this); return writer.Write(this);
} }
@ -147,7 +169,7 @@ namespace ImageSharp
return; return;
} }
IccReader reader = new IccReader(); var reader = new IccReader();
this.header = reader.ReadHeader(this.data); this.header = reader.ReadHeader(this.data);
} }
@ -164,7 +186,7 @@ namespace ImageSharp
return; return;
} }
IccReader reader = new IccReader(); var reader = new IccReader();
this.entries = new List<IccTagDataEntry>(reader.ReadTagData(this.data)); this.entries = new List<IccTagDataEntry>(reader.ReadTagData(this.data));
} }
} }

2
src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs

@ -8,6 +8,8 @@ namespace ImageSharp
using System; using System;
using System.Linq; using System.Linq;
using ImageSharp.Memory;
/// <summary> /// <summary>
/// A matrix element to process data /// A matrix element to process data
/// </summary> /// </summary>

3
tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs

@ -6,6 +6,9 @@
namespace ImageSharp.Tests.Icc namespace ImageSharp.Tests.Icc
{ {
using System.Numerics; using System.Numerics;
using ImageSharp.Memory;
using Xunit; using Xunit;
public class IccDataWriterMatrixTests public class IccDataWriterMatrixTests

2
tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs

@ -7,6 +7,8 @@ using System.Numerics;
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using ImageSharp.Memory;
internal static class IccTestDataMatrix internal static class IccTestDataMatrix
{ {
#region 2D #region 2D

Loading…
Cancel
Save