diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs
new file mode 100644
index 000000000..17a85106a
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs
@@ -0,0 +1,40 @@
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A segment of a curve
+ ///
+ internal abstract class IccCurveSegment : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The signature of this segment
+ protected IccCurveSegment(IccCurveSegmentSignature signature)
+ {
+ this.Signature = signature;
+ }
+
+ ///
+ /// Gets the signature of this segment
+ ///
+ public IccCurveSegmentSignature Signature { get; }
+
+ ///
+ public virtual bool Equals(IccCurveSegment other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Signature == other.Signature;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs
new file mode 100644
index 000000000..aa33fb776
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs
@@ -0,0 +1,84 @@
+namespace ImageSharp
+{
+ ///
+ /// A formula based curve segment
+ ///
+ internal sealed class IccFormulaCurveElement : IccCurveSegment
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of this segment (0-2)
+ /// Gamma segment parameter
+ /// A segment parameter
+ /// B segment parameter
+ /// C segment parameter
+ /// D segment parameter
+ /// E segment parameter
+ public IccFormulaCurveElement(ushort type, double gamma, double a, double b, double c, double d, double e)
+ : base(IccCurveSegmentSignature.FormulaCurve)
+ {
+ Guard.MustBeBetweenOrEqualTo(type, 0, 2, nameof(type));
+
+ this.Type = type;
+ this.Gamma = gamma;
+ this.A = a;
+ this.B = b;
+ this.C = c;
+ this.D = d;
+ this.E = e;
+ }
+
+ ///
+ /// Gets the type of this curve
+ ///
+ public ushort Type { get; }
+
+ ///
+ /// Gets the gamma curve parameter
+ ///
+ public double Gamma { get; }
+
+ ///
+ /// Gets the A curve parameter
+ ///
+ public double A { get; }
+
+ ///
+ /// Gets the B curve parameter
+ ///
+ public double B { get; }
+
+ ///
+ /// Gets the C curve parameter
+ ///
+ public double C { get; }
+
+ ///
+ /// Gets the D curve parameter
+ ///
+ public double D { get; }
+
+ ///
+ /// Gets the E curve parameter
+ ///
+ public double E { get; }
+
+ ///
+ public override bool Equals(IccCurveSegment other)
+ {
+ if (base.Equals(other) && other is IccFormulaCurveElement segment)
+ {
+ return this.Type == segment.Type
+ && this.Gamma == segment.Gamma
+ && this.A == segment.A
+ && this.B == segment.B
+ && this.C == segment.C
+ && this.D == segment.D
+ && this.E == segment.E;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs
new file mode 100644
index 000000000..ab3867826
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs
@@ -0,0 +1,55 @@
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// A one dimensional curve
+ ///
+ internal sealed class IccOneDimensionalCurve : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The break points of this curve
+ /// The segments of this curve
+ public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments)
+ {
+ Guard.NotNull(breakPoints, nameof(breakPoints));
+ Guard.NotNull(segments, nameof(segments));
+
+ bool isWrongSize = breakPoints.Length != segments.Length - 1;
+ Guard.IsTrue(isWrongSize, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments");
+
+ this.BreakPoints = breakPoints;
+ this.Segments = segments;
+ }
+
+ ///
+ /// Gets the breakpoints that separate two curve segments
+ ///
+ public float[] BreakPoints { get; }
+
+ ///
+ /// Gets an array of curve segments
+ ///
+ public IccCurveSegment[] Segments { get; }
+
+ ///
+ public bool Equals(IccOneDimensionalCurve other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.BreakPoints.SequenceEqual(other.BreakPoints)
+ && this.Segments.SequenceEqual(other.Segments);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs
new file mode 100644
index 000000000..5155737b5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs
@@ -0,0 +1,147 @@
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A parametric curve
+ ///
+ internal sealed class IccParametricCurve : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ public IccParametricCurve(double g)
+ : this(0, g, 0, 0, 0, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ public IccParametricCurve(double g, double a, double b)
+ : this(1, g, a, b, 0, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ /// C curve parameter
+ public IccParametricCurve(double g, double a, double b, double c)
+ : this(2, g, a, b, c, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ /// C curve parameter
+ /// D curve parameter
+ public IccParametricCurve(double g, double a, double b, double c, double d)
+ : this(3, g, a, b, c, d, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ /// C curve parameter
+ /// D curve parameter
+ /// E curve parameter
+ /// F curve parameter
+ public IccParametricCurve(double g, double a, double b, double c, double d, double e, double f)
+ : this(4, g, a, b, c, d, e, f)
+ {
+ }
+
+ private IccParametricCurve(ushort type, double g, double a, double b, double c, double d, double e, double f)
+ {
+ Guard.MustBeBetweenOrEqualTo(type, 0, 4, nameof(type));
+
+ this.Type = type;
+ this.G = g;
+ this.A = a;
+ this.B = b;
+ this.C = c;
+ this.D = d;
+ this.E = e;
+ this.F = f;
+ }
+
+ ///
+ /// Gets the type of this curve
+ ///
+ public ushort Type { get; }
+
+ ///
+ /// Gets the G curve parameter
+ ///
+ public double G { get; }
+
+ ///
+ /// Gets the A curve parameter
+ ///
+ public double A { get; }
+
+ ///
+ /// Gets the B curve parameter
+ ///
+ public double B { get; }
+
+ ///
+ /// Gets the C curve parameter
+ ///
+ public double C { get; }
+
+ ///
+ /// Gets the D curve parameter
+ ///
+ public double D { get; }
+
+ ///
+ /// Gets the E curve parameter
+ ///
+ public double E { get; }
+
+ ///
+ /// Gets the F curve parameter
+ ///
+ public double F { get; }
+
+ ///
+ public bool Equals(IccParametricCurve other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Type == other.Type
+ && this.G == other.G
+ && this.A == other.A
+ && this.B == other.B
+ && this.C == other.C
+ && this.D == other.D
+ && this.E == other.E
+ && this.F == other.F;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs
new file mode 100644
index 000000000..361046b3d
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs
@@ -0,0 +1,82 @@
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// A response curve
+ ///
+ internal sealed class IccResponseCurve : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of this curve
+ /// The XYZ values
+ /// The response arrays
+ public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays)
+ {
+ Guard.NotNull(xyzValues, nameof(xyzValues));
+ Guard.NotNull(responseArrays, nameof(responseArrays));
+
+ Guard.IsTrue(xyzValues.Length != responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length");
+ Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues));
+
+ this.CurveType = curveType;
+ this.XyzValues = xyzValues;
+ this.ResponseArrays = responseArrays;
+ }
+
+ ///
+ /// Gets the type of this curve
+ ///
+ public IccCurveMeasurementEncodings CurveType { get; }
+
+ ///
+ /// Gets the XYZ values
+ ///
+ public Vector3[] XyzValues { get; }
+
+ ///
+ /// Gets the response arrays
+ ///
+ public IccResponseNumber[][] ResponseArrays { get; }
+
+ ///
+ public bool Equals(IccResponseCurve other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.CurveType == other.CurveType
+ && this.XyzValues.SequenceEqual(other.XyzValues)
+ && this.EqualsResponseArray(other);
+ }
+
+ private bool EqualsResponseArray(IccResponseCurve other)
+ {
+ if (this.ResponseArrays.Length != other.ResponseArrays.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.ResponseArrays.Length; i++)
+ {
+ if (!this.ResponseArrays[i].SequenceEqual(other.ResponseArrays[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs
new file mode 100644
index 000000000..9373876fb
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs
@@ -0,0 +1,39 @@
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// A sampled curve segment
+ ///
+ internal sealed class IccSampledCurveElement : IccCurveSegment
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The curve values of this segment
+ public IccSampledCurveElement(float[] curveEntries)
+ : base(IccCurveSegmentSignature.SampledCurve)
+ {
+ Guard.NotNull(curveEntries, nameof(curveEntries));
+ Guard.IsTrue(curveEntries.Length < 1, nameof(curveEntries), "There must be at least one value");
+
+ this.CurveEntries = curveEntries;
+ }
+
+ ///
+ /// Gets the curve values of this segment
+ ///
+ public float[] CurveEntries { get; }
+
+ ///
+ public override bool Equals(IccCurveSegment other)
+ {
+ if (base.Equals(other) && other is IccSampledCurveElement segment)
+ {
+ return this.CurveEntries.SequenceEqual(segment.CurveEntries);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs
new file mode 100644
index 000000000..066cbe848
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Color lookup table data type
+ ///
+ internal enum IccClutDataType
+ {
+ ///
+ /// 32bit floating point
+ ///
+ Float,
+
+ ///
+ /// 8bit unsigned integer (byte)
+ ///
+ UInt8,
+
+ ///
+ /// 16bit unsigned integer (ushort)
+ ///
+ UInt16,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs
new file mode 100644
index 000000000..43af657c5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs
@@ -0,0 +1,138 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Color Space Type
+ ///
+ internal enum IccColorSpaceType : uint
+ {
+ ///
+ /// CIE XYZ
+ ///
+ CieXyz = 0x58595A20, // XYZ
+
+ ///
+ /// CIE Lab
+ ///
+ CieLab = 0x4C616220, // Lab
+
+ ///
+ /// CIE Luv
+ ///
+ CieLuv = 0x4C757620, // Luv
+
+ ///
+ /// YCbCr
+ ///
+ YCbCr = 0x59436272, // YCbr
+
+ ///
+ /// CIE Yxy
+ ///
+ CieYxy = 0x59787920, // Yxy
+
+ ///
+ /// RGB
+ ///
+ Rgb = 0x52474220, // RGB
+
+ ///
+ /// Gray
+ ///
+ Gray = 0x47524159, // GRAY
+
+ ///
+ /// HSV
+ ///
+ Hsv = 0x48535620, // HSV
+
+ ///
+ /// HLS
+ ///
+ Hls = 0x484C5320, // HLS
+
+ ///
+ /// CMYK
+ ///
+ Cmyk = 0x434D594B, // CMYK
+
+ ///
+ /// CMY
+ ///
+ Cmy = 0x434D5920, // CMY
+
+ ///
+ /// Generic 2 channel color
+ ///
+ Color2 = 0x32434C52, // 2CLR
+
+ ///
+ /// Generic 3 channel color
+ ///
+ Color3 = 0x33434C52, // 3CLR
+
+ ///
+ /// Generic 4 channel color
+ ///
+ Color4 = 0x34434C52, // 4CLR
+
+ ///
+ /// Generic 5 channel color
+ ///
+ Color5 = 0x35434C52, // 5CLR
+
+ ///
+ /// Generic 6 channel color
+ ///
+ Color6 = 0x36434C52, // 6CLR
+
+ ///
+ /// Generic 7 channel color
+ ///
+ Color7 = 0x37434C52, // 7CLR
+
+ ///
+ /// Generic 8 channel color
+ ///
+ Color8 = 0x38434C52, // 8CLR
+
+ ///
+ /// Generic 9 channel color
+ ///
+ Color9 = 0x39434C52, // 9CLR
+
+ ///
+ /// Generic 10 channel color
+ ///
+ Color10 = 0x41434C52, // ACLR
+
+ ///
+ /// Generic 11 channel color
+ ///
+ Color11 = 0x42434C52, // BCLR
+
+ ///
+ /// Generic 12 channel color
+ ///
+ Color12 = 0x43434C52, // CCLR
+
+ ///
+ /// Generic 13 channel color
+ ///
+ Color13 = 0x44434C52, // DCLR
+
+ ///
+ /// Generic 14 channel color
+ ///
+ Color14 = 0x45434C52, // ECLR
+
+ ///
+ /// Generic 15 channel color
+ ///
+ Color15 = 0x46434C52, // FCLR
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs
new file mode 100644
index 000000000..56f748fba
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Colorant Encoding
+ ///
+ internal enum IccColorantEncoding : ushort
+ {
+ ///
+ /// Unknown colorant encoding
+ ///
+ Unknown = 0x0000,
+
+ ///
+ /// ITU-R BT.709-2 colorant encoding
+ ///
+ ITU_R_BT_709_2 = 0x0001,
+
+ ///
+ /// SMPTE RP145 colorant encoding
+ ///
+ SMPTE_RP145 = 0x0002,
+
+ ///
+ /// EBU Tech.3213-E colorant encoding
+ ///
+ EBU_Tech_3213_E = 0x0003,
+
+ ///
+ /// P22 colorant encoding
+ ///
+ P22 = 0x0004,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs
new file mode 100644
index 000000000..b1324139f
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Curve Measurement Encodings
+ ///
+ internal enum IccCurveMeasurementEncodings : uint
+ {
+ ///
+ /// ISO 5-3 densitometer response. This is the accepted standard for
+ /// reflection densitometers for measuring photographic color prints
+ ///
+ StatusA = 0x53746141, // StaA
+
+ ///
+ /// ISO 5-3 densitometer response which is the accepted standard in
+ /// Europe for color reflection densitometers
+ ///
+ StatusE = 0x53746145, // StaE
+
+ ///
+ /// ISO 5-3 densitometer response commonly referred to as narrow band
+ /// or interference-type response.
+ ///
+ StatusI = 0x53746149, // StaI
+
+ ///
+ /// ISO 5-3 wide band color reflection densitometer response which is
+ /// the accepted standard in the United States for color reflection densitometers
+ ///
+ StatusT = 0x53746154, // StaT
+
+ ///
+ /// ISO 5-3 densitometer response for measuring color negatives
+ ///
+ StatusM = 0x5374614D, // StaM
+
+ ///
+ /// DIN 16536-2 densitometer response, with no polarizing filter
+ ///
+ DinE = 0x434E2020, // DN
+
+ ///
+ /// DIN 16536-2 densitometer response, with polarizing filter
+ ///
+ DinE_pol = 0x434E2050, // DNP
+
+ ///
+ /// DIN 16536-2 narrow band densitometer response, with no polarizing filter
+ ///
+ DinI = 0x434E4E20, // DNN
+
+ ///
+ /// DIN 16536-2 narrow band densitometer response, with polarizing filter
+ ///
+ DinI_pol = 0x434E4E50, // DNNP
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs
new file mode 100644
index 000000000..77ded0d1b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Curve Segment Signature
+ ///
+ internal enum IccCurveSegmentSignature : uint
+ {
+ ///
+ /// Curve defined by a formula
+ ///
+ FormulaCurve = 0x70617266, // parf
+
+ ///
+ /// Curve defined by multiple segments
+ ///
+ SampledCurve = 0x73616D66, // samf
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs
similarity index 98%
rename from src/ImageSharp/MetaData/Profiles/ICC/IccDataType.cs
rename to src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs
index 429628329..b864e2e6d 100644
--- a/src/ImageSharp/MetaData/Profiles/ICC/IccDataType.cs
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs
@@ -9,7 +9,7 @@ namespace ImageSharp
/// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0
/// Section 4.2 to 4.15
///
- public enum IccDataType
+ internal enum IccDataType
{
///
/// A 12-byte value representation of the time and date
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs
new file mode 100644
index 000000000..0a12dea0b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Device attributes. Can be combined with a logical OR
+ ///
+ [Flags]
+ internal enum IccDeviceAttribute : long
+ {
+ ///
+ /// Opacity transparent
+ ///
+ OpacityTransparent = 1 << 31,
+
+ ///
+ /// Opacity reflective
+ ///
+ OpacityReflective = 0,
+
+ ///
+ /// Reflectivity matte
+ ///
+ ReflectivityMatte = 1 << 30,
+
+ ///
+ /// Reflectivity glossy
+ ///
+ ReflectivityGlossy = 0,
+
+ ///
+ /// Polarity negative
+ ///
+ PolarityNegative = 1 << 29,
+
+ ///
+ /// Polarity positive
+ ///
+ PolarityPositive = 0,
+
+ ///
+ /// Chroma black and white
+ ///
+ ChromaBlackWhite = 1 << 28,
+
+ ///
+ /// Chroma color
+ ///
+ ChromaColor = 0,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs
new file mode 100644
index 000000000..cef2d9206
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Measurement Geometry
+ ///
+ internal enum IccMeasurementGeometry : uint
+ {
+ ///
+ /// Unknown geometry
+ ///
+ Unknown = 0,
+
+ ///
+ /// Geometry of 0°:45° or 45°:0°
+ ///
+ MG_0_45_45_0 = 1,
+
+ ///
+ /// Geometry of 0°:d or d:0°
+ ///
+ MG_0d_d0 = 2,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs
new file mode 100644
index 000000000..8ab690b64
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Multi process element signature
+ ///
+ internal enum IccMultiProcessElementSignature : uint
+ {
+ ///
+ /// Set of curves
+ ///
+ CurveSet = 0x6D666C74, // cvst
+
+ ///
+ /// Matrix transformation
+ ///
+ Matrix = 0x6D617466, // matf
+
+ ///
+ /// Color lookup table
+ ///
+ Clut = 0x636C7574, // clut
+
+ ///
+ /// Reserved for future expansion. Do not use!
+ ///
+ BAcs = 0x62414353, // bACS
+
+ ///
+ /// Reserved for future expansion. Do not use!
+ ///
+ EAcs = 0x65414353, // eACS
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs
new file mode 100644
index 000000000..cd4e555c1
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Enumerates the primary platform/operating system framework for which the profile was created
+ ///
+ internal enum IccPrimaryPlatformType : uint
+ {
+ ///
+ /// No platform identified
+ ///
+ NotIdentified = 0x00000000,
+
+ ///
+ /// Apple Computer, Inc.
+ ///
+ AppleComputerInc = 0x4150504C, // APPL
+
+ ///
+ /// Microsoft Corporation
+ ///
+ MicrosoftCorporation = 0x4D534654, // MSFT
+
+ ///
+ /// Silicon Graphics, Inc.
+ ///
+ SiliconGraphicsInc = 0x53474920, // SGI
+
+ ///
+ /// Sun Microsystems, Inc.
+ ///
+ SunMicrosystemsInc = 0x53554E57, // SUNW
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs
new file mode 100644
index 000000000..bd2f5b1c0
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Profile Class Name
+ ///
+ internal enum IccProfileClass : uint
+ {
+ InputDevice = 0x73636E72, // scnr
+ DisplayDevice = 0x6D6E7472, // mntr
+ OutputDevice = 0x70727472, // prtr
+ DeviceLink = 0x6C696E6B, // link
+ ColorSpace = 0x73706163, // spac
+ Abstract = 0x61627374, // abst
+ NamedColor = 0x6E6D636C, // nmcl
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileConversionMethod.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileConversionMethod.cs
new file mode 100644
index 000000000..c8923b238
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileConversionMethod.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Profile Conversion Method
+ ///
+ internal enum IccProfileConversionMethod
+ {
+ Invalid,
+ D0,
+ D1,
+ D2,
+ D3,
+ A0,
+ A1,
+ A2,
+ ColorTRC,
+ GrayTRC,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs
new file mode 100644
index 000000000..1ad3204f9
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs
@@ -0,0 +1,26 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Profile flags. Can be combined with a logical OR
+ ///
+ [Flags]
+ internal enum IccProfileFlag : int
+ {
+ ///
+ /// Profile is embedded within another file
+ ///
+ Embedded = 1 << 31,
+
+ ///
+ /// Profile cannot be used independently of the embedded colour data
+ ///
+ Independent = 1 << 30,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileTag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs
similarity index 95%
rename from src/ImageSharp/MetaData/Profiles/ICC/IccProfileTag.cs
rename to src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs
index 0075e863f..dbc7dfbdf 100644
--- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileTag.cs
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs
@@ -12,8 +12,13 @@ namespace ImageSharp
/// Each tag value represent the size of the tag in the profile.
///
///
- public enum IccProfileTag
+ internal enum IccProfileTag : uint
{
+ ///
+ /// Unknown tag
+ ///
+ Unknown,
+
///
/// A2B0 - This tag defines a colour transform from Device, Colour Encoding or PCS, to PCS, or a colour transform
/// from Device 1 to Device 2, using lookup table tag element structures
@@ -162,32 +167,32 @@ namespace ImageSharp
DeviceSettings = 0x64657673,
///
- /// D2B0 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// D2B0 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
/// input range, output range and transform, and provides a means to override the AToB0 tag
///
DToB0 = 0x44324230,
///
- /// D2B1 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// D2B1 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
/// input range, output range and transform, and provides a means to override the AToB1 tag
///
DToB1 = 0x44324230,
///
- /// D2B2 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// D2B2 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
/// input range, output range and transform, and provides a means to override the AToB1 tag
///
DToB2 = 0x44324230,
///
- /// D2B3 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// D2B3 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
/// input range, output range and transform, and provides a means to override the AToB1 tag
///
DToB3 = 0x44324230,
///
- /// gamt - This tag provides a table in which PCS values are the input and a single
- /// output value for each input value is the output. If the output value is 0, the PCS colour is in-gamut.
+ /// gamt - This tag provides a table in which PCS values are the input and a single
+ /// output value for each input value is the output. If the output value is 0, the PCS colour is in-gamut.
/// If the output is non-zero, the PCS colour is out-of-gamut
///
Gamut = 0x67616D74,
@@ -204,18 +209,18 @@ namespace ImageSharp
GreenMatrixColumn = 0x6758595A,
///
- /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no
- /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green).
+ /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no
+ /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green).
///
GreenTRC = 0x67545243,
///
- /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square metre as described by the Y channel.
+ /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square metre as described by the Y channel.
///
Luminance = 0x6C756d69,
///
- /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50.
+ /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50.
///
Measurement = 0x6D656173,
@@ -237,19 +242,19 @@ namespace ImageSharp
///
/// ncl2 - This tag contains the named colour information providing a PCS and optional device representation
- /// for a list of named colours.
+ /// for a list of named colours.
///
NamedColor2 = 0x6E636C32,
///
- /// resp - This tag describes the structure containing a description of the device response for which the profile is intended.
+ /// resp - This tag describes the structure containing a description of the device response for which the profile is intended.
///
OutputResponse = 0x72657370,
///
/// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3
///
- PerceptualRenderingIntentGamut = 0x72696730, /* 'rig0' */
+ PerceptualRenderingIntentGamut = 0x72696730,
///
/// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS.
@@ -309,7 +314,7 @@ namespace ImageSharp
Ps2RenderingIntent = 0x70733269,
///
- /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms.
+ /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms.
///
RedMatrixColumn = 0x7258595A,
@@ -345,8 +350,8 @@ namespace ImageSharp
UcrBg = 0x62666420,
///
- /// vued - This tag describes the structure containing invariant and localizable
- /// versions of the viewing conditions.
+ /// vued - This tag describes the structure containing invariant and localizable
+ /// versions of the viewing conditions.
///
ViewingCondDesc = 0x76756564,
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs
new file mode 100644
index 000000000..8ff74d07a
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs
@@ -0,0 +1,18 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Rendering intent
+ ///
+ internal enum IccRenderingIntent : uint
+ {
+ Perceptual = 0,
+ MediaRelativeColorimetric = 1,
+ Saturation = 2,
+ AbsoluteColorimetric = 3,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs
new file mode 100644
index 000000000..9755441ce
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs
@@ -0,0 +1,82 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Signature Name
+ ///
+ internal enum IccSignatureName : uint
+ {
+ ///
+ /// Unknown signature
+ ///
+ Unknown = 0,
+
+ SceneColorimetryEstimates = 0x73636F65, // scoe
+
+ SceneAppearanceEstimates = 0x73617065, // sape
+
+ FocalPlaneColorimetryEstimates = 0x66706365, // fpce
+
+ ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc
+
+ ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc
+
+ PerceptualReferenceMediumGamut = 0x70726D67, // prmg
+
+ FilmScanner = 0x6673636E, // fscn
+
+ DigitalCamera = 0x6463616D, // dcam
+
+ ReflectiveScanner = 0x7273636E, // rscn
+
+ InkJetPrinter = 0x696A6574, // ijet
+
+ ThermalWaxPrinter = 0x74776178, // twax
+
+ ElectrophotographicPrinter = 0x6570686F, // epho
+
+ ElectrostaticPrinter = 0x65737461, // esta
+
+ DyeSublimationPrinter = 0x64737562, // dsub
+
+ PhotographicPaperPrinter = 0x7270686F, // rpho
+
+ FilmWriter = 0x6670726E, // fprn
+
+ VideoMonitor = 0x7669646D, // vidm
+
+ VideoCamera = 0x76696463, // vidc
+
+ ProjectionTelevision = 0x706A7476, // pjtv
+
+ CathodeRayTubeDisplay = 0x43525420, // CRT
+
+ PassiveMatrixDisplay = 0x504D4420, // PMD
+
+ ActiveMatrixDisplay = 0x414D4420, // AMD
+
+ PhotoCD = 0x4B504344, // KPCD
+
+ PhotographicImageSetter = 0x696D6773, // imgs
+
+ Gravure = 0x67726176, // grav
+
+ OffsetLithography = 0x6F666673, // offs
+
+ Silkscreen = 0x73696C6B, // silk
+
+ Flexography = 0x666C6578, // flex
+
+ MotionPictureFilmScanner = 0x6D706673, // mpfs
+
+ MotionPictureFilmRecorder = 0x6D706672, // mpfr
+
+ DigitalMotionPictureCamera = 0x646D7063, // dmpc
+
+ DigitalCinemaProjector = 0x64636A70, // dcpj
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs
new file mode 100644
index 000000000..3526887ed
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Standard Illuminant
+ ///
+ internal enum IccStandardIlluminant : uint
+ {
+ ///
+ /// Unknown illuminant
+ ///
+ Unknown = 0,
+
+ ///
+ /// D50 illuminant
+ ///
+ D50 = 1,
+
+ ///
+ /// D65 illuminant
+ ///
+ D65 = 2,
+
+ ///
+ /// D93 illuminant
+ ///
+ D93 = 3,
+
+ ///
+ /// F2 illuminant
+ ///
+ F2 = 4,
+
+ ///
+ /// D55 illuminant
+ ///
+ D55 = 5,
+
+ ///
+ /// A illuminant
+ ///
+ A = 6,
+
+ ///
+ /// D50 illuminant
+ ///
+ EquiPowerE = 7,
+
+ ///
+ /// F8 illuminant
+ ///
+ F8 = 8,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs
new file mode 100644
index 000000000..bc3c6bb1a
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Standard Observer
+ ///
+ internal enum IccStandardObserver : uint
+ {
+ ///
+ /// Unknown observer
+ ///
+ Unkown = 0,
+
+ ///
+ /// CIE 1931 observer
+ ///
+ CIE1931Observer = 1,
+
+ ///
+ /// CIE 1964 observer
+ ///
+ CIE1964Observer = 2,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs
new file mode 100644
index 000000000..7f7c32d1e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs
@@ -0,0 +1,112 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Type Signature
+ ///
+ internal enum IccTypeSignature : uint
+ {
+ ///
+ /// Unknown type signature
+ ///
+ Unknown,
+
+ Chromaticity = 0x6368726D,
+
+ ColorantOrder = 0x636c726f,
+
+ ColorantTable = 0x636c7274,
+
+ Curve = 0x63757276,
+
+ Data = 0x64617461,
+
+ ///
+ /// Date and time defined by 6 unsigned 16bit integers (year, month, day, hour, minute, second)
+ ///
+ DateTime = 0x6474696D,
+
+ ///
+ /// Lookup table with 16bit unsigned integers (ushort)
+ ///
+ Lut16 = 0x6D667432,
+
+ ///
+ /// Lookup table with 8bit unsigned integers (byte)
+ ///
+ Lut8 = 0x6D667431,
+
+ LutAToB = 0x6D414220,
+
+ LutBToA = 0x6D424120,
+
+ Measurement = 0x6D656173,
+
+ ///
+ /// Unicode text in one or more languages
+ ///
+ MultiLocalizedUnicode = 0x6D6C7563,
+
+ MultiProcessElements = 0x6D706574,
+
+ NamedColor2 = 0x6E636C32,
+
+ ParametricCurve = 0x70617261,
+
+ ProfileSequenceDesc = 0x70736571,
+
+ ProfileSequenceIdentifier = 0x70736964,
+
+ ResponseCurveSet16 = 0x72637332,
+
+ ///
+ /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits
+ ///
+ S15Fixed16Array = 0x73663332,
+
+ Signature = 0x73696720,
+
+ ///
+ /// Simple ASCII text
+ ///
+ Text = 0x74657874,
+
+ ///
+ /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits
+ ///
+ U16Fixed16Array = 0x75663332,
+
+ ///
+ /// Array of unsigned 16bit integers (ushort)
+ ///
+ UInt16Array = 0x75693136,
+
+ ///
+ /// Array of unsigned 32bit integers (uint)
+ ///
+ UInt32Array = 0x75693332,
+
+ ///
+ /// Array of unsigned 64bit integers (ulong)
+ ///
+ UInt64Array = 0x75693634,
+
+ ///
+ /// Array of unsigned 8bit integers (byte)
+ ///
+ UInt8Array = 0x75693038,
+
+ ViewingConditions = 0x76696577,
+
+ ///
+ /// 3 floating point values describing a XYZ color value
+ ///
+ Xyz = 0x58595A20,
+
+ TextDescription = 0x64657363,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
new file mode 100644
index 000000000..54fe7c764
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Represents an error that happened while reading or writing a corrupt/invalid ICC profile
+ ///
+ public class InvalidIccProfileException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InvalidIccProfileException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error
+ public InvalidIccProfileException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public InvalidIccProfileException(string message, Exception inner)
+ : base(message, inner)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs
new file mode 100644
index 000000000..45bb9bd11
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs
@@ -0,0 +1,1727 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Globalization;
+ using System.Numerics;
+ using System.Text;
+ using ImageSharp.IO;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed class IccDataReader
+ {
+ private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
+
+ ///
+ /// The data that is read
+ ///
+ private readonly byte[] data;
+
+ ///
+ /// The current reading position
+ ///
+ private int index;
+
+ private EndianBitConverter converter = new BigEndianBitConverter();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The data to read
+ public IccDataReader(byte[] data)
+ {
+ this.data = data;
+ }
+
+ ///
+ /// Sets the reading position to the given value
+ ///
+ /// The new index position
+ public void SetIndex(int index)
+ {
+ this.index = index.Clamp(0, this.data.Length);
+ }
+
+ #region Read Primitives
+
+ ///
+ /// Reads an ushort
+ ///
+ /// the value
+ public ushort ReadUInt16()
+ {
+ return this.converter.ToUInt16(this.data, this.AddIndex(2));
+ }
+
+ ///
+ /// Reads a short
+ ///
+ /// the value
+ public short ReadInt16()
+ {
+ return this.converter.ToInt16(this.data, this.AddIndex(2));
+ }
+
+ ///
+ /// Reads an uint
+ ///
+ /// the value
+ public uint ReadUInt32()
+ {
+ return this.converter.ToUInt32(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads an int
+ ///
+ /// the value
+ public int ReadInt32()
+ {
+ return this.converter.ToInt32(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads an ulong
+ ///
+ /// the value
+ public ulong ReadUInt64()
+ {
+ return this.converter.ToUInt64(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads a long
+ ///
+ /// the value
+ public long ReadInt64()
+ {
+ return this.converter.ToInt64(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads a float
+ ///
+ /// the value
+ public float ReadSingle()
+ {
+ return this.converter.ToSingle(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads a double
+ ///
+ /// the value
+ public double ReadDouble()
+ {
+ return this.converter.ToDouble(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads an ASCII encoded string
+ ///
+ /// number of bytes to read
+ /// The value as a string
+ public string ReadASCIIString(int length)
+ {
+ // Encoding.ASCII is missing in netstandard1.1, use UTF8 instead because it's compatible with ASCII
+ string value = Encoding.UTF8.GetString(this.data, this.AddIndex(length), length);
+
+ // remove data after (potential) null terminator
+ int pos = value.IndexOf('\0');
+ if (pos >= 0)
+ {
+ value = value.Substring(0, pos);
+ }
+
+ return value;
+ }
+
+ ///
+ /// Reads an UTF-16 big-endian encoded string
+ ///
+ /// number of bytes to read
+ /// The value as a string
+ public string ReadUnicodeString(int length)
+ {
+ return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length);
+ }
+
+ ///
+ /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
+ ///
+ /// The number as double
+ public float ReadFix16()
+ {
+ return this.ReadInt32() / 65536f;
+ }
+
+ ///
+ /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits
+ ///
+ /// The number as double
+ public float ReadUFix16()
+ {
+ return this.ReadUInt32() / 65536f;
+ }
+
+ ///
+ /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits
+ ///
+ /// The number as double
+ public float ReadU1Fix15()
+ {
+ return this.ReadUInt16() / 32768f;
+ }
+
+ ///
+ /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits
+ ///
+ /// The number as double
+ public float ReadUFix8()
+ {
+ return this.ReadUInt16() / 256f;
+ }
+
+ ///
+ /// Reads a 16bit value ignoring endianness
+ ///
+ /// the value
+ public short ReadDirect16()
+ {
+ return BitConverter.ToInt16(this.data, this.AddIndex(2));
+ }
+
+ ///
+ /// Reads a 32bit value ignoring endianness
+ ///
+ /// the value
+ public int ReadDirect32()
+ {
+ return BitConverter.ToInt32(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads a 64bit value ignoring endianness
+ ///
+ /// the value
+ public long ReadDirect64()
+ {
+ return BitConverter.ToInt64(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads a number of bytes and advances the index
+ ///
+ /// The number of bytes to read
+ /// The read bytes
+ public byte[] ReadBytes(int count)
+ {
+ byte[] bytes = new byte[count];
+ Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count);
+ return bytes;
+ }
+
+ #endregion
+
+ #region Read Non-Primitives
+
+ ///
+ /// Reads a DateTime
+ ///
+ /// the value
+ public DateTime ReadDateTime()
+ {
+ try
+ {
+ return new DateTime(
+ year: this.ReadUInt16(),
+ month: this.ReadUInt16(),
+ day: this.ReadUInt16(),
+ hour: this.ReadUInt16(),
+ minute: this.ReadUInt16(),
+ second: this.ReadUInt16(),
+ kind: DateTimeKind.Utc);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ return DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// Reads an ICC profile version number
+ ///
+ /// the version number
+ public Version ReadVersionNumber()
+ {
+ int version = this.ReadDirect32();
+
+ int major = version >> 24;
+ int minor = (version >> 20) & 0x0F;
+ int bugfix = (version >> 16) & 0x0F;
+
+ return new Version(major, minor, bugfix);
+ }
+
+ ///
+ /// Reads an XYZ number
+ ///
+ /// the XYZ number
+ public Vector3 ReadXyzNumber()
+ {
+ return new Vector3(
+ x: this.ReadFix16(),
+ y: this.ReadFix16(),
+ z: this.ReadFix16());
+ }
+
+ ///
+ /// Reads a profile ID
+ ///
+ /// the profile ID
+ public IccProfileId ReadProfileId()
+ {
+ return new IccProfileId(
+ p1: this.ReadUInt32(),
+ p2: this.ReadUInt32(),
+ p3: this.ReadUInt32(),
+ p4: this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a position number
+ ///
+ /// the position number
+ public IccPositionNumber ReadPositionNumber()
+ {
+ return new IccPositionNumber(
+ offset: this.ReadUInt32(),
+ size: this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a response number
+ ///
+ /// the response number
+ public IccResponseNumber ReadResponseNumber()
+ {
+ return new IccResponseNumber(
+ deviceCode: this.ReadUInt16(),
+ measurementValue: this.ReadFix16());
+ }
+
+ ///
+ /// Reads a named color
+ ///
+ /// Number of device coordinates
+ /// the named color
+ public IccNamedColor ReadNamedColor(uint deviceCoordCount)
+ {
+ string name = this.ReadASCIIString(32);
+ ushort[] pcsCoord = new ushort[3] { this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16() };
+ ushort[] deviceCoord = new ushort[deviceCoordCount];
+
+ for (int i = 0; i < deviceCoordCount; i++)
+ {
+ deviceCoord[i] = this.ReadUInt16();
+ }
+
+ return new IccNamedColor(name, pcsCoord, deviceCoord);
+ }
+
+ ///
+ /// Reads a profile description
+ ///
+ /// the profile description
+ public IccProfileDescription ReadProfileDescription()
+ {
+ uint manufacturer = this.ReadUInt32();
+ uint model = this.ReadUInt32();
+ IccDeviceAttribute attributes = (IccDeviceAttribute)this.ReadDirect64();
+ IccProfileTag technologyInfo = (IccProfileTag)this.ReadUInt32();
+ this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
+ IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = this.ReadMultiLocalizedUnicodeTagDataEntry();
+ this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
+ IccMultiLocalizedUnicodeTagDataEntry modelInfo = this.ReadMultiLocalizedUnicodeTagDataEntry();
+
+ return new IccProfileDescription(
+ manufacturer,
+ model,
+ attributes,
+ technologyInfo,
+ manufacturerInfo.Texts,
+ modelInfo.Texts);
+ }
+
+ ///
+ /// Reads a colorant table entry
+ ///
+ /// the profile description
+ public IccColorantTableEntry ReadColorantTableEntry()
+ {
+ return new IccColorantTableEntry(
+ name: this.ReadASCIIString(32),
+ pcs1: this.ReadUInt16(),
+ pcs2: this.ReadUInt16(),
+ pcs3: this.ReadUInt16());
+ }
+
+ #endregion
+
+ #region Read Tag Data Entries
+
+ ///
+ /// Reads a tag data entry
+ ///
+ /// The table entry with reading information
+ /// the tag data entry
+ public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
+ {
+ this.index = (int)info.Offset;
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+
+ switch (type)
+ {
+ case IccTypeSignature.Chromaticity:
+ return this.ReadChromaticityTagDataEntry();
+ case IccTypeSignature.ColorantOrder:
+ return this.ReadColorantOrderTagDataEntry();
+ case IccTypeSignature.ColorantTable:
+ return this.ReadColorantTableTagDataEntry();
+ case IccTypeSignature.Curve:
+ return this.ReadCurveTagDataEntry();
+ case IccTypeSignature.Data:
+ return this.ReadDataTagDataEntry(info.DataSize);
+ case IccTypeSignature.DateTime:
+ return this.ReadDateTimeTagDataEntry();
+ case IccTypeSignature.Lut16:
+ return this.ReadLut16TagDataEntry();
+ case IccTypeSignature.Lut8:
+ return this.ReadLut8TagDataEntry();
+ case IccTypeSignature.LutAToB:
+ return this.ReadLutAToBTagDataEntry();
+ case IccTypeSignature.LutBToA:
+ return this.ReadLutBToATagDataEntry();
+ case IccTypeSignature.Measurement:
+ return this.ReadMeasurementTagDataEntry();
+ case IccTypeSignature.MultiLocalizedUnicode:
+ return this.ReadMultiLocalizedUnicodeTagDataEntry();
+ case IccTypeSignature.MultiProcessElements:
+ return this.ReadMultiProcessElementsTagDataEntry();
+ case IccTypeSignature.NamedColor2:
+ return this.ReadNamedColor2TagDataEntry();
+ case IccTypeSignature.ParametricCurve:
+ return this.ReadParametricCurveTagDataEntry();
+ case IccTypeSignature.ProfileSequenceDesc:
+ return this.ReadProfileSequenceDescTagDataEntry();
+ case IccTypeSignature.ProfileSequenceIdentifier:
+ return this.ReadProfileSequenceIdentifierTagDataEntry();
+ case IccTypeSignature.ResponseCurveSet16:
+ return this.ReadResponseCurveSet16TagDataEntry();
+ case IccTypeSignature.S15Fixed16Array:
+ return this.ReadFix16ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.Signature:
+ return this.ReadSignatureTagDataEntry();
+ case IccTypeSignature.Text:
+ return this.ReadTextTagDataEntry(info.DataSize);
+ case IccTypeSignature.U16Fixed16Array:
+ return this.ReadUFix16ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt16Array:
+ return this.ReadUInt16ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt32Array:
+ return this.ReadUInt32ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt64Array:
+ return this.ReadUInt64ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt8Array:
+ return this.ReadUInt8ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.ViewingConditions:
+ return this.ReadViewingConditionsTagDataEntry(info.DataSize);
+ case IccTypeSignature.Xyz:
+ return this.ReadXyzTagDataEntry(info.DataSize);
+
+ // V2 Type:
+ case IccTypeSignature.TextDescription:
+ return this.ReadTextDescriptionTagDataEntry();
+
+ case IccTypeSignature.Unknown:
+ default:
+ return this.ReadUnknownTagDataEntry(info.DataSize);
+ }
+ }
+
+ ///
+ /// Reads the header of a
+ ///
+ /// The read signature
+ public IccTypeSignature ReadTagDataEntryHeader()
+ {
+ IccTypeSignature type = (IccTypeSignature)this.ReadUInt32();
+ this.AddIndex(4); // 4 bytes are not used
+ return type;
+ }
+
+ ///
+ /// Reads the header of a and checks if it's the expected value
+ ///
+ /// expected value to check against
+ public void ReadCheckTagDataEntryHeader(IccTypeSignature expected)
+ {
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+ if (expected != (IccTypeSignature)uint.MaxValue && type != expected)
+ {
+ throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}");
+ }
+ }
+
+ ///
+ /// Reads a with an unknown
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
+ {
+ int count = (int)size - 8; // 8 is the tag header size
+ return new IccUnknownTagDataEntry(this.ReadBytes(count));
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
+ {
+ ushort channelCount = this.ReadUInt16();
+ IccColorantEncoding colorant = (IccColorantEncoding)this.ReadUInt16();
+
+ if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown)
+ {
+ // The type is known and so are the values (they are constant)
+ // channelCount should always be 3 but it doesn't really matter if it's not
+ return new IccChromaticityTagDataEntry(colorant);
+ }
+ else
+ {
+ // The type is not know, so the values need be read
+ double[][] values = new double[channelCount][];
+ for (int i = 0; i < channelCount; i++)
+ {
+ values[i] = new double[2];
+ values[i][0] = this.ReadUFix16();
+ values[i][1] = this.ReadUFix16();
+ }
+
+ return new IccChromaticityTagDataEntry(values);
+ }
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
+ {
+ uint colorantCount = this.ReadUInt32();
+ byte[] number = this.ReadBytes((int)colorantCount);
+ return new IccColorantOrderTagDataEntry(number);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry()
+ {
+ uint colorantCount = this.ReadUInt32();
+ IccColorantTableEntry[] cdata = new IccColorantTableEntry[colorantCount];
+ for (int i = 0; i < colorantCount; i++)
+ {
+ cdata[i] = this.ReadColorantTableEntry();
+ }
+
+ return new IccColorantTableTagDataEntry(cdata);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccCurveTagDataEntry ReadCurveTagDataEntry()
+ {
+ uint pointCount = this.ReadUInt32();
+
+ if (pointCount == 0)
+ {
+ return new IccCurveTagDataEntry();
+ }
+ else if (pointCount == 1)
+ {
+ return new IccCurveTagDataEntry(this.ReadUFix8());
+ }
+ else
+ {
+ float[] cdata = new float[pointCount];
+ for (int i = 0; i < pointCount; i++)
+ {
+ cdata[i] = this.ReadUInt16() / 65535f;
+ }
+
+ return new IccCurveTagDataEntry(cdata);
+ }
+
+ // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768).
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccDataTagDataEntry ReadDataTagDataEntry(uint size)
+ {
+ this.AddIndex(3); // first 3 bytes are zero
+ byte b = this.data[this.AddIndex(1)];
+
+ // last bit of 4th byte is either 0 = ASCII or 1 = binary
+ bool ascii = this.GetBit(b, 7);
+ int length = (int)size - 12;
+ byte[] cdata = this.ReadBytes(length);
+
+ return new IccDataTagDataEntry(cdata, ascii);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry()
+ {
+ return new IccDateTimeTagDataEntry(this.ReadDateTime());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLut16TagDataEntry ReadLut16TagDataEntry()
+ {
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ byte clutPointCount = this.data[this.AddIndex(1)];
+ this.AddIndex(1); // 1 byte reserved
+
+ float[,] matrix = this.ReadMatrix(3, 3, false);
+
+ ushort inTableCount = this.ReadUInt16();
+ ushort outTableCount = this.ReadUInt16();
+
+ // Input LUT
+ IccLut[] inValues = new IccLut[inChCount];
+ byte[] gridPointCount = new byte[inChCount];
+ for (int i = 0; i < inChCount; i++)
+ {
+ inValues[i] = this.ReadLUT16(inTableCount);
+ gridPointCount[i] = clutPointCount;
+ }
+
+ // CLUT
+ IccClut clut = this.ReadCLUT16(inChCount, outChCount, gridPointCount);
+
+ // Output LUT
+ IccLut[] outValues = new IccLut[outChCount];
+ for (int i = 0; i < outChCount; i++)
+ {
+ outValues[i] = this.ReadLUT16(outTableCount);
+ }
+
+ return new IccLut16TagDataEntry(matrix, inValues, clut, outValues);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLut8TagDataEntry ReadLut8TagDataEntry()
+ {
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ byte clutPointCount = this.data[this.AddIndex(1)];
+ this.AddIndex(1); // 1 byte reserved
+
+ float[,] matrix = this.ReadMatrix(3, 3, false);
+
+ // Input LUT
+ IccLut[] inValues = new IccLut[inChCount];
+ byte[] gridPointCount = new byte[inChCount];
+ for (int i = 0; i < inChCount; i++)
+ {
+ inValues[i] = this.ReadLUT8();
+ gridPointCount[i] = clutPointCount;
+ }
+
+ // CLUT
+ IccClut clut = this.ReadCLUT8(inChCount, outChCount, gridPointCount);
+
+ // Output LUT
+ IccLut[] outValues = new IccLut[outChCount];
+ for (int i = 0; i < outChCount; i++)
+ {
+ outValues[i] = this.ReadLUT8();
+ }
+
+ return new IccLut8TagDataEntry(matrix, inValues, clut, outValues);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLutAToBTagDataEntry ReadLutAToBTagDataEntry()
+ {
+ int start = this.index - 8; // 8 is the tag header size
+
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ this.AddIndex(2); // 2 bytes reserved
+
+ uint bCurveOffset = this.ReadUInt32();
+ uint matrixOffset = this.ReadUInt32();
+ uint mCurveOffset = this.ReadUInt32();
+ uint clutOffset = this.ReadUInt32();
+ uint aCurveOffset = this.ReadUInt32();
+
+ IccTagDataEntry[] bCurve = null;
+ IccTagDataEntry[] mCurve = null;
+ IccTagDataEntry[] aCurve = null;
+ IccClut clut = null;
+ float[,] matrix3x3 = null;
+ float[] matrix3x1 = null;
+
+ if (bCurveOffset != 0)
+ {
+ this.index = (int)bCurveOffset + start;
+ bCurve = this.ReadCurves(outChCount);
+ }
+
+ if (mCurveOffset != 0)
+ {
+ this.index = (int)mCurveOffset + start;
+ mCurve = this.ReadCurves(outChCount);
+ }
+
+ if (aCurveOffset != 0)
+ {
+ this.index = (int)aCurveOffset + start;
+ aCurve = this.ReadCurves(inChCount);
+ }
+
+ if (clutOffset != 0)
+ {
+ this.index = (int)clutOffset + start;
+ clut = this.ReadCLUT(inChCount, outChCount, false);
+ }
+
+ if (matrixOffset != 0)
+ {
+ this.index = (int)matrixOffset + start;
+ matrix3x3 = this.ReadMatrix(3, 3, false);
+ matrix3x1 = this.ReadMatrix(3, false);
+ }
+
+ return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLutBToATagDataEntry ReadLutBToATagDataEntry()
+ {
+ int start = this.index - 8; // 8 is the tag header size
+
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ this.AddIndex(2); // 2 bytes reserved
+
+ uint bCurveOffset = this.ReadUInt32();
+ uint matrixOffset = this.ReadUInt32();
+ uint mCurveOffset = this.ReadUInt32();
+ uint clutOffset = this.ReadUInt32();
+ uint aCurveOffset = this.ReadUInt32();
+
+ IccTagDataEntry[] bCurve = null;
+ IccTagDataEntry[] mCurve = null;
+ IccTagDataEntry[] aCurve = null;
+ IccClut clut = null;
+ float[,] matrix3x3 = null;
+ float[] matrix3x1 = null;
+
+ if (bCurveOffset != 0)
+ {
+ this.index = (int)bCurveOffset + start;
+ bCurve = this.ReadCurves(inChCount);
+ }
+
+ if (mCurveOffset != 0)
+ {
+ this.index = (int)mCurveOffset + start;
+ mCurve = this.ReadCurves(inChCount);
+ }
+
+ if (aCurveOffset != 0)
+ {
+ this.index = (int)aCurveOffset + start;
+ aCurve = this.ReadCurves(outChCount);
+ }
+
+ if (clutOffset != 0)
+ {
+ this.index = (int)clutOffset + start;
+ clut = this.ReadCLUT(inChCount, outChCount, false);
+ }
+
+ if (matrixOffset != 0)
+ {
+ this.index = (int)matrixOffset + start;
+ matrix3x3 = this.ReadMatrix(3, 3, false);
+ matrix3x1 = this.ReadMatrix(3, false);
+ }
+
+ return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry()
+ {
+ return new IccMeasurementTagDataEntry(
+ observer: (IccStandardObserver)this.ReadUInt32(),
+ xyzBacking: this.ReadXyzNumber(),
+ geometry: (IccMeasurementGeometry)this.ReadUInt32(),
+ flare: this.ReadUFix16(),
+ illuminant: (IccStandardIlluminant)this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry()
+ {
+ int start = this.index - 8; // 8 is the tag header size
+ uint recordCount = this.ReadUInt32();
+ uint recordSize = this.ReadUInt32();
+ IccLocalizedString[] text = new IccLocalizedString[recordCount];
+
+ string[] culture = new string[recordCount];
+ uint[] length = new uint[recordCount];
+ uint[] offset = new uint[recordCount];
+
+ for (int i = 0; i < recordCount; i++)
+ {
+ culture[i] = $"{this.ReadASCIIString(2)}-{this.ReadASCIIString(2)}";
+ length[i] = this.ReadUInt32();
+ offset[i] = this.ReadUInt32();
+ }
+
+ for (int i = 0; i < recordCount; i++)
+ {
+ this.index = (int)(start + offset[i]);
+ text[i] = new IccLocalizedString(new CultureInfo(culture[i]), this.ReadUnicodeString((int)length[i]));
+ }
+
+ return new IccMultiLocalizedUnicodeTagDataEntry(text);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry()
+ {
+ int start = this.index - 8;
+
+ ushort inChannelCount = this.ReadUInt16();
+ ushort outChannelCount = this.ReadUInt16();
+ uint elementCount = this.ReadUInt32();
+
+ IccPositionNumber[] positionTable = new IccPositionNumber[elementCount];
+ for (int i = 0; i < elementCount; i++)
+ {
+ positionTable[i] = this.ReadPositionNumber();
+ }
+
+ IccMultiProcessElement[] elements = new IccMultiProcessElement[elementCount];
+ for (int i = 0; i < elementCount; i++)
+ {
+ this.index = (int)positionTable[i].Offset + start;
+ elements[i] = this.ReadMultiProcessElement();
+ }
+
+ return new IccMultiProcessElementsTagDataEntry(elements);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry()
+ {
+ int vendorFlag = this.ReadDirect32();
+ uint colorCount = this.ReadUInt32();
+ uint coordCount = this.ReadUInt32();
+ string prefix = this.ReadASCIIString(32);
+ string suffix = this.ReadASCIIString(32);
+
+ IccNamedColor[] colors = new IccNamedColor[colorCount];
+ for (int i = 0; i < colorCount; i++)
+ {
+ colors[i] = this.ReadNamedColor(coordCount);
+ }
+
+ return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry()
+ {
+ return new IccParametricCurveTagDataEntry(this.ReadParametricCurve());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry()
+ {
+ uint count = this.ReadUInt32();
+ IccProfileDescription[] description = new IccProfileDescription[count];
+ for (int i = 0; i < count; i++)
+ {
+ description[i] = this.ReadProfileDescription();
+ }
+
+ return new IccProfileSequenceDescTagDataEntry(description);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry()
+ {
+ int start = this.index - 8; // 8 is the tag header size
+ uint count = this.ReadUInt32();
+ IccPositionNumber[] table = new IccPositionNumber[count];
+ for (int i = 0; i < count; i++)
+ {
+ table[i] = this.ReadPositionNumber();
+ }
+
+ IccProfileSequenceIdentifier[] entries = new IccProfileSequenceIdentifier[count];
+ for (int i = 0; i < count; i++)
+ {
+ this.index = (int)(start + table[i].Offset);
+ IccProfileId id = this.ReadProfileId();
+ this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
+ IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry();
+ entries[i] = new IccProfileSequenceIdentifier(id, description.Texts);
+ }
+
+ return new IccProfileSequenceIdentifierTagDataEntry(entries);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry()
+ {
+ int start = this.index - 8; // 8 is the tag header size
+ ushort channelCount = this.ReadUInt16();
+ ushort measurmentCount = this.ReadUInt16();
+
+ uint[] offset = new uint[measurmentCount];
+ for (int i = 0; i < measurmentCount; i++)
+ {
+ offset[i] = this.ReadUInt32();
+ }
+
+ IccResponseCurve[] curves = new IccResponseCurve[measurmentCount];
+ for (int i = 0; i < measurmentCount; i++)
+ {
+ this.index = (int)(start + offset[i]);
+ curves[i] = this.ReadResponseCurve(channelCount);
+ }
+
+ return new IccResponseCurveSet16TagDataEntry(curves);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 4;
+ float[] arrayData = new float[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadFix16() / 256f;
+ }
+
+ return new IccFix16ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccSignatureTagDataEntry ReadSignatureTagDataEntry()
+ {
+ return new IccSignatureTagDataEntry(this.ReadASCIIString(4));
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccTextTagDataEntry ReadTextTagDataEntry(uint size)
+ {
+ return new IccTextTagDataEntry(this.ReadASCIIString((int)size - 8)); // 8 is the tag header size
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 4;
+ float[] arrayData = new float[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUFix16();
+ }
+
+ return new IccUFix16ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 2;
+ ushort[] arrayData = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUInt16();
+ }
+
+ return new IccUInt16ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 4;
+ uint[] arrayData = new uint[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUInt32();
+ }
+
+ return new IccUInt32ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 8;
+ ulong[] arrayData = new ulong[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUInt64();
+ }
+
+ return new IccUInt64ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size)
+ {
+ int count = (int)size - 8; // 8 is the tag header size
+ byte[] adata = this.ReadBytes(count);
+
+ return new IccUInt8ArrayTagDataEntry(adata);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry(uint size)
+ {
+ return new IccViewingConditionsTagDataEntry(
+ illuminantXyz: this.ReadXyzNumber(),
+ surroundXyz: this.ReadXyzNumber(),
+ illuminant: (IccStandardIlluminant)this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 12;
+ Vector3[] arrayData = new Vector3[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadXyzNumber();
+ }
+
+ return new IccXyzTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry()
+ {
+ string asciiValue, unicodeValue, scriptcodeValue;
+ asciiValue = unicodeValue = scriptcodeValue = null;
+
+ int asciiCount = (int)this.ReadUInt32();
+ if (asciiCount > 0)
+ {
+ asciiValue = this.ReadASCIIString(asciiCount - 1);
+ this.AddIndex(1); // Null terminator
+ }
+
+ uint unicodeLangCode = this.ReadUInt32();
+ int unicodeCount = (int)this.ReadUInt32();
+ if (unicodeCount > 0)
+ {
+ unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2);
+ this.AddIndex(2); // Null terminator
+ }
+
+ ushort scriptcodeCode = this.ReadUInt16();
+ int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67);
+ if (scriptcodeCount > 0)
+ {
+ scriptcodeValue = this.ReadASCIIString(scriptcodeCount - 1);
+ this.AddIndex(1); // Null terminator
+ }
+
+ return new IccTextDescriptionTagDataEntry(
+ asciiValue,
+ unicodeValue,
+ scriptcodeValue,
+ unicodeLangCode,
+ scriptcodeCode);
+ }
+
+ #endregion
+
+ #region Read Matrix
+
+ ///
+ /// Reads a two dimensional matrix
+ ///
+ /// Number of values in X
+ /// Number of values in Y
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The read matrix
+ public float[,] ReadMatrix(int xCount, int yCount, bool isSingle)
+ {
+ float[,] matrix = new float[xCount, yCount];
+ for (int y = 0; y < yCount; y++)
+ {
+ for (int x = 0; x < xCount; x++)
+ {
+ if (isSingle)
+ {
+ matrix[x, y] = this.ReadSingle();
+ }
+ else
+ {
+ matrix[x, y] = this.ReadFix16();
+ }
+ }
+ }
+
+ return matrix;
+ }
+
+ ///
+ /// Reads a one dimensional matrix
+ ///
+ /// Number of values
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The read matrix
+ public float[] ReadMatrix(int yCount, bool isSingle)
+ {
+ float[] matrix = new float[yCount];
+ for (int i = 0; i < yCount; i++)
+ {
+ if (isSingle)
+ {
+ matrix[i] = this.ReadSingle();
+ }
+ else
+ {
+ matrix[i] = this.ReadFix16();
+ }
+ }
+
+ return matrix;
+ }
+
+ #endregion
+
+ #region Read (C)LUT
+
+ ///
+ /// Reads an 8bit lookup table
+ ///
+ /// The read LUT
+ public IccLut ReadLUT8()
+ {
+ return new IccLut(this.ReadBytes(256));
+ }
+
+ ///
+ /// Reads a 16bit lookup table
+ ///
+ /// The number of entries
+ /// The read LUT
+ public IccLut ReadLUT16(int count)
+ {
+ ushort[] values = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ values[i] = this.ReadUInt16();
+ }
+
+ return new IccLut(values);
+ }
+
+ ///
+ /// Reads a CLUT depending on type
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// If true, it's read as CLUTf32,
+ /// else read as either CLUT8 or CLUT16 depending on embedded information
+ /// The read CLUT
+ public IccClut ReadCLUT(int inChannelCount, int outChannelCount, bool isFloat)
+ {
+ // Grid-points are always 16 bytes long but only 0-inChCount are used
+ byte[] gridPointCount = new byte[inChannelCount];
+ Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount);
+
+ if (!isFloat)
+ {
+ byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved
+ if (size == 1)
+ {
+ return this.ReadCLUT8(inChannelCount, outChannelCount, gridPointCount);
+ }
+ else if (size == 2)
+ {
+ return this.ReadCLUT16(inChannelCount, outChannelCount, gridPointCount);
+ }
+ else
+ {
+ throw new InvalidIccProfileException($"Invalid CLUT size of {size}");
+ }
+ }
+ else
+ {
+ return this.ReadCLUTf32(inChannelCount, outChannelCount, gridPointCount);
+ }
+ }
+
+ ///
+ /// Reads an 8 bit CLUT
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// Grid point count for each CLUT channel
+ /// The read CLUT8
+ public IccClut ReadCLUT8(int inChannelCount, int outChannelCount, byte[] gridPointCount)
+ {
+ int start = this.index;
+ int length = 0;
+ for (int i = 0; i < inChannelCount; i++)
+ {
+ length += (int)Math.Pow(gridPointCount[i], inChannelCount);
+ }
+
+ length /= inChannelCount;
+
+ const float max = byte.MaxValue;
+
+ float[][] values = new float[length][];
+ for (int i = 0; i < length; i++)
+ {
+ values[i] = new float[outChannelCount];
+ for (int j = 0; j < outChannelCount; j++)
+ {
+ values[i][j] = this.data[this.index++] / max;
+ }
+ }
+
+ this.index = start + (length * outChannelCount);
+ return new IccClut(values, gridPointCount, IccClutDataType.UInt8);
+ }
+
+ ///
+ /// Reads a 16 bit CLUT
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// Grid point count for each CLUT channel
+ /// The read CLUT16
+ public IccClut ReadCLUT16(int inChannelCount, int outChannelCount, byte[] gridPointCount)
+ {
+ int start = this.index;
+ int length = 0;
+ for (int i = 0; i < inChannelCount; i++)
+ {
+ length += (int)Math.Pow(gridPointCount[i], inChannelCount);
+ }
+
+ length /= inChannelCount;
+
+ const float max = ushort.MaxValue;
+
+ float[][] values = new float[length][];
+ for (int i = 0; i < length; i++)
+ {
+ values[i] = new float[outChannelCount];
+ for (int j = 0; j < outChannelCount; j++)
+ {
+ values[i][j] = this.ReadUInt16() / max;
+ }
+ }
+
+ this.index = start + (length * outChannelCount * 2);
+ return new IccClut(values, gridPointCount, IccClutDataType.UInt16);
+ }
+
+ ///
+ /// Reads a 32bit floating point CLUT
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// Grid point count for each CLUT channel
+ /// The read CLUTf32
+ public IccClut ReadCLUTf32(int inChCount, int outChCount, byte[] gridPointCount)
+ {
+ int start = this.index;
+ int length = 0;
+ for (int i = 0; i < inChCount; i++)
+ {
+ length += (int)Math.Pow(gridPointCount[i], inChCount);
+ }
+
+ length /= inChCount;
+
+ float[][] values = new float[length][];
+ for (int i = 0; i < length; i++)
+ {
+ values[i] = new float[outChCount];
+ for (int j = 0; j < outChCount; j++)
+ {
+ values[i][j] = this.ReadSingle();
+ }
+ }
+
+ this.index = start + (length * outChCount * 4);
+ return new IccClut(values, gridPointCount, IccClutDataType.Float);
+ }
+
+ #endregion
+
+ #region Read MultiProcessElement
+
+ ///
+ /// Reads a
+ ///
+ /// The read
+ public IccMultiProcessElement ReadMultiProcessElement()
+ {
+ IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32();
+ ushort inChannelCount = this.ReadUInt16();
+ ushort outChannelCount = this.ReadUInt16();
+
+ switch (signature)
+ {
+ case IccMultiProcessElementSignature.CurveSet:
+ return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount);
+ case IccMultiProcessElementSignature.Matrix:
+ return this.ReadMatrixProcessElement(inChannelCount, outChannelCount);
+ case IccMultiProcessElementSignature.Clut:
+ return this.ReadCLUTProcessElement(inChannelCount, outChannelCount);
+
+ // Currently just placeholders for future ICC expansion
+ case IccMultiProcessElementSignature.BAcs:
+ this.AddIndex(8);
+ return new IccBAcsProcessElement(inChannelCount, outChannelCount);
+ case IccMultiProcessElementSignature.EAcs:
+ this.AddIndex(8);
+ return new IccEAcsProcessElement(inChannelCount, outChannelCount);
+
+ default:
+ throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}");
+ }
+ }
+
+ ///
+ /// Reads a CurveSet
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ /// The read
+ public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount)
+ {
+ IccOneDimensionalCurve[] curves = new IccOneDimensionalCurve[inChannelCount];
+ for (int i = 0; i < inChannelCount; i++)
+ {
+ curves[i] = this.ReadOneDimensionalCurve();
+ this.AddPadding();
+ }
+
+ return new IccCurveSetProcessElement(curves);
+ }
+
+ ///
+ /// Reads a Matrix
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ /// The read
+ public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount)
+ {
+ return new IccMatrixProcessElement(
+ this.ReadMatrix(inChannelCount, outChannelCount, true),
+ this.ReadMatrix(outChannelCount, true));
+ }
+
+ ///
+ /// Reads a CLUT
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ /// The read
+ public IccClutProcessElement ReadCLUTProcessElement(int inChCount, int outChCount)
+ {
+ return new IccClutProcessElement(this.ReadCLUT(inChCount, outChCount, true));
+ }
+
+ #endregion
+
+ #region Read Curves
+
+ ///
+ /// Reads a
+ ///
+ /// The read curve
+ public IccOneDimensionalCurve ReadOneDimensionalCurve()
+ {
+ ushort segmentCount = this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ float[] breakPoints = new float[segmentCount - 1];
+ for (int i = 0; i < breakPoints.Length; i++)
+ {
+ breakPoints[i] = this.ReadSingle();
+ }
+
+ IccCurveSegment[] segments = new IccCurveSegment[segmentCount];
+ for (int i = 0; i < segmentCount; i++)
+ {
+ segments[i] = this.ReadCurveSegment();
+ }
+
+ return new IccOneDimensionalCurve(breakPoints, segments);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The number of channels
+ /// The read curve
+ public IccResponseCurve ReadResponseCurve(int channelCount)
+ {
+ IccCurveMeasurementEncodings type = (IccCurveMeasurementEncodings)this.ReadUInt32();
+ uint[] measurment = new uint[channelCount];
+ for (int i = 0; i < channelCount; i++)
+ {
+ measurment[i] = this.ReadUInt32();
+ }
+
+ Vector3[] xyzValues = new Vector3[channelCount];
+ for (int i = 0; i < channelCount; i++)
+ {
+ xyzValues[i] = this.ReadXyzNumber();
+ }
+
+ IccResponseNumber[][] response = new IccResponseNumber[channelCount][];
+ for (int i = 0; i < channelCount; i++)
+ {
+ response[i] = new IccResponseNumber[measurment[i]];
+ for (uint j = 0; j < measurment[i]; j++)
+ {
+ response[i][j] = this.ReadResponseNumber();
+ }
+ }
+
+ return new IccResponseCurve(type, xyzValues, response);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read curve
+ public IccParametricCurve ReadParametricCurve()
+ {
+ ushort type = this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ double gamma, a, b, c, d, e, f;
+ gamma = a = b = c = d = e = f = 0;
+
+ if (type >= 0 && type <= 4)
+ {
+ gamma = this.ReadFix16();
+ }
+
+ if (type > 0 && type <= 4)
+ {
+ a = this.ReadFix16();
+ b = this.ReadFix16();
+ }
+
+ if (type > 1 && type <= 4)
+ {
+ c = this.ReadFix16();
+ }
+
+ if (type > 2 && type <= 4)
+ {
+ d = this.ReadFix16();
+ }
+
+ if (type == 4)
+ {
+ e = this.ReadFix16();
+ f = this.ReadFix16();
+ }
+
+ switch (type)
+ {
+ case 0: return new IccParametricCurve(gamma);
+ case 1: return new IccParametricCurve(gamma, a, b);
+ case 2: return new IccParametricCurve(gamma, a, b, c);
+ case 3: return new IccParametricCurve(gamma, a, b, c, d);
+ case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f);
+ default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}");
+ }
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read segment
+ public IccCurveSegment ReadCurveSegment()
+ {
+ IccCurveSegmentSignature signature = (IccCurveSegmentSignature)this.ReadUInt32();
+ this.AddIndex(4); // 4 bytes reserved
+
+ switch (signature)
+ {
+ case IccCurveSegmentSignature.FormulaCurve:
+ return this.ReadFormulaCurveElement();
+ case IccCurveSegmentSignature.SampledCurve:
+ return this.ReadSampledCurveElement();
+ default:
+ throw new InvalidIccProfileException($"Invalid curve segment type of {signature}");
+ }
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read segment
+ public IccFormulaCurveElement ReadFormulaCurveElement()
+ {
+ ushort type = this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ double gamma, a, b, c, d, e;
+ gamma = a = b = c = d = e = 0;
+
+ if (type == 0 || type == 1)
+ {
+ gamma = this.ReadSingle();
+ }
+
+ if (type >= 0 && type <= 2)
+ {
+ a = this.ReadSingle();
+ b = this.ReadSingle();
+ c = this.ReadSingle();
+ }
+
+ if (type == 1 || type == 2)
+ {
+ d = this.ReadSingle();
+ }
+
+ if (type == 2)
+ {
+ e = this.ReadSingle();
+ }
+
+ return new IccFormulaCurveElement(type, gamma, a, b, c, d, e);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read segment
+ public IccSampledCurveElement ReadSampledCurveElement()
+ {
+ uint count = this.ReadUInt32();
+ float[] entries = new float[count];
+ for (int i = 0; i < count; i++)
+ {
+ entries[i] = this.ReadSingle();
+ }
+
+ return new IccSampledCurveElement(entries);
+ }
+
+ ///
+ /// Reads curve data
+ ///
+ /// Number of input channels
+ /// The curve data
+ private IccTagDataEntry[] ReadCurves(int count)
+ {
+ IccTagDataEntry[] tdata = new IccTagDataEntry[count];
+ for (int i = 0; i < count; i++)
+ {
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+ if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve)
+ {
+ throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" +
+ $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries");
+ }
+
+ if (type == IccTypeSignature.Curve)
+ {
+ tdata[i] = this.ReadCurveTagDataEntry();
+ }
+ else if (type == IccTypeSignature.ParametricCurve)
+ {
+ tdata[i] = this.ReadParametricCurveTagDataEntry();
+ }
+
+ this.AddPadding();
+ }
+
+ return tdata;
+ }
+
+ #endregion
+
+ #region Subroutines
+
+ ///
+ /// Returns the current without increment and adds the given increment
+ ///
+ /// The value to increment
+ /// The current without the increment
+ private int AddIndex(int increment)
+ {
+ int tmp = this.index;
+ this.index += increment;
+ return tmp;
+ }
+
+ ///
+ /// Calculates the 4 byte padding and adds it to the variable
+ ///
+ private void AddPadding()
+ {
+ this.index += this.CalcPadding();
+ }
+
+ ///
+ /// Calculates the 4 byte padding
+ ///
+ /// the number of bytes to pad
+ private int CalcPadding()
+ {
+ int p = 4 - (this.index % 4);
+ return p >= 4 ? 0 : p;
+ }
+
+ ///
+ /// Gets the bit value at a specified position
+ ///
+ /// The value from where the bit will be extracted
+ /// Position of the bit. Zero based index from left to right.
+ /// The bit value at specified position
+ private bool GetBit(byte value, int position)
+ {
+ return ((value >> (7 - position)) & 1) == 1;
+ }
+
+ ///
+ /// Gets the bit value at a specified position
+ ///
+ /// The value from where the bit will be extracted
+ /// Position of the bit. Zero based index from left to right.
+ /// The bit value at specified position
+ private bool GetBit(ushort value, int position)
+ {
+ return ((value >> (15 - position)) & 1) == 1;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs
new file mode 100644
index 000000000..b394d8213
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs
@@ -0,0 +1,2003 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.IO;
+ using System.Numerics;
+ using System.Text;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed class IccDataWriter
+ {
+ private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
+
+ private static readonly double[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
+
+ ///
+ /// The underlying stream where the data is written to
+ ///
+ private readonly MemoryStream dataStream;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IccDataWriter()
+ {
+ this.dataStream = new MemoryStream();
+ }
+
+ ///
+ /// Gets the currently written length in bytes
+ ///
+ public uint Length
+ {
+ get { return (uint)this.dataStream.Length; }
+ }
+
+ ///
+ /// Gets the written data bytes
+ ///
+ /// The written data
+ public byte[] GetData()
+ {
+ return this.dataStream.ToArray();
+ }
+
+ ///
+ /// Sets the writing position to the given value
+ ///
+ /// The new index position
+ public void SetIndex(int index)
+ {
+ this.dataStream.Position = index;
+ }
+
+ #region Write Primitives
+
+ ///
+ /// Writes a byte
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteByte(byte value)
+ {
+ this.dataStream.WriteByte(value);
+ return 1;
+ }
+
+ ///
+ /// Writes an ushort
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteUInt16(ushort value)
+ {
+ return this.WriteBytes((byte*)&value, 2);
+ }
+
+ ///
+ /// Writes a short
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteInt16(short value)
+ {
+ return this.WriteBytes((byte*)&value, 2);
+ }
+
+ ///
+ /// Writes an uint
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteUInt32(uint value)
+ {
+ return this.WriteBytes((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes an int
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteInt32(int value)
+ {
+ return this.WriteBytes((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes an ulong
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteUInt64(ulong value)
+ {
+ return this.WriteBytes((byte*)&value, 8);
+ }
+
+ ///
+ /// Writes a long
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteInt64(long value)
+ {
+ return this.WriteBytes((byte*)&value, 8);
+ }
+
+ ///
+ /// Writes a float
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteSingle(float value)
+ {
+ return this.WriteBytes((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes a double
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteDouble(double value)
+ {
+ return this.WriteBytes((byte*)&value, 8);
+ }
+
+ ///
+ /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteFix16(double value)
+ {
+ const double max = short.MaxValue + (65535d / 65536d);
+ const double min = short.MinValue;
+
+ value = value.Clamp(min, max);
+ value *= 65536d;
+
+ return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteUFix16(double value)
+ {
+ const double max = ushort.MaxValue + (65535d / 65536d);
+ const double min = ushort.MinValue;
+
+ value = value.Clamp(min, max);
+ value *= 65536d;
+
+ return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteU1Fix15(double value)
+ {
+ const double max = 1 + (32767d / 32768d);
+ const double min = 0;
+
+ value = value.Clamp(min, max);
+ value *= 32768d;
+
+ return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteUFix8(double value)
+ {
+ const double max = byte.MaxValue + (255d / 256d);
+ const double min = byte.MinValue;
+
+ value = value.Clamp(min, max);
+ value *= 256d;
+
+ return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an ASCII encoded string
+ ///
+ /// the string to write
+ /// the number of bytes written
+ public int WriteASCIIString(string value)
+ {
+ // Encoding.ASCII is missing in netstandard1.1, use UTF8 instead because it's compatible with ASCII
+ byte[] data = Encoding.UTF8.GetBytes(value);
+ this.dataStream.Write(data, 0, data.Length);
+ return data.Length;
+ }
+
+ ///
+ /// Writes an ASCII encoded string resizes it to the given length
+ ///
+ /// The string to write
+ /// The desired length of the string including 1 padding character
+ /// The character to pad to the given length
+ /// the number of bytes written
+ public int WriteASCIIString(string value, int length, char paddingChar)
+ {
+ value = value.Substring(0, Math.Min(length - 1, value.Length));
+
+ // Encoding.ASCII is missing in netstandard1.1, use UTF8 instead because it's compatible with ASCII
+ byte[] textData = Encoding.UTF8.GetBytes(value);
+ int actualLength = Math.Min(length - 1, textData.Length);
+ this.dataStream.Write(textData, 0, actualLength);
+ for (int i = 0; i < length - actualLength; i++)
+ {
+ this.dataStream.WriteByte((byte)paddingChar);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes an UTF-16 big-endian encoded string
+ ///
+ /// the string to write
+ /// the number of bytes written
+ public int WriteUnicodeString(string value)
+ {
+ byte[] data = Encoding.BigEndianUnicode.GetBytes(value);
+ this.dataStream.Write(data, 0, data.Length);
+ return data.Length;
+ }
+
+ ///
+ /// Writes a short ignoring endianness
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteDirect16(short value)
+ {
+ return this.WriteBytesDirect((byte*)&value, 2);
+ }
+
+ ///
+ /// Writes an int ignoring endianness
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteDirect32(int value)
+ {
+ return this.WriteBytesDirect((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes a long ignoring endianness
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteDirect64(long value)
+ {
+ return this.WriteBytesDirect((byte*)&value, 8);
+ }
+
+ #endregion
+
+ #region Write Non-Primitives
+
+ ///
+ /// Writes a DateTime
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteDateTime(DateTime value)
+ {
+ return this.WriteUInt16((ushort)value.Year)
+ + this.WriteUInt16((ushort)value.Month)
+ + this.WriteUInt16((ushort)value.Day)
+ + this.WriteUInt16((ushort)value.Hour)
+ + this.WriteUInt16((ushort)value.Minute)
+ + this.WriteUInt16((ushort)value.Second);
+ }
+
+ ///
+ /// Writes an ICC profile version number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteVersionNumber(Version value)
+ {
+ int major = value.Major.Clamp(0, byte.MaxValue);
+ int minor = value.Minor.Clamp(0, 15);
+ int bugfix = value.Build.Clamp(0, 15);
+ byte mb = (byte)((minor << 4) | bugfix);
+
+ int version = (major << 24) | (minor << 20) | (bugfix << 16);
+ return this.WriteInt32(version);
+ }
+
+ ///
+ /// Writes an XYZ number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteXYZNumber(Vector3 value)
+ {
+ return this.WriteFix16(value.X)
+ + this.WriteFix16(value.Y)
+ + this.WriteFix16(value.Z);
+ }
+
+ ///
+ /// Writes a profile ID
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteProfileId(IccProfileId value)
+ {
+ return this.WriteUInt32(value.Part1)
+ + this.WriteUInt32(value.Part2)
+ + this.WriteUInt32(value.Part3)
+ + this.WriteUInt32(value.Part4);
+ }
+
+ ///
+ /// Writes a position number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WritePositionNumber(IccPositionNumber value)
+ {
+ return this.WriteUInt32(value.Offset)
+ + this.WriteUInt32(value.Size);
+ }
+
+ ///
+ /// Writes a response number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteResponseNumber(IccResponseNumber value)
+ {
+ return this.WriteUInt16(value.DeviceCode)
+ + this.WriteFix16(value.MeasurementValue);
+ }
+
+ ///
+ /// Writes a named color
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteNamedColor(IccNamedColor value)
+ {
+ return this.WriteASCIIString(value.Name, 32, '\0')
+ + this.WriteArray(value.PcsCoordinates)
+ + this.WriteArray(value.DeviceCoordinates);
+ }
+
+ ///
+ /// Writes a profile description
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteProfileDescription(IccProfileDescription value)
+ {
+ return this.WriteUInt32(value.DeviceManufacturer)
+ + this.WriteUInt32(value.DeviceModel)
+ + this.WriteDirect64((long)value.DeviceAttributes)
+ + this.WriteUInt32((uint)value.TechnologyInformation)
+ + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode)
+ + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo))
+ + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode)
+ + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo));
+ }
+
+ #endregion
+
+ #region Write Tag Data Entries
+
+ ///
+ /// Writes a tag data entry
+ ///
+ /// The entry to write
+ /// The table entry for the written data entry
+ /// The number of bytes written (excluding padding)
+ public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table)
+ {
+ uint offset = (uint)this.dataStream.Position;
+ int count = this.WriteTagDataEntry(data);
+ this.WritePadding();
+ table = new IccTagTableEntry(data.TagSignature, offset, (uint)count);
+ return count;
+ }
+
+ ///
+ /// Writes a tag data entry (without padding)
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteTagDataEntry(IccTagDataEntry entry)
+ {
+ int count = this.WriteTagDataEntryHeader(entry.Signature);
+
+ switch (entry.Signature)
+ {
+ case IccTypeSignature.Chromaticity:
+ count += this.WriteChromaticityTagDataEntry(entry as IccChromaticityTagDataEntry);
+ break;
+ case IccTypeSignature.ColorantOrder:
+ count += this.WriteColorantOrderTagDataEntry(entry as IccColorantOrderTagDataEntry);
+ break;
+ case IccTypeSignature.ColorantTable:
+ count += this.WriteColorantTableTagDataEntry(entry as IccColorantTableTagDataEntry);
+ break;
+ case IccTypeSignature.Curve:
+ count += this.WriteCurveTagDataEntry(entry as IccCurveTagDataEntry);
+ break;
+ case IccTypeSignature.Data:
+ count += this.WriteDataTagDataEntry(entry as IccDataTagDataEntry);
+ break;
+ case IccTypeSignature.DateTime:
+ count += this.WriteDateTimeTagDataEntry(entry as IccDateTimeTagDataEntry);
+ break;
+ case IccTypeSignature.Lut16:
+ count += this.WriteLut16TagDataEntry(entry as IccLut16TagDataEntry);
+ break;
+ case IccTypeSignature.Lut8:
+ count += this.WriteLut8TagDataEntry(entry as IccLut8TagDataEntry);
+ break;
+ case IccTypeSignature.LutAToB:
+ count += this.WriteLutAToBTagDataEntry(entry as IccLutAToBTagDataEntry);
+ break;
+ case IccTypeSignature.LutBToA:
+ count += this.WriteLutBToATagDataEntry(entry as IccLutBToATagDataEntry);
+ break;
+ case IccTypeSignature.Measurement:
+ count += this.WriteMeasurementTagDataEntry(entry as IccMeasurementTagDataEntry);
+ break;
+ case IccTypeSignature.MultiLocalizedUnicode:
+ count += this.WriteMultiLocalizedUnicodeTagDataEntry(entry as IccMultiLocalizedUnicodeTagDataEntry);
+ break;
+ case IccTypeSignature.MultiProcessElements:
+ count += this.WriteMultiProcessElementsTagDataEntry(entry as IccMultiProcessElementsTagDataEntry);
+ break;
+ case IccTypeSignature.NamedColor2:
+ count += this.WriteNamedColor2TagDataEntry(entry as IccNamedColor2TagDataEntry);
+ break;
+ case IccTypeSignature.ParametricCurve:
+ count += this.WriteParametricCurveTagDataEntry(entry as IccParametricCurveTagDataEntry);
+ break;
+ case IccTypeSignature.ProfileSequenceDesc:
+ count += this.WriteProfileSequenceDescTagDataEntry(entry as IccProfileSequenceDescTagDataEntry);
+ break;
+ case IccTypeSignature.ProfileSequenceIdentifier:
+ count += this.WriteProfileSequenceIdentifierTagDataEntry(entry as IccProfileSequenceIdentifierTagDataEntry);
+ break;
+ case IccTypeSignature.ResponseCurveSet16:
+ count += this.WriteResponseCurveSet16TagDataEntry(entry as IccResponseCurveSet16TagDataEntry);
+ break;
+ case IccTypeSignature.S15Fixed16Array:
+ count += this.WriteFix16ArrayTagDataEntry(entry as IccFix16ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.Signature:
+ count += this.WriteSignatureTagDataEntry(entry as IccSignatureTagDataEntry);
+ break;
+ case IccTypeSignature.Text:
+ count += this.WriteTextTagDataEntry(entry as IccTextTagDataEntry);
+ break;
+ case IccTypeSignature.U16Fixed16Array:
+ count += this.WriteUFix16ArrayTagDataEntry(entry as IccUFix16ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt16Array:
+ count += this.WriteUInt16ArrayTagDataEntry(entry as IccUInt16ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt32Array:
+ count += this.WriteUInt32ArrayTagDataEntry(entry as IccUInt32ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt64Array:
+ count += this.WriteUInt64ArrayTagDataEntry(entry as IccUInt64ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt8Array:
+ count += this.WriteUInt8ArrayTagDataEntry(entry as IccUInt8ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.ViewingConditions:
+ count += this.WriteViewingConditionsTagDataEntry(entry as IccViewingConditionsTagDataEntry);
+ break;
+ case IccTypeSignature.Xyz:
+ count += this.WriteXyzTagDataEntry(entry as IccXyzTagDataEntry);
+ break;
+
+ // V2 Type:
+ case IccTypeSignature.TextDescription:
+ count += this.WriteTextDescriptionTagDataEntry(entry as IccTextDescriptionTagDataEntry);
+ break;
+
+ case IccTypeSignature.Unknown:
+ default:
+ count += this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry);
+ break;
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes the header of a
+ ///
+ /// The signature of the entry
+ /// The number of bytes written
+ public int WriteTagDataEntryHeader(IccTypeSignature signature)
+ {
+ return this.WriteUInt32((uint)signature)
+ + this.WriteEmpty(4);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value)
+ {
+ int count = this.WriteUInt16((ushort)value.ChannelCount);
+ count += this.WriteUInt16((ushort)value.ColorantType);
+
+ for (int i = 0; i < value.ChannelCount; i++)
+ {
+ count += this.WriteUFix16(value.ChannelValues[i][0]);
+ count += this.WriteUFix16(value.ChannelValues[i][1]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value)
+ {
+ return this.WriteUInt32((uint)value.ColorantNumber.Length)
+ + this.WriteArray(value.ColorantNumber);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value)
+ {
+ int count = this.WriteUInt32((uint)value.ColorantData.Length);
+ foreach (IccColorantTableEntry colorant in value.ColorantData)
+ {
+ count += this.WriteASCIIString(colorant.Name, 32, '\0');
+ count += this.WriteUInt16(colorant.Pcs1);
+ count += this.WriteUInt16(colorant.Pcs2);
+ count += this.WriteUInt16(colorant.Pcs3);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteCurveTagDataEntry(IccCurveTagDataEntry value)
+ {
+ int count = 0;
+
+ if (value.IsIdentityResponse)
+ {
+ count += this.WriteUInt32(0);
+ }
+ else if (value.IsGamma)
+ {
+ count += this.WriteUInt32(1);
+ count += this.WriteUFix8(value.Gamma);
+ }
+ else
+ {
+ count += this.WriteUInt32((uint)value.CurveData.Length);
+ for (int i = 0; i < value.CurveData.Length; i++)
+ {
+ count += this.WriteUInt16((ushort)((value.CurveData[i] * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
+ }
+ }
+
+ return count;
+
+ // TODO: Page 48: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768).
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteDataTagDataEntry(IccDataTagDataEntry value)
+ {
+ return this.WriteEmpty(3)
+ + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00))
+ + this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value)
+ {
+ return this.WriteDateTime(value.Value);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLut16TagDataEntry(IccLut16TagDataEntry value)
+ {
+ int count = this.WriteByte((byte)value.InputValues.Length);
+ count += this.WriteByte((byte)value.OutputValues.Length);
+ count += this.WriteByte(value.ClutValues.GridPointCount[0]);
+ count += this.WriteEmpty(1);
+
+ count += this.WriteMatrix(value.Matrix, false);
+
+ count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length);
+ count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length);
+
+ foreach (IccLut lut in value.InputValues)
+ {
+ count += this.WriteLUT16(lut);
+ }
+
+ count += this.WriteCLUT16(value.ClutValues);
+
+ foreach (IccLut lut in value.OutputValues)
+ {
+ count += this.WriteLUT16(lut);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLut8TagDataEntry(IccLut8TagDataEntry value)
+ {
+ int count = this.WriteByte((byte)value.InputChannelCount);
+ count += this.WriteByte((byte)value.OutputChannelCount);
+ count += this.WriteByte((byte)value.ClutValues.Values[0].Length);
+ count += this.WriteEmpty(1);
+
+ count += this.WriteMatrix(value.Matrix, false);
+
+ foreach (IccLut lut in value.InputValues)
+ {
+ count += this.WriteLUT8(lut);
+ }
+
+ count += this.WriteCLUT8(value.ClutValues);
+
+ foreach (IccLut lut in value.OutputValues)
+ {
+ count += this.WriteLUT8(lut);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLutAToBTagDataEntry(IccLutAToBTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int count = this.WriteByte((byte)value.InputChannelCount);
+ count += this.WriteByte((byte)value.OutputChannelCount);
+ count += this.WriteEmpty(2);
+
+ long bCurveOffset = 0;
+ long matrixOffset = 0;
+ long mCurveOffset = 0;
+ long clutOffset = 0;
+ long aCurveOffset = 0;
+
+ // Jump over offset values
+ long offsetpos = this.dataStream.Position;
+ this.dataStream.Position += 5 * 4;
+
+ if (value.CurveB != null)
+ {
+ bCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveB);
+ count += this.WritePadding();
+ }
+
+ if (value.Matrix3x1 != null && value.Matrix3x3 != null)
+ {
+ matrixOffset = this.dataStream.Position;
+ count += this.WriteMatrix(value.Matrix3x3.Value, false);
+ count += this.WriteMatrix(value.Matrix3x1.Value, false);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveM != null)
+ {
+ mCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveM);
+ count += this.WritePadding();
+ }
+
+ if (value.ClutValues != null)
+ {
+ clutOffset = this.dataStream.Position;
+ count += this.WriteCLUT(value.ClutValues);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveA != null)
+ {
+ aCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveA);
+ count += this.WritePadding();
+ }
+
+ // Set offset values
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = offsetpos;
+
+ if (bCurveOffset != 0)
+ {
+ bCurveOffset -= start;
+ }
+
+ if (matrixOffset != 0)
+ {
+ matrixOffset -= start;
+ }
+
+ if (mCurveOffset != 0)
+ {
+ mCurveOffset -= start;
+ }
+
+ if (clutOffset != 0)
+ {
+ clutOffset -= start;
+ }
+
+ if (aCurveOffset != 0)
+ {
+ aCurveOffset -= start;
+ }
+
+ count += this.WriteUInt32((uint)bCurveOffset);
+ count += this.WriteUInt32((uint)matrixOffset);
+ count += this.WriteUInt32((uint)mCurveOffset);
+ count += this.WriteUInt32((uint)clutOffset);
+ count += this.WriteUInt32((uint)aCurveOffset);
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLutBToATagDataEntry(IccLutBToATagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int count = this.WriteByte((byte)value.InputChannelCount);
+ count += this.WriteByte((byte)value.OutputChannelCount);
+ count += this.WriteEmpty(2);
+
+ long bCurveOffset = 0;
+ long matrixOffset = 0;
+ long mCurveOffset = 0;
+ long clutOffset = 0;
+ long aCurveOffset = 0;
+
+ // Jump over offset values
+ long offsetpos = this.dataStream.Position;
+ this.dataStream.Position += 5 * 4;
+
+ if (value.CurveB != null)
+ {
+ bCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveB);
+ count += this.WritePadding();
+ }
+
+ if (value.Matrix3x1 != null && value.Matrix3x3 != null)
+ {
+ matrixOffset = this.dataStream.Position;
+ count += this.WriteMatrix(value.Matrix3x3.Value, false);
+ count += this.WriteMatrix(value.Matrix3x1.Value, false);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveM != null)
+ {
+ mCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveM);
+ count += this.WritePadding();
+ }
+
+ if (value.ClutValues != null)
+ {
+ clutOffset = this.dataStream.Position;
+ count += this.WriteCLUT(value.ClutValues);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveA != null)
+ {
+ aCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveA);
+ count += this.WritePadding();
+ }
+
+ // Set offset values
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = offsetpos;
+
+ if (bCurveOffset != 0)
+ {
+ bCurveOffset -= start;
+ }
+
+ if (matrixOffset != 0)
+ {
+ matrixOffset -= start;
+ }
+
+ if (mCurveOffset != 0)
+ {
+ mCurveOffset -= start;
+ }
+
+ if (clutOffset != 0)
+ {
+ clutOffset -= start;
+ }
+
+ if (aCurveOffset != 0)
+ {
+ aCurveOffset -= start;
+ }
+
+ count += this.WriteUInt32((uint)bCurveOffset);
+ count += this.WriteUInt32((uint)matrixOffset);
+ count += this.WriteUInt32((uint)mCurveOffset);
+ count += this.WriteUInt32((uint)clutOffset);
+ count += this.WriteUInt32((uint)aCurveOffset);
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value)
+ {
+ return this.WriteUInt32((uint)value.Observer)
+ + this.WriteXYZNumber(value.XyzBacking)
+ + this.WriteUInt32((uint)value.Geometry)
+ + this.WriteUFix16(value.Flare)
+ + this.WriteUInt32((uint)value.Illuminant);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int cultureCount = value.Texts.Length;
+
+ int count = this.WriteUInt32((uint)cultureCount);
+ count += this.WriteUInt32(12); // One record has always 12 bytes size
+
+ // Jump over position table
+ long tpos = this.dataStream.Position;
+ this.dataStream.Position += cultureCount * 12;
+
+ uint[] offset = new uint[cultureCount];
+ int[] lengths = new int[cultureCount];
+
+ for (int i = 0; i < cultureCount; i++)
+ {
+ offset[i] = (uint)(this.dataStream.Position - start);
+ count += lengths[i] = this.WriteUnicodeString(value.Texts[i].Text);
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tpos;
+ for (int i = 0; i < cultureCount; i++)
+ {
+ string[] code = value.Texts[i].Culture.Name.Split('-');
+
+ count += this.WriteASCIIString(code[0].ToLower(), 2, ' ');
+ count += this.WriteASCIIString(code[1].ToUpper(), 2, ' ');
+
+ count += this.WriteUInt32((uint)lengths[i]);
+ count += this.WriteUInt32(offset[i]);
+ }
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int count = this.WriteUInt16((ushort)value.InputChannelCount);
+ count += this.WriteUInt16((ushort)value.OutputChannelCount);
+ count += this.WriteUInt32((uint)value.Data.Length);
+
+ // Jump over position table
+ long tpos = this.dataStream.Position;
+ this.dataStream.Position += value.Data.Length * 8;
+
+ IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length];
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ uint offset = (uint)(this.dataStream.Position - start);
+ int size = this.WriteMultiProcessElement(value.Data[i]);
+ count += this.WritePadding();
+ posTable[i] = new IccPositionNumber(offset, (uint)size);
+ count += size;
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tpos;
+ foreach (IccPositionNumber pos in posTable)
+ {
+ count += this.WritePositionNumber(pos);
+ }
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value)
+ {
+ int count = this.WriteDirect32(value.VendorFlags)
+ + this.WriteUInt32((uint)value.Colors.Length)
+ + this.WriteUInt32((uint)value.CoordinateCount)
+ + this.WriteASCIIString(value.Prefix, 32, '\0')
+ + this.WriteASCIIString(value.Suffix, 32, '\0');
+
+ foreach (IccNamedColor color in value.Colors)
+ {
+ count += this.WriteNamedColor(color);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value)
+ {
+ return this.WriteParametricCurve(value.Curve);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value)
+ {
+ int count = this.WriteUInt32((uint)value.Descriptions.Length);
+ foreach (IccProfileDescription desc in value.Descriptions)
+ {
+ count += this.WriteProfileDescription(desc);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+ int length = value.Data.Length;
+
+ int count = this.WriteUInt32((uint)length);
+
+ // Jump over position table
+ long tablePosition = this.dataStream.Position;
+ this.dataStream.Position += length * 8;
+ IccPositionNumber[] table = new IccPositionNumber[length];
+
+ for (int i = 0; i < length; i++)
+ {
+ uint offset = (uint)(this.dataStream.Position - start);
+ int size = this.WriteProfileId(value.Data[i].Id);
+ size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.Data[i].Description));
+ size += this.WritePadding();
+ table[i] = new IccPositionNumber(offset, (uint)size);
+ count += size;
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tablePosition;
+ foreach (IccPositionNumber pos in table)
+ {
+ count += this.WritePositionNumber(pos);
+ }
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8;
+
+ int count = this.WriteUInt16(value.ChannelCount);
+ count += this.WriteUInt16((ushort)value.Curves.Length);
+
+ // Jump over position table
+ long tablePosition = this.dataStream.Position;
+ this.dataStream.Position += value.Curves.Length * 4;
+
+ uint[] offset = new uint[value.Curves.Length];
+
+ for (int i = 0; i < value.Curves.Length; i++)
+ {
+ offset[i] = (uint)(this.dataStream.Position - start);
+ count += this.WriteResponseCurve(value.Curves[i]);
+ count += this.WritePadding();
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tablePosition;
+ count += this.WriteArray(offset);
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ count += this.WriteFix16(value.Data[i] * 256d);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value)
+ {
+ return this.WriteASCIIString(value.SignatureData, 4, ' ');
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteTextTagDataEntry(IccTextTagDataEntry value)
+ {
+ return this.WriteASCIIString(value.Text);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ count += this.WriteUFix16(value.Data[i]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value)
+ {
+ return this.WriteXYZNumber(value.IlluminantXyz)
+ + this.WriteXYZNumber(value.SurroundXyz)
+ + this.WriteUInt32((uint)value.Illuminant);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteXyzTagDataEntry(IccXyzTagDataEntry value)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ count += this.WriteXYZNumber(value.Data[i]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value)
+ {
+ int size, count = 0;
+
+ if (value.Ascii == null)
+ {
+ count += this.WriteUInt32(0);
+ }
+ else
+ {
+ this.dataStream.Position += 4;
+ count += size = this.WriteASCIIString(value.Ascii + '\0');
+ this.dataStream.Position -= size + 4;
+ count += this.WriteUInt32((uint)size);
+ this.dataStream.Position += size;
+ }
+
+ if (value.Unicode == null)
+ {
+ count += this.WriteUInt32(0);
+ count += this.WriteUInt32(0);
+ }
+ else
+ {
+ this.dataStream.Position += 8;
+ count += size = this.WriteUnicodeString(value.Unicode + '\0');
+ this.dataStream.Position -= size + 8;
+ count += this.WriteUInt32(value.UnicodeLanguageCode);
+ count += this.WriteUInt32((uint)value.Unicode.Length + 1);
+ this.dataStream.Position += size;
+ }
+
+ if (value.ScriptCode == null)
+ {
+ count += this.WriteUInt16(0);
+ count += this.WriteByte(0);
+ count += this.WriteEmpty(67);
+ }
+ else
+ {
+ this.dataStream.Position += 3;
+ count += size = this.WriteASCIIString(value.ScriptCode, 67, '\0');
+ this.dataStream.Position -= size + 3;
+ count += this.WriteUInt16(value.ScriptCodeCode);
+ count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length));
+ this.dataStream.Position += size;
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Write Matrix
+
+ ///
+ /// Writes a two dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(Matrix4x4 value, bool isSingle)
+ {
+ int count = 0;
+
+ if (isSingle)
+ {
+ count += this.WriteSingle(value.M11);
+ count += this.WriteSingle(value.M21);
+ count += this.WriteSingle(value.M31);
+
+ count += this.WriteSingle(value.M12);
+ count += this.WriteSingle(value.M22);
+ count += this.WriteSingle(value.M32);
+
+ count += this.WriteSingle(value.M13);
+ count += this.WriteSingle(value.M23);
+ count += this.WriteSingle(value.M33);
+ }
+ else
+ {
+ count += this.WriteFix16(value.M11);
+ count += this.WriteFix16(value.M21);
+ count += this.WriteFix16(value.M31);
+
+ count += this.WriteFix16(value.M12);
+ count += this.WriteFix16(value.M22);
+ count += this.WriteFix16(value.M32);
+
+ count += this.WriteFix16(value.M13);
+ count += this.WriteFix16(value.M23);
+ count += this.WriteFix16(value.M33);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a two dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(Fast2DArray value, bool isSingle)
+ {
+ int count = 0;
+ for (int y = 0; y < value.Height; y++)
+ {
+ for (int x = 0; x < value.Width; x++)
+ {
+ if (isSingle)
+ {
+ count += this.WriteSingle(value[x, y]);
+ }
+ else
+ {
+ count += this.WriteFix16(value[x, y]);
+ }
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a two dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(float[,] value, bool isSingle)
+ {
+ int count = 0;
+ for (int y = 0; y < value.GetLength(1); y++)
+ {
+ for (int x = 0; x < value.GetLength(0); x++)
+ {
+ if (isSingle)
+ {
+ count += this.WriteSingle(value[x, y]);
+ }
+ else
+ {
+ count += this.WriteFix16(value[x, y]);
+ }
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a one dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(Vector3 value, bool isSingle)
+ {
+ int count = 0;
+ if (isSingle)
+ {
+ count += this.WriteSingle(value.X);
+ count += this.WriteSingle(value.X);
+ count += this.WriteSingle(value.X);
+ }
+ else
+ {
+ count += this.WriteFix16(value.X);
+ count += this.WriteFix16(value.X);
+ count += this.WriteFix16(value.X);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a one dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(float[] value, bool isSingle)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (isSingle)
+ {
+ count += this.WriteSingle(value[i]);
+ }
+ else
+ {
+ count += this.WriteFix16(value[i]);
+ }
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Write (C)LUT
+
+ ///
+ /// Writes an 8bit lookup table
+ ///
+ /// The LUT to write
+ /// The number of bytes written
+ public int WriteLUT8(IccLut value)
+ {
+ foreach (double item in value.Values)
+ {
+ this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue));
+ }
+
+ return value.Values.Length;
+ }
+
+ ///
+ /// Writes an 16bit lookup table
+ ///
+ /// The LUT to write
+ /// The number of bytes written
+ public int WriteLUT16(IccLut value)
+ {
+ foreach (double item in value.Values)
+ {
+ this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
+ }
+
+ return value.Values.Length * 2;
+ }
+
+ ///
+ /// Writes an color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteCLUT(IccClut value)
+ {
+ int count = this.WriteArray(value.GridPointCount);
+ count += this.WriteEmpty(16 - value.GridPointCount.Length);
+
+ switch (value.DataType)
+ {
+ case IccClutDataType.Float:
+ return count + this.WriteCLUTf32(value);
+ case IccClutDataType.UInt8:
+ count += this.WriteByte(1);
+ count += this.WriteEmpty(3);
+ return count + this.WriteCLUT8(value);
+ case IccClutDataType.UInt16:
+ count += this.WriteByte(2);
+ count += this.WriteEmpty(3);
+ return count + this.WriteCLUT16(value);
+
+ default:
+ throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}");
+ }
+ }
+
+ ///
+ /// Writes a 8bit color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteCLUT8(IccClut value)
+ {
+ int count = 0;
+ foreach (float[] inArray in value.Values)
+ {
+ foreach (float item in inArray)
+ {
+ count += this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue));
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a 16bit color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteCLUT16(IccClut value)
+ {
+ int count = 0;
+ foreach (float[] inArray in value.Values)
+ {
+ foreach (float item in inArray)
+ {
+ count += this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a 32bit float color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteCLUTf32(IccClut value)
+ {
+ int count = 0;
+ foreach (float[] inArray in value.Values)
+ {
+ foreach (float item in inArray)
+ {
+ count += this.WriteSingle(item);
+ }
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Write MultiProcessElement
+
+ ///
+ /// Writes a
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteMultiProcessElement(IccMultiProcessElement value)
+ {
+ int count = this.WriteUInt32((uint)value.Signature);
+ count += this.WriteUInt16((ushort)value.InputChannelCount);
+ count += this.WriteUInt16((ushort)value.OutputChannelCount);
+
+ switch (value.Signature)
+ {
+ case IccMultiProcessElementSignature.CurveSet:
+ return count + this.WriteCurveSetProcessElement(value as IccCurveSetProcessElement);
+ case IccMultiProcessElementSignature.Matrix:
+ return count + this.WriteMatrixProcessElement(value as IccMatrixProcessElement);
+ case IccMultiProcessElementSignature.Clut:
+ return count + this.WriteCLUTProcessElement(value as IccClutProcessElement);
+
+ case IccMultiProcessElementSignature.BAcs:
+ case IccMultiProcessElementSignature.EAcs:
+ return count + this.WriteEmpty(8);
+
+ default:
+ throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}");
+ }
+ }
+
+ ///
+ /// Writes a CurveSet
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteCurveSetProcessElement(IccCurveSetProcessElement value)
+ {
+ int count = 0;
+ foreach (IccOneDimensionalCurve curve in value.Curves)
+ {
+ count += this.WriteOneDimensionalCurve(curve);
+ count += this.WritePadding();
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a Matrix
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteMatrixProcessElement(IccMatrixProcessElement value)
+ {
+ return this.WriteMatrix(value.MatrixIxO, true)
+ + this.WriteMatrix(value.MatrixOx1, true);
+ }
+
+ ///
+ /// Writes a CLUT
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteCLUTProcessElement(IccClutProcessElement value)
+ {
+ return this.WriteCLUT(value.ClutValue);
+ }
+
+ #endregion
+
+ #region Write Curves
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteOneDimensionalCurve(IccOneDimensionalCurve value)
+ {
+ int count = this.WriteUInt16((ushort)value.Segments.Length);
+ count += this.WriteEmpty(2);
+
+ foreach (double point in value.BreakPoints)
+ {
+ count += this.WriteSingle((float)point);
+ }
+
+ foreach (IccCurveSegment segment in value.Segments)
+ {
+ count += this.WriteCurveSegment(segment);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteResponseCurve(IccResponseCurve value)
+ {
+ int count = this.WriteUInt32((uint)value.CurveType);
+ int channels = value.XyzValues.Length;
+
+ foreach (IccResponseNumber[] responseArray in value.ResponseArrays)
+ {
+ count += this.WriteUInt32((uint)responseArray.Length);
+ }
+
+ foreach (Vector3 xyz in value.XyzValues)
+ {
+ count += this.WriteXYZNumber(xyz);
+ }
+
+ foreach (IccResponseNumber[] responseArray in value.ResponseArrays)
+ {
+ foreach (IccResponseNumber response in responseArray)
+ {
+ count += this.WriteResponseNumber(response);
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteParametricCurve(IccParametricCurve value)
+ {
+ int count = this.WriteUInt16(value.Type);
+ count += this.WriteEmpty(2);
+
+ if (value.Type >= 0 && value.Type <= 4)
+ {
+ count += this.WriteFix16(value.G);
+ }
+
+ if (value.Type > 0 && value.Type <= 4)
+ {
+ count += this.WriteFix16(value.A);
+ count += this.WriteFix16(value.B);
+ }
+
+ if (value.Type > 1 && value.Type <= 4)
+ {
+ count += this.WriteFix16(value.C);
+ }
+
+ if (value.Type > 2 && value.Type <= 4)
+ {
+ count += this.WriteFix16(value.D);
+ }
+
+ if (value.Type == 4)
+ {
+ count += this.WriteFix16(value.E);
+ count += this.WriteFix16(value.F);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteCurveSegment(IccCurveSegment value)
+ {
+ int count = this.WriteUInt32((uint)value.Signature);
+ count += this.WriteEmpty(4);
+
+ switch (value.Signature)
+ {
+ case IccCurveSegmentSignature.FormulaCurve:
+ return count + this.WriteFormulaCurveElement(value as IccFormulaCurveElement);
+ case IccCurveSegmentSignature.SampledCurve:
+ return count + this.WriteSampledCurveElement(value as IccSampledCurveElement);
+ default:
+ throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}");
+ }
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteFormulaCurveElement(IccFormulaCurveElement value)
+ {
+ int count = this.WriteUInt16(value.Type);
+ count += this.WriteEmpty(2);
+
+ if (value.Type == 0 || value.Type == 1)
+ {
+ count += this.WriteSingle((float)value.Gamma);
+ }
+
+ if (value.Type >= 0 && value.Type <= 2)
+ {
+ count += this.WriteSingle((float)value.A);
+ count += this.WriteSingle((float)value.B);
+ count += this.WriteSingle((float)value.C);
+ }
+
+ if (value.Type == 1 || value.Type == 2)
+ {
+ count += this.WriteSingle((float)value.D);
+ }
+
+ if (value.Type == 2)
+ {
+ count += this.WriteSingle((float)value.E);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteSampledCurveElement(IccSampledCurveElement value)
+ {
+ int count = this.WriteUInt32((uint)value.CurveEntries.Length);
+ foreach (double entry in value.CurveEntries)
+ {
+ count += this.WriteSingle((float)entry);
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Write Array
+
+ ///
+ /// Writes a byte array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(byte[] data)
+ {
+ this.dataStream.Write(data, 0, data.Length);
+ return data.Length;
+ }
+
+ ///
+ /// Writes a ushort array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(ushort[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteUInt16(data[i]);
+ }
+
+ return data.Length * 2;
+ }
+
+ ///
+ /// Writes a short array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(short[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteInt16(data[i]);
+ }
+
+ return data.Length * 2;
+ }
+
+ ///
+ /// Writes a uint array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(uint[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteUInt32(data[i]);
+ }
+
+ return data.Length * 4;
+ }
+
+ ///
+ /// Writes an int array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(int[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteInt32(data[i]);
+ }
+
+ return data.Length * 4;
+ }
+
+ ///
+ /// Writes a ulong array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(ulong[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteUInt64(data[i]);
+ }
+
+ return data.Length * 8;
+ }
+
+ #endregion
+
+ #region Write Misc
+
+ ///
+ /// Write a number of empty bytes
+ ///
+ /// The number of bytes to write
+ /// The number of bytes written
+ public int WriteEmpty(int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ this.dataStream.WriteByte(0);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes empty bytes to a 4-byte margin
+ ///
+ /// The number of bytes written
+ public int WritePadding()
+ {
+ int p = 4 - ((int)this.dataStream.Position % 4);
+ return this.WriteEmpty(p >= 4 ? 0 : p);
+ }
+
+ ///
+ /// Writes given bytes from pointer
+ ///
+ /// Pointer to the bytes to write
+ /// The number of bytes to write
+ /// The number of bytes written
+ private unsafe int WriteBytes(byte* data, int length)
+ {
+ if (IsLittleEndian)
+ {
+ for (int i = length - 1; i >= 0; i--)
+ {
+ this.dataStream.WriteByte(data[i]);
+ }
+ }
+ else
+ {
+ this.WriteBytesDirect(data, length);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes given bytes from pointer ignoring endianness
+ ///
+ /// Pointer to the bytes to write
+ /// The number of bytes to write
+ /// The number of bytes written
+ private unsafe int WriteBytesDirect(byte* data, int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ this.dataStream.WriteByte(data[i]);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes curve data
+ ///
+ /// The curves to write
+ /// The number of bytes written
+ private int WriteCurves(IccTagDataEntry[] curves)
+ {
+ int count = 0;
+ foreach (IccTagDataEntry curve in curves)
+ {
+ if (curve.Signature != IccTypeSignature.Curve && curve.Signature != IccTypeSignature.ParametricCurve)
+ {
+ throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" +
+ $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries");
+ }
+
+ count += this.WriteTagDataEntry(curve);
+ count += this.WritePadding();
+ }
+
+ return count;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
new file mode 100644
index 000000000..ed64c459b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
@@ -0,0 +1,107 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Collections.Generic;
+#if !NETSTANDARD1_1
+ using System;
+ using System.Security.Cryptography;
+#endif
+
+ ///
+ /// Represents an ICC profile
+ ///
+ internal class IccProfile
+ {
+ ///
+ /// The byte array to read the ICC profile from
+ ///
+ private readonly byte[] data;
+
+ ///
+ /// The backing file for the property
+ ///
+ private readonly List entries;
+
+ ///
+ /// ICC profile header
+ ///
+ private readonly IccProfileHeader header;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IccProfile()
+ {
+ this.data = null;
+ this.entries = new List();
+ this.header = new IccProfileHeader();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw ICC profile data
+ public IccProfile(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+
+ this.data = data;
+ IccReader reader = new IccReader();
+ this.header = reader.ReadHeader(data);
+ this.entries = new List(reader.ReadTagData(data));
+ }
+
+ ///
+ /// Gets the profile header
+ ///
+ public IccProfileHeader Header
+ {
+ get { return this.header; }
+ }
+
+ ///
+ /// Gets the actual profile data
+ ///
+ public List Entries
+ {
+ get { return this.entries; }
+ }
+
+#if !NETSTANDARD1_1
+
+ ///
+ /// Calculates the MD5 hash value of an ICC profile header
+ ///
+ /// The data of which to calculate the hash value
+ /// The calculated hash
+ public static IccProfileId CalculateHash(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length < 128, nameof(data), "Data length must be at least 128 to be a valid profile header");
+
+ byte[] header = new byte[128];
+ Buffer.BlockCopy(data, 0, header, 0, 128);
+
+ using (MD5 md5 = MD5.Create())
+ {
+ // Zero out some values
+ Array.Clear(header, 44, 4); // Profile flags
+ Array.Clear(header, 64, 4); // Rendering Intent
+ Array.Clear(header, 84, 16); // Profile ID
+
+ // Calculate hash
+ byte[] hash = md5.ComputeHash(data);
+
+ // Read values from hash
+ IccDataReader reader = new IccDataReader(hash);
+ return reader.ReadProfileId();
+ }
+ }
+
+#endif
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs
new file mode 100644
index 000000000..af421f7eb
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Contains all values of an ICC profile header
+ ///
+ internal sealed class IccProfileHeader
+ {
+ ///
+ /// Gets or sets the profile size in bytes (will be ignored when writing a profile)
+ ///
+ public uint Size { get; set; }
+
+ ///
+ /// Gets or sets the preferred CMM (Color Management Module) type
+ ///
+ public string CmmType { get; set; }
+
+ ///
+ /// Gets or sets the profiles version number
+ ///
+ public Version Version { get; set; }
+
+ ///
+ /// Gets or sets the type of the profile
+ ///
+ public IccProfileClass Class { get; set; }
+
+ ///
+ /// Gets or sets the data colorspace
+ ///
+ public IccColorSpaceType DataColorSpace { get; set; }
+
+ ///
+ /// Gets or sets the profile connection space
+ ///
+ public IccColorSpaceType ProfileConnectionSpace { get; set; }
+
+ ///
+ /// Gets or sets the date and time this profile was created
+ ///
+ public DateTime CreationDate { get; set; }
+
+ ///
+ /// Gets or sets the file signature. Should always be "acsp".
+ /// Value will be ignored when writing a profile.
+ ///
+ public string FileSignature { get; set; }
+
+ ///
+ /// Gets or sets the primary platform this profile as created for
+ ///
+ public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; }
+
+ ///
+ /// Gets or sets the profile flags to indicate various options for the CMM
+ /// such as distributed processing and caching options
+ ///
+ public IccProfileFlag Flags { get; set; }
+
+ ///
+ /// Gets or sets the device manufacturer of the device for which this profile is created
+ ///
+ public uint DeviceManufacturer { get; set; }
+
+ ///
+ /// Gets or sets the model of the device for which this profile is created
+ ///
+ public uint DeviceModel { get; set; }
+
+ ///
+ /// Gets or sets the device attributes unique to the particular device setup such as media type
+ ///
+ public IccDeviceAttribute DeviceAttributes { get; set; }
+
+ ///
+ /// Gets or sets the rendering Intent
+ ///
+ public IccRenderingIntent RenderingIntent { get; set; }
+
+ ///
+ /// Gets or sets The normalized XYZ values of the illuminant of the PCS
+ ///
+ public Vector3 PcsIlluminant { get; set; }
+
+ ///
+ /// Gets or sets Profile creator signature
+ ///
+ public string CreatorSignature { get; set; }
+
+ ///
+ /// Gets or sets the profile ID (hash)
+ ///
+ public IccProfileId Id { get; set; }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs
new file mode 100644
index 000000000..faa86b6f9
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs
@@ -0,0 +1,113 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Reads and parses ICC data from a byte array
+ ///
+ internal sealed class IccReader
+ {
+ ///
+ /// Reads an ICC profile
+ ///
+ /// The raw ICC data
+ /// The read ICC profile
+ public IccProfile Read(byte[] data)
+ {
+ Guard.IsTrue(data.Length < 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile");
+
+ IccDataReader reader = new IccDataReader(data);
+ IccProfileHeader header = this.ReadHeader(reader);
+ IccTagDataEntry[] tagDate = this.ReadTagData(reader);
+
+ return new IccProfile();
+ }
+
+ ///
+ /// Reads an ICC profile header
+ ///
+ /// The raw ICC data
+ /// The read ICC profile header
+ public IccProfileHeader ReadHeader(byte[] data)
+ {
+ Guard.IsTrue(data.Length < 128, nameof(data), "Data length must be at least 128 to be a valid profile header");
+
+ IccDataReader reader = new IccDataReader(data);
+ return this.ReadHeader(reader);
+ }
+
+ ///
+ /// Reads the ICC profile tag data
+ ///
+ /// The raw ICC data
+ /// The read ICC profile tag data
+ public IccTagDataEntry[] ReadTagData(byte[] data)
+ {
+ Guard.IsTrue(data.Length < 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile");
+
+ IccDataReader reader = new IccDataReader(data);
+ return this.ReadTagData(reader);
+ }
+
+ private IccProfileHeader ReadHeader(IccDataReader reader)
+ {
+ reader.SetIndex(0);
+
+ return new IccProfileHeader
+ {
+ Size = reader.ReadUInt32(),
+ CmmType = reader.ReadASCIIString(4),
+ Version = reader.ReadVersionNumber(),
+ Class = (IccProfileClass)reader.ReadUInt32(),
+ DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(),
+ ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(),
+ CreationDate = reader.ReadDateTime(),
+ FileSignature = reader.ReadASCIIString(4),
+ PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(),
+ Flags = (IccProfileFlag)reader.ReadDirect32(),
+ DeviceManufacturer = reader.ReadUInt32(),
+ DeviceModel = reader.ReadUInt32(),
+ DeviceAttributes = (IccDeviceAttribute)reader.ReadDirect64(),
+ RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(),
+ PcsIlluminant = reader.ReadXyzNumber(),
+ CreatorSignature = reader.ReadASCIIString(4),
+ Id = reader.ReadProfileId(),
+ };
+ }
+
+ private IccTagDataEntry[] ReadTagData(IccDataReader reader)
+ {
+ IccTagTableEntry[] tagTable = this.ReadTagTable(reader);
+ IccTagDataEntry[] entries = new IccTagDataEntry[tagTable.Length];
+ for (int i = 0; i < tagTable.Length; i++)
+ {
+ IccTagDataEntry entry = reader.ReadTagDataEntry(tagTable[i]);
+ entry.TagSignature = tagTable[i].Signature;
+ entries[i] = entry;
+ }
+
+ return entries;
+ }
+
+ private IccTagTableEntry[] ReadTagTable(IccDataReader reader)
+ {
+ reader.SetIndex(128); // An ICC header is 128 bytes long
+
+ uint tagCount = reader.ReadUInt32();
+ IccTagTableEntry[] table = new IccTagTableEntry[tagCount];
+
+ for (int i = 0; i < tagCount; i++)
+ {
+ uint tagSignature = reader.ReadUInt32();
+ uint tagOffset = reader.ReadUInt32();
+ uint tagSize = reader.ReadUInt32();
+ table[i] = new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize);
+ }
+
+ return table;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs
new file mode 100644
index 000000000..50ad5ded0
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs
@@ -0,0 +1,68 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// The data of an ICC tag entry
+ ///
+ internal abstract class IccTagDataEntry : IEquatable
+ {
+ private IccProfileTag tagSignature = IccProfileTag.Unknown;
+
+ ///
+ /// Initializes a new instance of the class.
+ /// TagSignature will be
+ ///
+ /// Type Signature
+ protected IccTagDataEntry(IccTypeSignature signature)
+ : this(signature, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type Signature
+ /// Tag Signature
+ protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature)
+ {
+ this.Signature = signature;
+ this.tagSignature = tagSignature;
+ }
+
+ ///
+ /// Gets the type Signature
+ ///
+ public IccTypeSignature Signature { get; }
+
+ ///
+ /// Gets or sets the tag Signature
+ ///
+ public IccProfileTag TagSignature
+ {
+ get { return this.tagSignature; }
+ set { this.tagSignature = value; }
+ }
+
+ ///
+ public virtual bool Equals(IccTagDataEntry other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Signature == other.Signature;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs
new file mode 100644
index 000000000..dd6de5688
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Contains methods for writing ICC profiles.
+ ///
+ internal sealed class IccWriter
+ {
+ ///
+ /// Writes the ICC profile into a byte array
+ ///
+ /// The ICC profile to write
+ /// The ICC profile as a byte array
+ public byte[] Write(IccProfile profile)
+ {
+ 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)
+ {
+ writer.SetIndex(0);
+
+ writer.WriteUInt32(writer.Length + 128);
+ writer.WriteASCIIString(header.CmmType, 4, ' ');
+ writer.WriteVersionNumber(header.Version);
+ writer.WriteUInt32((uint)header.Class);
+ writer.WriteUInt32((uint)header.DataColorSpace);
+ writer.WriteUInt32((uint)header.ProfileConnectionSpace);
+ writer.WriteDateTime(header.CreationDate);
+ writer.WriteASCIIString("acsp");
+ writer.WriteUInt32((uint)header.PrimaryPlatformSignature);
+ writer.WriteDirect32((int)header.Flags);
+ writer.WriteUInt32(header.DeviceManufacturer);
+ writer.WriteUInt32(header.DeviceModel);
+ writer.WriteDirect64((long)header.DeviceAttributes);
+ writer.WriteUInt32((uint)header.RenderingIntent);
+ writer.WriteXYZNumber(header.PcsIlluminant);
+ writer.WriteASCIIString(header.CreatorSignature, 4, ' ');
+
+#if !NETSTANDARD1_1
+ IccProfileId id = IccProfile.CalculateHash(writer.GetData());
+ writer.WriteProfileId(id);
+#else
+ writer.WriteProfileId(IccProfileId.Zero);
+#endif
+ }
+
+ private void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table)
+ {
+ // 128 = size of ICC header
+ writer.SetIndex(128);
+
+ writer.WriteUInt32((uint)table.Length);
+ foreach (IccTagTableEntry entry in table)
+ {
+ writer.WriteUInt32((uint)entry.Signature);
+ writer.WriteUInt32(entry.Offset);
+ writer.WriteUInt32(entry.DataSize);
+ }
+ }
+
+ private IccTagTableEntry[] WriteTagData(IccDataWriter writer, List entries)
+ {
+ List inData = new List(entries);
+ List dupData = new List();
+
+ // Filter out duplicate entries. They only need to be defined once but can be used multiple times
+ while (inData.Count > 0)
+ {
+ IccTagDataEntry[] items = inData.Where(t => inData[0].Equals(t)).ToArray();
+ dupData.Add(items);
+ foreach (IccTagDataEntry item in items)
+ {
+ inData.Remove(item);
+ }
+ }
+
+ List table = new List();
+
+ // (Header size) + (entry count) + (nr of entries) * (size of table entry)
+ writer.SetIndex(128 + 4 + (entries.Count * 12));
+
+ foreach (IccTagDataEntry[] entry in dupData)
+ {
+ writer.WriteTagDataEntry(entry[0], out IccTagTableEntry tentry);
+ foreach (IccTagDataEntry item in entry)
+ {
+ table.Add(new IccTagTableEntry(item.TagSignature, tentry.Offset, tentry.DataSize));
+ }
+ }
+
+ return table.ToArray();
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs
new file mode 100644
index 000000000..90d07a200
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// A placeholder (might be used for future ICC versions)
+ ///
+ internal sealed class IccBAcsProcessElement : IccMultiProcessElement
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ public IccBAcsProcessElement(int inChannelCount, int outChannelCount)
+ : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs
new file mode 100644
index 000000000..329d305a9
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// A CLUT (color lookup table) element to process data
+ ///
+ internal sealed class IccClutProcessElement : IccMultiProcessElement
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The color lookup table of this element
+ public IccClutProcessElement(IccClut clutValue)
+ : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1)
+ {
+ Guard.NotNull(clutValue, nameof(clutValue));
+ this.ClutValue = clutValue;
+ }
+
+ ///
+ /// Gets the color lookup table of this element
+ ///
+ public IccClut ClutValue { get; }
+
+ ///
+ public override bool Equals(IccMultiProcessElement other)
+ {
+ if (base.Equals(other) && other is IccClutProcessElement element)
+ {
+ return this.ClutValue.Equals(element.ClutValue);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs
new file mode 100644
index 000000000..3a6252d01
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// A set of curves to process data
+ ///
+ internal sealed class IccCurveSetProcessElement : IccMultiProcessElement
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An array with one dimensional curves
+ public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves)
+ : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1)
+ {
+ Guard.NotNull(curves, nameof(curves));
+ this.Curves = curves;
+ }
+
+ ///
+ /// Gets an array of one dimensional curves
+ ///
+ public IccOneDimensionalCurve[] Curves { get; }
+
+ ///
+ public override bool Equals(IccMultiProcessElement other)
+ {
+ if (base.Equals(other) && other is IccCurveSetProcessElement element)
+ {
+ return this.Curves.SequenceEqual(element.Curves);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs
new file mode 100644
index 000000000..b08fe2cf5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// A placeholder (might be used for future ICC versions)
+ ///
+ internal sealed class IccEAcsProcessElement : IccMultiProcessElement
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ public IccEAcsProcessElement(int inChannelCount, int outChannelCount)
+ : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs
new file mode 100644
index 000000000..e86e515c6
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// A matrix element to process data
+ ///
+ internal sealed class IccMatrixProcessElement : IccMultiProcessElement
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Two dimensional matrix with size of Input-Channels x Output-Channels
+ /// One dimensional matrix with size of Output-Channels x 1
+ public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1)
+ : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1)
+ {
+ Guard.NotNull(matrixIxO, nameof(matrixIxO));
+ Guard.NotNull(matrixOx1, nameof(matrixOx1));
+
+ bool wrongMatrixSize = matrixIxO.GetLength(1) != matrixOx1.Length;
+ Guard.IsTrue(wrongMatrixSize, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match");
+
+ this.MatrixIxO = matrixIxO;
+ this.MatrixOx1 = matrixOx1;
+ }
+
+ ///
+ /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels
+ ///
+ public Fast2DArray MatrixIxO { get; }
+
+ ///
+ /// Gets the one dimensional matrix with size of Output-Channels x 1
+ ///
+ public float[] MatrixOx1 { get; }
+
+ ///
+ public override bool Equals(IccMultiProcessElement other)
+ {
+ if (base.Equals(other) && other is IccMatrixProcessElement element)
+ {
+ return this.EqualsMatrix(element)
+ && this.MatrixOx1.SequenceEqual(element.MatrixOx1);
+ }
+
+ return false;
+ }
+
+ private bool EqualsMatrix(IccMatrixProcessElement element)
+ {
+ for (int x = 0; x < this.MatrixIxO.Width; x++)
+ {
+ for (int y = 0; y < this.MatrixIxO.Height; y++)
+ {
+ if (this.MatrixIxO[x, y] != element.MatrixIxO[x, y])
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs
new file mode 100644
index 000000000..205e61f01
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// An element to process data
+ ///
+ internal abstract class IccMultiProcessElement : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The signature of this element
+ /// Number of input channels
+ /// Number of output channels
+ protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount)
+ {
+ Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount));
+ Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount));
+
+ this.Signature = signature;
+ this.InputChannelCount = inChannelCount;
+ this.OutputChannelCount = outChannelCount;
+ }
+
+ ///
+ /// Gets the signature of this element
+ ///
+ public IccMultiProcessElementSignature Signature { get; }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ public virtual bool Equals(IccMultiProcessElement other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Signature == other.Signature
+ && this.InputChannelCount == other.InputChannelCount
+ && this.OutputChannelCount == other.OutputChannelCount;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs
new file mode 100644
index 000000000..cb0117cd0
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs
@@ -0,0 +1,136 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// The chromaticity tag type provides basic chromaticity data
+ /// and type of phosphors or colorants of a monitor to applications and utilities.
+ ///
+ internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Type
+ public IccChromaticityTagDataEntry(IccColorantEncoding colorantType)
+ : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Values per channel
+ public IccChromaticityTagDataEntry(double[][] channelValues)
+ : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Type
+ /// Tag Signature
+ public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature)
+ : this(colorantType, GetColorantArray(colorantType), tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Values per channel
+ /// Tag Signature
+ public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature)
+ : this(IccColorantEncoding.Unknown, channelValues, tagSignature)
+ {
+ }
+
+ private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Chromaticity, tagSignature)
+ {
+ Guard.NotNull(channelValues, nameof(channelValues));
+ Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues));
+
+ this.ColorantType = colorantType;
+ this.ChannelValues = channelValues;
+
+ int channelLength = channelValues[0].Length;
+ bool channelsNotSame = channelValues.Any(t => t == null || t.Length != channelLength);
+ Guard.IsTrue(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels");
+ }
+
+ ///
+ /// Gets the number of channels
+ ///
+ public int ChannelCount
+ {
+ get { return this.ChannelValues.Length; }
+ }
+
+ ///
+ /// Gets the colorant type
+ ///
+ public IccColorantEncoding ColorantType { get; }
+
+ ///
+ /// Gets the values per channel
+ ///
+ public double[][] ChannelValues { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccChromaticityTagDataEntry entry)
+ {
+ return this.ColorantType == entry.ColorantType
+ && this.ChannelValues.SequenceEqual(entry.ChannelValues);
+ }
+
+ return false;
+ }
+
+ private static double[][] GetColorantArray(IccColorantEncoding colorantType)
+ {
+ switch (colorantType)
+ {
+ case IccColorantEncoding.EBU_Tech_3213_E:
+ return new double[][]
+ {
+ new double[] { 0.640, 0.330 },
+ new double[] { 0.290, 0.600 },
+ new double[] { 0.150, 0.060 },
+ };
+ case IccColorantEncoding.ITU_R_BT_709_2:
+ return new double[][]
+ {
+ new double[] { 0.640, 0.330 },
+ new double[] { 0.300, 0.600 },
+ new double[] { 0.150, 0.060 },
+ };
+ case IccColorantEncoding.P22:
+ return new double[][]
+ {
+ new double[] { 0.625, 0.340 },
+ new double[] { 0.280, 0.605 },
+ new double[] { 0.155, 0.070 },
+ };
+ case IccColorantEncoding.SMPTE_RP145:
+ return new double[][]
+ {
+ new double[] { 0.630, 0.340 },
+ new double[] { 0.310, 0.595 },
+ new double[] { 0.155, 0.070 },
+ };
+ default:
+ throw new ArgumentException("Unrecognized colorant encoding");
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs
new file mode 100644
index 000000000..9f02bdf24
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs
@@ -0,0 +1,55 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This tag specifies the laydown order in which colorants
+ /// will be printed on an n-colorant device.
+ ///
+ internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant order numbers
+ public IccColorantOrderTagDataEntry(byte[] colorantNumber)
+ : this(colorantNumber, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant order numbers
+ /// Tag Signature
+ public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ColorantOrder, tagSignature)
+ {
+ Guard.NotNull(colorantNumber, nameof(colorantNumber));
+ Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber));
+
+ this.ColorantNumber = colorantNumber;
+ }
+
+ ///
+ /// Gets the colorant order numbers
+ ///
+ public byte[] ColorantNumber { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccColorantOrderTagDataEntry entry)
+ {
+ return this.ColorantNumber.SequenceEqual(entry.ColorantNumber);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs
new file mode 100644
index 000000000..657c53cbc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs
@@ -0,0 +1,56 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// The purpose of this tag is to identify the colorants used in
+ /// the profile by a unique name and set of PCSXYZ or PCSLAB values
+ /// to give the colorant an unambiguous value.
+ ///
+ internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Data
+ public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData)
+ : this(colorantData, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Data
+ /// Tag Signature
+ public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ColorantTable, tagSignature)
+ {
+ Guard.NotNull(colorantData, nameof(colorantData));
+ Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData));
+
+ this.ColorantData = colorantData;
+ }
+
+ ///
+ /// Gets the colorant data
+ ///
+ public IccColorantTableEntry[] ColorantData { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccColorantTableTagDataEntry entry)
+ {
+ return this.ColorantData.SequenceEqual(entry.ColorantData);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs
new file mode 100644
index 000000000..334064c53
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs
@@ -0,0 +1,122 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// The type contains a one-dimensional table of double values.
+ ///
+ internal sealed class IccCurveTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IccCurveTagDataEntry()
+ : this(new float[0], IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Gamma value
+ public IccCurveTagDataEntry(float gamma)
+ : this(new float[] { gamma }, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Curve Data
+ public IccCurveTagDataEntry(float[] curveData)
+ : this(curveData, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Tag Signature
+ public IccCurveTagDataEntry(IccProfileTag tagSignature)
+ : this(new float[0], tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Gamma value
+ /// Tag Signature
+ public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature)
+ : this(new float[] { gamma }, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Curve Data
+ /// Tag Signature
+ public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Curve, tagSignature)
+ {
+ this.CurveData = curveData ?? new float[0];
+ }
+
+ ///
+ /// Gets the curve data
+ ///
+ public float[] CurveData { get; }
+
+ ///
+ /// Gets the gamma value.
+ /// Only valid if is true
+ ///
+ public float Gamma
+ {
+ get
+ {
+ if (this.IsGamma)
+ {
+ return this.CurveData[0];
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the curve maps input directly to output
+ ///
+ public bool IsIdentityResponse
+ {
+ get { return this.CurveData.Length == 0; }
+ }
+
+ ///
+ /// Gets a value indicating whether the curve is a gamma curve
+ ///
+ public bool IsGamma
+ {
+ get { return this.CurveData.Length == 1; }
+ }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccCurveTagDataEntry entry)
+ {
+ return this.CurveData.SequenceEqual(entry.CurveData);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs
new file mode 100644
index 000000000..de6c81c9e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// The dataType is a simple data structure that contains
+ /// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes.
+ ///
+ internal sealed class IccDataTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data
+ public IccDataTagDataEntry(byte[] data)
+ : this(data, false, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data
+ /// True if the given data is 7bit ASCII encoded text
+ public IccDataTagDataEntry(byte[] data, bool isAscii)
+ : this(data, isAscii, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data
+ /// True if the given data is 7bit ASCII encoded text
+ /// Tag Signature
+ public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Data, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ this.IsAscii = isAscii;
+ }
+
+ ///
+ /// Gets the raw Data
+ ///
+ public byte[] Data { get; }
+
+ ///
+ /// Gets a value indicating whether the represents 7bit ASCII encoded text
+ ///
+ public bool IsAscii { get; }
+
+ ///
+ /// Gets the decoded as 7bit ASCII.
+ /// If is false, returns null
+ ///
+ public string AsciiString
+ {
+ get
+ {
+ if (this.IsAscii)
+ {
+ // Encoding.ASCII is missing in netstandard1.1, use UTF8 instead because it's compatible with ASCII
+ return Encoding.UTF8.GetString(this.Data, 0, this.Data.Length);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccDataTagDataEntry entry)
+ {
+ return this.IsAscii == entry.IsAscii
+ && this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs
new file mode 100644
index 000000000..819c0d4bc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// This type is a representation of the time and date.
+ ///
+ internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The DateTime value
+ public IccDateTimeTagDataEntry(DateTime value)
+ : this(value, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The DateTime value
+ /// Tag Signature
+ public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature)
+ : base(IccTypeSignature.DateTime, tagSignature)
+ {
+ this.Value = value;
+ }
+
+ ///
+ /// Gets the date and time value
+ ///
+ public DateTime Value { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccDateTimeTagDataEntry entry)
+ {
+ return this.Value == entry.Value;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs
new file mode 100644
index 000000000..f98b8ed7f
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type represents an array of doubles (from 32bit fixed point values).
+ ///
+ internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccFix16ArrayTagDataEntry(float[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.S15Fixed16Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public float[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccFix16ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs
new file mode 100644
index 000000000..8ec1aee3c
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs
@@ -0,0 +1,153 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform using tables
+ /// with 16-bit precision.
+ ///
+ internal sealed class IccLut16TagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(null, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : this(null, inputValues, clutValues, outputValues, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Lut16, tagSignature)
+ {
+ Guard.NotNull(inputValues, nameof(inputValues));
+ Guard.NotNull(clutValues, nameof(clutValues));
+ Guard.NotNull(outputValues, nameof(outputValues));
+
+ if (matrix != null)
+ {
+ bool isNot3By3 = matrix.GetLength(0) != 3 || matrix.GetLength(1) != 3;
+ Guard.IsTrue(isNot3By3, nameof(matrix), "Matrix must have a size of three by three");
+ }
+
+ Guard.IsTrue(this.InputChannelCount != clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount != clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+
+ this.Matrix = this.CreateMatrix(matrix);
+ this.InputValues = inputValues;
+ this.ClutValues = clutValues;
+ this.OutputValues = outputValues;
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount
+ {
+ get { return this.InputValues.Length; }
+ }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount
+ {
+ get { return this.OutputValues.Length; }
+ }
+
+ ///
+ /// Gets the conversion matrix
+ ///
+ public Matrix4x4 Matrix { get; }
+
+ ///
+ /// Gets the input lookup table
+ ///
+ public IccLut[] InputValues { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the output lookup table
+ ///
+ public IccLut[] OutputValues { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLut16TagDataEntry entry)
+ {
+ return this.ClutValues == entry.ClutValues
+ && this.Matrix == entry.Matrix
+ && this.InputValues.SequenceEqual(entry.InputValues)
+ && this.OutputValues.SequenceEqual(entry.OutputValues);
+ }
+
+ return false;
+ }
+
+ private Matrix4x4 CreateMatrix(float[,] matrix)
+ {
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs
new file mode 100644
index 000000000..46ac6efe8
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs
@@ -0,0 +1,156 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform using tables
+ /// with 8-bit precision.
+ ///
+ internal sealed class IccLut8TagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(null, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : this(null, inputValues, clutValues, outputValues, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Lut8, tagSignature)
+ {
+ Guard.NotNull(inputValues, nameof(inputValues));
+ Guard.NotNull(clutValues, nameof(clutValues));
+ Guard.NotNull(outputValues, nameof(outputValues));
+
+ if (matrix != null)
+ {
+ bool isNot3By3 = matrix.GetLength(0) != 3 || matrix.GetLength(1) != 3;
+ Guard.IsTrue(isNot3By3, nameof(matrix), "Matrix must have a size of three by three");
+ }
+
+ Guard.IsTrue(this.InputChannelCount != clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount != clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+
+ Guard.IsTrue(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256");
+ Guard.IsTrue(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256");
+
+ this.Matrix = this.CreateMatrix(matrix);
+ this.InputValues = inputValues;
+ this.ClutValues = clutValues;
+ this.OutputValues = outputValues;
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount
+ {
+ get { return this.InputValues.Length; }
+ }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount
+ {
+ get { return this.OutputValues.Length; }
+ }
+
+ ///
+ /// Gets the conversion matrix
+ ///
+ public Matrix4x4 Matrix { get; }
+
+ ///
+ /// Gets the input lookup table
+ ///
+ public IccLut[] InputValues { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the output lookup table
+ ///
+ public IccLut[] OutputValues { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLut16TagDataEntry entry)
+ {
+ return this.ClutValues == entry.ClutValues
+ && this.Matrix == entry.Matrix
+ && this.InputValues.SequenceEqual(entry.InputValues)
+ && this.OutputValues.SequenceEqual(entry.OutputValues);
+ }
+
+ return false;
+ }
+
+ private Matrix4x4 CreateMatrix(float[,] matrix)
+ {
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs
new file mode 100644
index 000000000..e4cc527ea
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs
@@ -0,0 +1,274 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform.
+ ///
+ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ public IccLutAToBTagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA)
+ : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ /// Tag Signature
+ public IccLutAToBTagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA,
+ IccProfileTag tagSignature)
+ : base(IccTypeSignature.LutAToB, tagSignature)
+ {
+ this.VerifyMatrix(matrix3x3, matrix3x1);
+ this.VerifyCurve(curveA, nameof(curveA));
+ this.VerifyCurve(curveB, nameof(curveB));
+ this.VerifyCurve(curveM, nameof(curveM));
+
+ this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3);
+ this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1);
+ this.CurveA = curveA;
+ this.CurveB = curveB;
+ this.CurveM = curveM;
+ this.ClutValues = clutValues;
+
+ if (this.IsAClutMMatrixB())
+ {
+ Guard.IsTrue(this.CurveB.Length != 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length != 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+
+ this.InputChannelCount = curveA.Length;
+ this.OutputChannelCount = 3;
+
+ Guard.IsTrue(this.InputChannelCount != clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount != clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsMMatrixB())
+ {
+ Guard.IsTrue(this.CurveB.Length != 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length != 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+
+ this.InputChannelCount = this.OutputChannelCount = 3;
+ }
+ else if (this.IsAClutB())
+ {
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+ Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB));
+
+ this.InputChannelCount = curveA.Length;
+ this.OutputChannelCount = curveB.Length;
+
+ Guard.IsTrue(this.InputChannelCount != clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount != clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsB())
+ {
+ this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length;
+ }
+ else
+ {
+ throw new ArgumentException("Invalid combination of values given");
+ }
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the two dimensional conversion matrix (3x3)
+ ///
+ public Matrix4x4? Matrix3x3 { get; }
+
+ ///
+ /// Gets the one dimensional conversion matrix (3x1)
+ ///
+ public Vector3? Matrix3x1 { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the B Curve
+ ///
+ public IccTagDataEntry[] CurveB { get; }
+
+ ///
+ /// Gets the M Curve
+ ///
+ public IccTagDataEntry[] CurveM { get; }
+
+ ///
+ /// Gets the A Curve
+ ///
+ public IccTagDataEntry[] CurveA { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLutAToBTagDataEntry entry)
+ {
+ return this.InputChannelCount == entry.InputChannelCount
+ && this.OutputChannelCount == entry.OutputChannelCount
+ && this.Matrix3x1 == entry.Matrix3x1
+ && this.Matrix3x3 == entry.Matrix3x3
+ && this.ClutValues == entry.ClutValues
+ && this.EqualsCurve(this.CurveA, entry.CurveA)
+ && this.EqualsCurve(this.CurveB, entry.CurveB)
+ && this.EqualsCurve(this.CurveM, entry.CurveM);
+ }
+
+ return false;
+ }
+
+ private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves)
+ {
+ bool thisNull = thisCurves == null;
+ bool entryNull = entryCurves == null;
+
+ if (thisNull && entryNull)
+ {
+ return true;
+ }
+
+ if (entryNull)
+ {
+ return false;
+ }
+
+ return thisCurves.SequenceEqual(entryCurves);
+ }
+
+ private bool IsAClutMMatrixB()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsMMatrixB()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null;
+ }
+
+ private bool IsAClutB()
+ {
+ return this.CurveB != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsB()
+ {
+ return this.CurveB != null;
+ }
+
+ private void VerifyCurve(IccTagDataEntry[] curves, string name)
+ {
+ if (curves != null)
+ {
+ bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry));
+ Guard.IsTrue(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}");
+ }
+ }
+
+ private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1)
+ {
+ if (matrix3x1 != null)
+ {
+ Guard.IsTrue(matrix3x1.Length != 3, nameof(matrix3x1), "Matrix must have a size of three");
+ }
+
+ if (matrix3x3 != null)
+ {
+ bool isNot3By3 = matrix3x3.GetLength(0) != 3 || matrix3x3.GetLength(1) != 3;
+ Guard.IsTrue(isNot3By3, nameof(matrix3x3), "Matrix must have a size of three by three");
+ }
+ }
+
+ private Vector3? CreateMatrix3x1(float[] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Vector3(matrix[0], matrix[1], matrix[2]);
+ }
+
+ private Matrix4x4? CreateMatrix3x3(float[,] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs
new file mode 100644
index 000000000..ce524e270
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs
@@ -0,0 +1,274 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform.
+ ///
+ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ public IccLutBToATagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA)
+ : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ /// Tag Signature
+ public IccLutBToATagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA,
+ IccProfileTag tagSignature)
+ : base(IccTypeSignature.LutBToA, tagSignature)
+ {
+ this.VerifyMatrix(matrix3x3, matrix3x1);
+ this.VerifyCurve(curveA, nameof(curveA));
+ this.VerifyCurve(curveB, nameof(curveB));
+ this.VerifyCurve(curveM, nameof(curveM));
+
+ this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3);
+ this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1);
+ this.CurveA = curveA;
+ this.CurveB = curveB;
+ this.CurveM = curveM;
+ this.ClutValues = clutValues;
+
+ if (this.IsBMatrixMClutA())
+ {
+ Guard.IsTrue(this.CurveB.Length != 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length != 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+
+ this.InputChannelCount = 3;
+ this.OutputChannelCount = curveA.Length;
+
+ Guard.IsTrue(this.InputChannelCount != clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount != clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsBMatrixM())
+ {
+ Guard.IsTrue(this.CurveB.Length != 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length != 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+
+ this.InputChannelCount = this.OutputChannelCount = 3;
+ }
+ else if (this.IsBClutA())
+ {
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+ Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB));
+
+ this.InputChannelCount = curveB.Length;
+ this.OutputChannelCount = curveA.Length;
+
+ Guard.IsTrue(this.InputChannelCount != clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount != clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsB())
+ {
+ this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length;
+ }
+ else
+ {
+ throw new ArgumentException("Invalid combination of values given");
+ }
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the two dimensional conversion matrix (3x3)
+ ///
+ public Matrix4x4? Matrix3x3 { get; }
+
+ ///
+ /// Gets the one dimensional conversion matrix (3x1)
+ ///
+ public Vector3? Matrix3x1 { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the B Curve
+ ///
+ public IccTagDataEntry[] CurveB { get; }
+
+ ///
+ /// Gets the M Curve
+ ///
+ public IccTagDataEntry[] CurveM { get; }
+
+ ///
+ /// Gets the A Curve
+ ///
+ public IccTagDataEntry[] CurveA { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLutBToATagDataEntry entry)
+ {
+ return this.InputChannelCount == entry.InputChannelCount
+ && this.OutputChannelCount == entry.OutputChannelCount
+ && this.Matrix3x1 == entry.Matrix3x1
+ && this.Matrix3x3 == entry.Matrix3x3
+ && this.ClutValues == entry.ClutValues
+ && this.EqualsCurve(this.CurveA, entry.CurveA)
+ && this.EqualsCurve(this.CurveB, entry.CurveB)
+ && this.EqualsCurve(this.CurveM, entry.CurveM);
+ }
+
+ return false;
+ }
+
+ private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves)
+ {
+ bool thisNull = thisCurves == null;
+ bool entryNull = entryCurves == null;
+
+ if (thisNull && entryNull)
+ {
+ return true;
+ }
+
+ if (entryNull)
+ {
+ return false;
+ }
+
+ return thisCurves.SequenceEqual(entryCurves);
+ }
+
+ private bool IsBMatrixMClutA()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsBMatrixM()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null;
+ }
+
+ private bool IsBClutA()
+ {
+ return this.CurveB != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsB()
+ {
+ return this.CurveB != null;
+ }
+
+ private void VerifyCurve(IccTagDataEntry[] curves, string name)
+ {
+ if (curves != null)
+ {
+ bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry));
+ Guard.IsTrue(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}");
+ }
+ }
+
+ private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1)
+ {
+ if (matrix3x1 != null)
+ {
+ Guard.IsTrue(matrix3x1.Length != 3, nameof(matrix3x1), "Matrix must have a size of three");
+ }
+
+ if (matrix3x3 != null)
+ {
+ bool isNot3By3 = matrix3x3.GetLength(0) != 3 || matrix3x3.GetLength(1) != 3;
+ Guard.IsTrue(isNot3By3, nameof(matrix3x3), "Matrix must have a size of three by three");
+ }
+ }
+
+ private Vector3? CreateMatrix3x1(float[] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Vector3(matrix[0], matrix[1], matrix[2]);
+ }
+
+ private Matrix4x4? CreateMatrix3x3(float[,] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs
new file mode 100644
index 000000000..cdc68eb91
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs
@@ -0,0 +1,89 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// The measurementType information refers only to the internal
+ /// profile data and is meant to provide profile makers an alternative
+ /// to the default measurement specifications.
+ ///
+ internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Observer
+ /// XYZ Backing values
+ /// Geometry
+ /// Flare
+ /// Illuminant
+ public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant)
+ : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Observer
+ /// XYZ Backing values
+ /// Geometry
+ /// Flare
+ /// Illuminant
+ /// Tag Signature
+ public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Measurement, tagSignature)
+ {
+ this.Observer = observer;
+ this.XyzBacking = xyzBacking;
+ this.Geometry = geometry;
+ this.Flare = flare;
+ this.Illuminant = illuminant;
+ }
+
+ ///
+ /// Gets the observer
+ ///
+ public IccStandardObserver Observer { get; }
+
+ ///
+ /// Gets the XYZ Backing values
+ ///
+ public Vector3 XyzBacking { get; }
+
+ ///
+ /// Gets the geometry
+ ///
+ public IccMeasurementGeometry Geometry { get; }
+
+ ///
+ /// Gets the flare
+ ///
+ public float Flare { get; }
+
+ ///
+ /// Gets the illuminant
+ ///
+ public IccStandardIlluminant Illuminant { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccMeasurementTagDataEntry entry)
+ {
+ return this.Observer == entry.Observer
+ && this.XyzBacking == entry.XyzBacking
+ && this.Geometry == entry.Geometry
+ && this.Flare == entry.Flare
+ && this.Illuminant == entry.Illuminant;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs
new file mode 100644
index 000000000..5c1c4a38b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This tag structure contains a set of records each referencing
+ /// a multilingual string associated with a profile.
+ ///
+ internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Localized Text
+ public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts)
+ : this(texts, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Localized Text
+ /// Tag Signature
+ public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature)
+ : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature)
+ {
+ Guard.NotNull(texts, nameof(texts));
+ this.Texts = texts;
+ }
+
+ ///
+ /// Gets the localized texts
+ ///
+ public IccLocalizedString[] Texts { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccMultiLocalizedUnicodeTagDataEntry entry)
+ {
+ return this.Texts.SequenceEqual(entry.Texts);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs
new file mode 100644
index 000000000..b04c338b0
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This structure represents a color transform, containing
+ /// a sequence of processing elements.
+ ///
+ internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Processing elements
+ public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Processing elements
+ /// Tag Signature
+ public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.MultiProcessElements, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length < 1, nameof(data), $"{nameof(data)} must have at least one element");
+
+ this.InputChannelCount = data[0].InputChannelCount;
+ this.OutputChannelCount = data[0].OutputChannelCount;
+ this.Data = data;
+
+ bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount);
+ Guard.IsTrue(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements");
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the processing elements
+ ///
+ public IccMultiProcessElement[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccMultiProcessElementsTagDataEntry entry)
+ {
+ return this.InputChannelCount == entry.InputChannelCount
+ && this.OutputChannelCount == entry.OutputChannelCount
+ && this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs
new file mode 100644
index 000000000..a51961009
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs
@@ -0,0 +1,137 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// The namedColor2Type is a count value and array of structures
+ /// that provide color coordinates for color names.
+ ///
+ internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The named colors
+ public IccNamedColor2TagDataEntry(IccNamedColor[] colors)
+ : this(0, null, null, colors, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Prefix
+ /// Suffix
+ /// /// The named colors
+ public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors)
+ : this(0, prefix, suffix, colors, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Vendor specific flags
+ /// Prefix
+ /// Suffix
+ /// The named colors
+ public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors)
+ : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The named colors
+ /// Tag Signature
+ public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature)
+ : this(0, null, null, colors, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Prefix
+ /// Suffix
+ /// The named colors
+ /// Tag Signature
+ public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature)
+ : this(0, prefix, suffix, colors, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Vendor specific flags
+ /// Prefix
+ /// Suffix
+ /// The named colors
+ /// Tag Signature
+ public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature)
+ : base(IccTypeSignature.NamedColor2, tagSignature)
+ {
+ Guard.NotNull(colors, nameof(colors));
+
+ int coordinateCount = 0;
+ if (colors.Length > 0)
+ {
+ coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0;
+ Guard.IsTrue(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors");
+ }
+
+ this.VendorFlags = vendorFlags;
+ this.CoordinateCount = coordinateCount;
+ this.Prefix = prefix;
+ this.Suffix = suffix;
+ this.Colors = colors;
+ }
+
+ ///
+ /// Gets the number of coordinates
+ ///
+ public int CoordinateCount { get; }
+
+ ///
+ /// Gets the prefix
+ ///
+ public string Prefix { get; }
+
+ ///
+ /// Gets the suffix
+ ///
+ public string Suffix { get; }
+
+ ///
+ /// Gets the vendor specific flags
+ ///
+ public int VendorFlags { get; }
+
+ ///
+ /// Gets the named colors
+ ///
+ public IccNamedColor[] Colors { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccNamedColor2TagDataEntry entry)
+ {
+ return this.CoordinateCount == entry.CoordinateCount
+ && this.Prefix == entry.Prefix
+ && this.Suffix == entry.Suffix
+ && this.VendorFlags == entry.VendorFlags
+ && this.Colors.SequenceEqual(entry.Colors);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs
new file mode 100644
index 000000000..bd329226a
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// The parametricCurveType describes a one-dimensional curve by
+ /// specifying one of a predefined set of functions using the parameters.
+ ///
+ internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curve
+ public IccParametricCurveTagDataEntry(IccParametricCurve curve)
+ : this(curve, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curve
+ /// Tag Signature
+ public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ParametricCurve, tagSignature)
+ {
+ this.Curve = curve;
+ }
+
+ ///
+ /// Gets the Curve
+ ///
+ public IccParametricCurve Curve { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccParametricCurveTagDataEntry entry)
+ {
+ return this.Curve == entry.Curve;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs
new file mode 100644
index 000000000..56b3eea14
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs
@@ -0,0 +1,54 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type is an array of structures, each of which contains information
+ /// from the header fields and tags from the original profiles which were
+ /// combined to create the final profile.
+ ///
+ internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Descriptions
+ public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions)
+ : this(descriptions, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Descriptions
+ /// Tag Signature
+ public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ProfileSequenceDesc, tagSignature)
+ {
+ Guard.NotNull(descriptions, nameof(descriptions));
+ this.Descriptions = descriptions;
+ }
+
+ ///
+ /// Gets the profile descriptions
+ ///
+ public IccProfileDescription[] Descriptions { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccProfileSequenceDescTagDataEntry entry)
+ {
+ return this.Descriptions.SequenceEqual(entry.Descriptions);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs
new file mode 100644
index 000000000..4151ed77c
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type is an array of structures, each of which contains information
+ /// for identification of a profile used in a sequence.
+ ///
+ internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Identifiers
+ public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Identifiers
+ /// Tag Signature
+ public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the profile identifiers
+ ///
+ public IccProfileSequenceIdentifier[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccProfileSequenceIdentifierTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs
new file mode 100644
index 000000000..afa73cca4
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs
@@ -0,0 +1,66 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// The purpose of this tag type is to provide a mechanism to relate physical
+ /// colorant amounts with the normalized device codes produced by lut8Type, lut16Type,
+ /// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can
+ /// be made for variation in the device without having to produce a new profile.
+ ///
+ internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curves
+ public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves)
+ : this(curves, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curves
+ /// Tag Signature
+ public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ResponseCurveSet16, tagSignature)
+ {
+ Guard.NotNull(curves, nameof(curves));
+ Guard.IsTrue(curves.Length < 1, nameof(curves), $"{nameof(curves)} needs at least one element");
+
+ this.Curves = curves;
+ this.ChannelCount = (ushort)curves[0].ResponseArrays.Length;
+
+ Guard.IsTrue(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels");
+ }
+
+ ///
+ /// Gets the number of channels
+ ///
+ public ushort ChannelCount { get; }
+
+ ///
+ /// Gets the curves
+ ///
+ public IccResponseCurve[] Curves { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccResponseCurveSet16TagDataEntry entry)
+ {
+ return this.ChannelCount == entry.ChannelCount
+ && this.Curves.SequenceEqual(entry.Curves);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs
new file mode 100644
index 000000000..42fd18504
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Typically this type is used for registered tags that can
+ /// be displayed on many development systems as a sequence of four characters.
+ ///
+ internal sealed class IccSignatureTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Signature
+ public IccSignatureTagDataEntry(string signatureData)
+ : this(signatureData, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Signature
+ /// Tag Signature
+ public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Signature, tagSignature)
+ {
+ Guard.NotNull(signatureData, nameof(signatureData));
+ this.SignatureData = signatureData;
+ }
+
+ ///
+ /// Gets the Signature
+ ///
+ public string SignatureData { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccSignatureTagDataEntry entry)
+ {
+ return this.SignatureData == entry.SignatureData;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs
new file mode 100644
index 000000000..19dacb421
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs
@@ -0,0 +1,85 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// The TextDescriptionType contains three types of text description.
+ ///
+ internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// ASCII text
+ /// Unicode text
+ /// ScriptCode text
+ /// Unicode Language-Code
+ /// ScriptCode Code
+ public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode)
+ : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// ASCII text
+ /// Unicode text
+ /// ScriptCode text
+ /// Unicode Language-Code
+ /// ScriptCode Code
+ /// Tag Signature
+ public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature)
+ : base(IccTypeSignature.TextDescription, tagSignature)
+ {
+ this.Ascii = ascii;
+ this.Unicode = unicode;
+ this.ScriptCode = scriptCode;
+ this.UnicodeLanguageCode = unicodeLanguageCode;
+ this.ScriptCodeCode = scriptCodeCode;
+ }
+
+ ///
+ /// Gets the ASCII text
+ ///
+ public string Ascii { get; }
+
+ ///
+ /// Gets the Unicode text
+ ///
+ public string Unicode { get; }
+
+ ///
+ /// Gets the ScriptCode text
+ ///
+ public string ScriptCode { get; }
+
+ ///
+ /// Gets the Unicode Language-Code
+ ///
+ public uint UnicodeLanguageCode { get; }
+
+ ///
+ /// Gets the ScriptCode Code
+ ///
+ public ushort ScriptCodeCode { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccTextDescriptionTagDataEntry entry)
+ {
+ return this.Ascii == entry.Ascii
+ && this.Unicode == entry.Unicode
+ && this.ScriptCode == entry.ScriptCode
+ && this.UnicodeLanguageCode == entry.UnicodeLanguageCode
+ && this.ScriptCodeCode == entry.ScriptCodeCode;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs
new file mode 100644
index 000000000..746a050f5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// This is a simple text structure that contains a text string.
+ ///
+ internal sealed class IccTextTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Text
+ public IccTextTagDataEntry(string text)
+ : this(text, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Text
+ /// Tag Signature
+ public IccTextTagDataEntry(string text, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Text, tagSignature)
+ {
+ Guard.NotNull(text, nameof(text));
+ this.Text = text;
+ }
+
+ ///
+ /// Gets the Text
+ ///
+ public string Text { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccTextTagDataEntry entry)
+ {
+ return this.Text == entry.Text;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs
new file mode 100644
index 000000000..8ac0995dc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type represents an array of doubles (from 32bit values).
+ ///
+ internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUFix16ArrayTagDataEntry(float[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.U16Fixed16Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public float[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUFix16ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs
new file mode 100644
index 000000000..ec11e4971
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type represents an array of unsigned shorts.
+ ///
+ internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt16ArrayTagDataEntry(ushort[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt16Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public ushort[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt16ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs
new file mode 100644
index 000000000..12acf1abe
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type represents an array of unsigned 32bit integers.
+ ///
+ internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt32ArrayTagDataEntry(uint[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt32Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public uint[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt32ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs
new file mode 100644
index 000000000..ec65f82d5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type represents an array of unsigned 64bit integers.
+ ///
+ internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt64ArrayTagDataEntry(ulong[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt64Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public ulong[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt64ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs
new file mode 100644
index 000000000..f452a825f
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This type represents an array of bytes.
+ ///
+ internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt8ArrayTagDataEntry(byte[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt8Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public byte[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt8ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs
new file mode 100644
index 000000000..50dc3a785
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+
+ ///
+ /// This tag stores data of an unknown tag data entry
+ ///
+ internal sealed class IccUnknownTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data of the entry
+ public IccUnknownTagDataEntry(byte[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data of the entry
+ /// Tag Signature
+ public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Unknown, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the raw data of the entry
+ ///
+ public byte[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUnknownTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs
new file mode 100644
index 000000000..f279b19c4
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// This type represents a set of viewing condition parameters.
+ ///
+ internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// XYZ values of Illuminant
+ /// XYZ values of Surrounding
+ /// Illuminant
+ public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant)
+ : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// XYZ values of Illuminant
+ /// XYZ values of Surrounding
+ /// Illuminant
+ /// Tag Signature
+ public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ViewingConditions, tagSignature)
+ {
+ this.IlluminantXyz = illuminantXyz;
+ this.SurroundXyz = surroundXyz;
+ this.Illuminant = illuminant;
+ }
+
+ ///
+ /// Gets the XYZ values of Illuminant
+ ///
+ public Vector3 IlluminantXyz { get; }
+
+ ///
+ /// Gets the XYZ values of Surrounding
+ ///
+ public Vector3 SurroundXyz { get; }
+
+ ///
+ /// Gets the illuminant
+ ///
+ public IccStandardIlluminant Illuminant { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccViewingConditionsTagDataEntry entry)
+ {
+ return this.IlluminantXyz == entry.IlluminantXyz
+ && this.SurroundXyz == entry.SurroundXyz
+ && this.Illuminant == entry.Illuminant;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs
new file mode 100644
index 000000000..e33e3d87b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// The XYZType contains an array of XYZ values.
+ ///
+ internal sealed class IccXyzTagDataEntry : IccTagDataEntry
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The XYZ numbers
+ public IccXyzTagDataEntry(Vector3[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The XYZ numbers
+ /// Tag Signature
+ public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Xyz, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the XYZ numbers
+ ///
+ public Vector3[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccXyzTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs
new file mode 100644
index 000000000..52c603d1c
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs
@@ -0,0 +1,175 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Color Lookup Table
+ ///
+ internal sealed class IccClut : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The CLUT values
+ /// The gridpoint count
+ /// The data type of this CLUT
+ public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type)
+ {
+ Guard.NotNull(values, nameof(values));
+ Guard.NotNull(gridPointCount, nameof(gridPointCount));
+
+ this.Values = values;
+ this.DataType = IccClutDataType.Float;
+ this.InputChannelCount = gridPointCount.Length;
+ this.OutputChannelCount = values[0].Length;
+ this.GridPointCount = gridPointCount;
+ this.CheckValues();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The CLUT values
+ /// The gridpoint count
+ public IccClut(ushort[][] values, byte[] gridPointCount)
+ {
+ Guard.NotNull(values, nameof(values));
+ Guard.NotNull(gridPointCount, nameof(gridPointCount));
+
+ const float max = ushort.MaxValue;
+
+ this.Values = new float[values.Length][];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = new float[values[i].Length];
+ for (int j = 0; j < values[i].Length; j++)
+ {
+ this.Values[i][j] = values[i][j] / max;
+ }
+ }
+
+ this.DataType = IccClutDataType.UInt16;
+ this.InputChannelCount = gridPointCount.Length;
+ this.OutputChannelCount = values[0].Length;
+ this.GridPointCount = gridPointCount;
+ this.CheckValues();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The CLUT values
+ /// The gridpoint count
+ public IccClut(byte[][] values, byte[] gridPointCount)
+ {
+ Guard.NotNull(values, nameof(values));
+ Guard.NotNull(gridPointCount, nameof(gridPointCount));
+
+ const float max = byte.MaxValue;
+
+ this.Values = new float[values.Length][];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = new float[values[i].Length];
+ for (int j = 0; j < values[i].Length; j++)
+ {
+ this.Values[i][j] = values[i][j] / max;
+ }
+ }
+
+ this.DataType = IccClutDataType.UInt8;
+ this.InputChannelCount = gridPointCount.Length;
+ this.OutputChannelCount = values[0].Length;
+ this.GridPointCount = gridPointCount;
+ this.CheckValues();
+ }
+
+ ///
+ /// Gets the values that make up this table
+ ///
+ public float[][] Values { get; }
+
+ ///
+ /// Gets or sets the CLUT data type (important when writing a profile)
+ ///
+ public IccClutDataType DataType { get; set; }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the number of grid points per input channel
+ ///
+ public byte[] GridPointCount { get; }
+
+ ///
+ public bool Equals(IccClut other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.DataType == other.DataType
+ && this.InputChannelCount == other.InputChannelCount
+ && this.OutputChannelCount == other.OutputChannelCount
+ && this.GridPointCount.SequenceEqual(other.GridPointCount)
+ && this.EqualsValuesArray(other);
+ }
+
+ private bool EqualsValuesArray(IccClut other)
+ {
+ if (this.Values.Length != other.Values.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.Values.Length; i++)
+ {
+ if (!this.Values[i].SequenceEqual(other.Values[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void CheckValues()
+ {
+ Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount));
+ Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount));
+
+ bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount);
+ Guard.IsTrue(isLengthDifferent, nameof(this.Values), "The number of output values varies");
+
+ int length = 0;
+ for (int i = 0; i < this.InputChannelCount; i++)
+ {
+ length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount);
+ }
+
+ length /= this.InputChannelCount;
+
+ Guard.IsTrue(this.Values.Length != length, nameof(this.Values), "Length of values array does not match the grid points");
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs
new file mode 100644
index 000000000..bd8955075
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Entry of ICC colorant table
+ ///
+ internal struct IccColorantTableEntry : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of the colorant
+ public IccColorantTableEntry(string name)
+ : this(name, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of the colorant
+ /// First PCS value
+ /// Second PCS value
+ /// Third PCS value
+ public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3)
+ {
+ Guard.NotNull(name, nameof(name));
+
+ this.Name = name;
+ this.Pcs1 = pcs1;
+ this.Pcs2 = pcs2;
+ this.Pcs3 = pcs3;
+ }
+
+ ///
+ /// Gets the colorant name
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the first PCS value
+ ///
+ public ushort Pcs1 { get; }
+
+ ///
+ /// Gets the second PCS value
+ ///
+ public ushort Pcs2 { get; }
+
+ ///
+ /// Gets the third PCS value
+ ///
+ public ushort Pcs3 { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccColorantTableEntry) && this.Equals((IccColorantTableEntry)other);
+ }
+
+ ///
+ public bool Equals(IccColorantTableEntry other)
+ {
+ return this.Name == other.Name
+ && this.Pcs1 == other.Pcs1
+ && this.Pcs2 == other.Pcs2
+ && this.Pcs3 == other.Pcs3;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Name.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Pcs1.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Pcs2.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Pcs3.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs
new file mode 100644
index 000000000..0e2772963
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Globalization;
+
+ ///
+ /// A string with a specific locale
+ ///
+ internal sealed class IccLocalizedString : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// The culture will be
+ ///
+ /// The text value of this string
+ public IccLocalizedString(string text)
+ : this(CultureInfo.CurrentCulture, text)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// The culture will be
+ ///
+ /// The culture of this string
+ /// The text value of this string
+ public IccLocalizedString(CultureInfo culture, string text)
+ {
+ Guard.NotNull(culture, nameof(culture));
+ Guard.NotNull(text, nameof(text));
+
+ this.Culture = culture;
+ this.Text = text;
+ }
+
+ ///
+ /// Gets the actual text value
+ ///
+ public string Text { get; }
+
+ ///
+ /// Gets the culture of the text
+ ///
+ public CultureInfo Culture { get; }
+
+ ///
+ public bool Equals(IccLocalizedString other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Culture.Equals(other.Culture)
+ && this.Text == other.Text;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Culture.Name}: {this.Text}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs
new file mode 100644
index 000000000..9b86e1113
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Lookup Table
+ ///
+ internal sealed class IccLut : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The LUT values
+ public IccLut(float[] values)
+ {
+ Guard.NotNull(values, nameof(values));
+ this.Values = values;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The LUT values
+ public IccLut(ushort[] values)
+ {
+ Guard.NotNull(values, nameof(values));
+
+ const float max = ushort.MaxValue;
+
+ this.Values = new float[values.Length];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = values[i] / max;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The LUT values
+ public IccLut(byte[] values)
+ {
+ Guard.NotNull(values, nameof(values));
+
+ const float max = byte.MaxValue;
+
+ this.Values = new float[values.Length];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = values[i] / max;
+ }
+ }
+
+ ///
+ /// Gets the values that make up this table
+ ///
+ public float[] Values { get; }
+
+ ///
+ public bool Equals(IccLut other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Values.SequenceEqual(other.Values);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs
new file mode 100644
index 000000000..38417f873
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs
@@ -0,0 +1,110 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// A specific color with a name
+ ///
+ internal struct IccNamedColor : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of the color
+ /// Coordinates of the color in the profiles PCS
+ /// Coordinates of the color in the profiles Device-Space
+ public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates)
+ {
+ Guard.NotNull(name, nameof(name));
+ Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates));
+ Guard.IsTrue(pcsCoordinates.Length != 3, nameof(pcsCoordinates), "Must have a length of 3");
+
+ this.Name = name;
+ this.PcsCoordinates = pcsCoordinates;
+ this.DeviceCoordinates = deviceCoordinates;
+ }
+
+ ///
+ /// Gets the name of the color
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the coordinates of the color in the profiles PCS
+ ///
+ public ushort[] PcsCoordinates { get; }
+
+ ///
+ /// Gets the coordinates of the color in the profiles Device-Space
+ ///
+ public ushort[] DeviceCoordinates { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccNamedColor left, IccNamedColor right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccNamedColor left, IccNamedColor right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccNamedColor) && this.Equals((IccNamedColor)other);
+ }
+
+ ///
+ public bool Equals(IccNamedColor other)
+ {
+ return this.Name == other.Name
+ && this.PcsCoordinates.SequenceEqual(other.PcsCoordinates)
+ && this.DeviceCoordinates.SequenceEqual(other.DeviceCoordinates);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Name.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.PcsCoordinates.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.DeviceCoordinates.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return this.Name;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs
new file mode 100644
index 000000000..768aead0e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs
@@ -0,0 +1,91 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Position of an object within an ICC profile
+ ///
+ internal struct IccPositionNumber : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Offset in bytes
+ /// Size in bytes
+ public IccPositionNumber(uint offset, uint size)
+ {
+ this.Offset = offset;
+ this.Size = size;
+ }
+
+ ///
+ /// Gets the offset in bytes
+ ///
+ public uint Offset { get; }
+
+ ///
+ /// Gets the size in bytes
+ ///
+ public uint Size { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccPositionNumber left, IccPositionNumber right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccPositionNumber left, IccPositionNumber right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccPositionNumber) && this.Equals((IccPositionNumber)other);
+ }
+
+ ///
+ public bool Equals(IccPositionNumber other)
+ {
+ return this.Offset == other.Offset
+ && this.Size == other.Size;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return unchecked((int)(this.Offset ^ this.Size));
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Offset}; {this.Size}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs
new file mode 100644
index 000000000..dc5c7c1aa
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs
@@ -0,0 +1,90 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// ICC Profile description
+ ///
+ internal sealed class IccProfileDescription : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Device Manufacturer
+ /// Device Model
+ /// Device Attributes
+ /// Technology Information
+ /// Device Manufacturer Info
+ /// Device Model Info
+ public IccProfileDescription(
+ uint deviceManufacturer,
+ uint deviceModel,
+ IccDeviceAttribute deviceAttributes,
+ IccProfileTag technologyInformation,
+ IccLocalizedString[] deviceManufacturerInfo,
+ IccLocalizedString[] deviceModelInfo)
+ {
+ Guard.NotNull(deviceManufacturerInfo, nameof(deviceManufacturerInfo));
+ Guard.NotNull(deviceModelInfo, nameof(deviceModelInfo));
+
+ this.DeviceManufacturer = deviceManufacturer;
+ this.DeviceModel = deviceModel;
+ this.DeviceAttributes = deviceAttributes;
+ this.TechnologyInformation = technologyInformation;
+ this.DeviceManufacturerInfo = deviceManufacturerInfo;
+ this.DeviceModelInfo = deviceModelInfo;
+ }
+
+ ///
+ /// Gets the device manufacturer
+ ///
+ public uint DeviceManufacturer { get; }
+
+ ///
+ /// Gets the device model
+ ///
+ public uint DeviceModel { get; }
+
+ ///
+ /// Gets the device attributes
+ ///
+ public IccDeviceAttribute DeviceAttributes { get; }
+
+ ///
+ /// Gets the technology information
+ ///
+ public IccProfileTag TechnologyInformation { get; }
+
+ ///
+ /// Gets the device manufacturer info
+ ///
+ public IccLocalizedString[] DeviceManufacturerInfo { get; }
+
+ ///
+ /// Gets the device model info
+ ///
+ public IccLocalizedString[] DeviceModelInfo { get; }
+
+ ///
+ public bool Equals(IccProfileDescription other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.DeviceManufacturer == other.DeviceManufacturer
+ && this.DeviceModel == other.DeviceModel
+ && this.DeviceAttributes == other.DeviceAttributes
+ && this.TechnologyInformation == other.TechnologyInformation
+ && this.DeviceManufacturerInfo.SequenceEqual(other.DeviceManufacturerInfo)
+ && this.DeviceModelInfo.SequenceEqual(other.DeviceModelInfo);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs
new file mode 100644
index 000000000..532b65564
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs
@@ -0,0 +1,138 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// ICC Profile ID
+ ///
+ internal struct IccProfileId : IEquatable
+ {
+ ///
+ /// A profile ID with all values set to zero
+ ///
+ public static readonly IccProfileId Zero = new IccProfileId(0, 0, 0, 0);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Part 1 of the ID
+ /// Part 2 of the ID
+ /// Part 3 of the ID
+ /// Part 4 of the ID
+ public IccProfileId(uint p1, uint p2, uint p3, uint p4)
+ {
+ this.Part1 = p1;
+ this.Part2 = p2;
+ this.Part3 = p3;
+ this.Part4 = p4;
+ }
+
+ ///
+ /// Gets the first part of the ID
+ ///
+ public uint Part1 { get; }
+
+ ///
+ /// Gets the second part of the ID
+ ///
+ public uint Part2 { get; }
+
+ ///
+ /// Gets the third part of the ID
+ ///
+ public uint Part3 { get; }
+
+ ///
+ /// Gets the fourth part of the ID
+ ///
+ public uint Part4 { get; }
+
+ ///
+ /// Gets a value indicating whether the ID is set or just consists of zeros
+ ///
+ public bool IsSet
+ {
+ get
+ {
+ return this.Part1 != 0
+ && this.Part2 != 0
+ && this.Part3 != 0
+ && this.Part4 != 0;
+ }
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccProfileId left, IccProfileId right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccProfileId left, IccProfileId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccProfileId) && this.Equals((IccProfileId)other);
+ }
+
+ ///
+ public bool Equals(IccProfileId other)
+ {
+ return this.Part1 == other.Part1
+ && this.Part2 == other.Part2
+ && this.Part3 == other.Part3
+ && this.Part4 == other.Part4;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Part1.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Part2.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Part3.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Part4.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}";
+ }
+
+ private static string ToHex(uint value)
+ {
+ return value.ToString("X").PadLeft(8, '0');
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs
new file mode 100644
index 000000000..c6d8519fc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Description of a profile within a sequence
+ ///
+ internal sealed class IccProfileSequenceIdentifier : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// ID of the profile
+ /// Description of the profile
+ public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description)
+ {
+ Guard.NotNull(description, nameof(description));
+
+ this.Id = id;
+ this.Description = description;
+ }
+
+ ///
+ /// Gets the ID of the profile
+ ///
+ public IccProfileId Id { get; }
+
+ ///
+ /// Gets the description of the profile
+ ///
+ public IccLocalizedString[] Description { get; }
+
+ ///
+ public bool Equals(IccProfileSequenceIdentifier other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Id.Equals(other.Id)
+ && this.Description.SequenceEqual(other.Description);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs
new file mode 100644
index 000000000..5c58aa1b1
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs
@@ -0,0 +1,96 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Associates a normalized device code with a measurement value
+ ///
+ internal struct IccResponseNumber : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Device Code
+ /// Measurement Value
+ public IccResponseNumber(ushort deviceCode, float measurementValue)
+ {
+ this.DeviceCode = deviceCode;
+ this.MeasurementValue = measurementValue;
+ }
+
+ ///
+ /// Gets the device code
+ ///
+ public ushort DeviceCode { get; }
+
+ ///
+ /// Gets the measurement value
+ ///
+ public float MeasurementValue { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccResponseNumber left, IccResponseNumber right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccResponseNumber left, IccResponseNumber right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccResponseNumber) && this.Equals((IccResponseNumber)other);
+ }
+
+ ///
+ public bool Equals(IccResponseNumber other)
+ {
+ return this.DeviceCode == other.DeviceCode
+ && this.MeasurementValue == other.MeasurementValue;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.DeviceCode.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.MeasurementValue.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs
new file mode 100644
index 000000000..eb7f0c63b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs
@@ -0,0 +1,105 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Entry of ICC tag table
+ ///
+ internal struct IccTagTableEntry : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Signature of the tag
+ /// Offset of entry in bytes
+ /// Size of entry in bytes
+ public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize)
+ {
+ this.Signature = signature;
+ this.Offset = offset;
+ this.DataSize = dataSize;
+ }
+
+ ///
+ /// Gets the signature of the tag
+ ///
+ public IccProfileTag Signature { get; }
+
+ ///
+ /// Gets the offset of entry in bytes
+ ///
+ public uint Offset { get; }
+
+ ///
+ /// Gets the size of entry in bytes
+ ///
+ public uint DataSize { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccProfileId) && this.Equals((IccProfileId)other);
+ }
+
+ ///
+ public bool Equals(IccTagTableEntry other)
+ {
+ return this.Signature == other.Signature
+ && this.Offset == other.Offset
+ && this.DataSize == other.DataSize;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Signature.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Offset.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.DataSize.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})";
+ }
+ }
+}