From 4152134a976d0ebd70923cdbec64da720b56cbe1 Mon Sep 17 00:00:00 2001 From: Johannes Bildstein Date: Thu, 23 Mar 2017 03:58:20 +0100 Subject: [PATCH] initial version for reading and writing ICC profiles --- .../Profiles/ICC/Curves/IccCurveSegment.cs | 40 + .../ICC/Curves/IccFormulaCurveElement.cs | 84 + .../ICC/Curves/IccOneDimensionalCurve.cs | 55 + .../Profiles/ICC/Curves/IccParametricCurve.cs | 147 ++ .../Profiles/ICC/Curves/IccResponseCurve.cs | 82 + .../ICC/Curves/IccSampledCurveElement.cs | 39 + .../Profiles/ICC/Enums/IccClutDataType.cs | 28 + .../Profiles/ICC/Enums/IccColorSpaceType.cs | 138 ++ .../Profiles/ICC/Enums/IccColorantEncoding.cs | 38 + .../ICC/Enums/IccCurveMeasurementEncodings.cs | 62 + .../ICC/Enums/IccCurveSegmentSignature.cs | 23 + .../Profiles/ICC/{ => Enums}/IccDataType.cs | 2 +- .../Profiles/ICC/Enums/IccDeviceAttribute.cs | 56 + .../ICC/Enums/IccMeasurementGeometry.cs | 28 + .../Enums/IccMultiProcessElementSignature.cs | 38 + .../ICC/Enums/IccPrimaryPlatformType.cs | 38 + .../Profiles/ICC/Enums/IccProfileClass.cs | 21 + .../ICC/Enums/IccProfileConversionMethod.cs | 24 + .../Profiles/ICC/Enums/IccProfileFlag.cs | 26 + .../Profiles/ICC/{ => Enums}/IccProfileTag.cs | 39 +- .../Profiles/ICC/Enums/IccRenderingIntent.cs | 18 + .../Profiles/ICC/Enums/IccSignatureName.cs | 82 + .../ICC/Enums/IccStandardIlluminant.cs | 58 + .../Profiles/ICC/Enums/IccStandardObserver.cs | 28 + .../Profiles/ICC/Enums/IccTypeSignature.cs | 112 + .../Exceptions/InvalidIccProfileException.cs | 42 + .../MetaData/Profiles/ICC/IccDataReader.cs | 1727 ++++++++++++++ .../MetaData/Profiles/ICC/IccDataWriter.cs | 2003 +++++++++++++++++ .../MetaData/Profiles/ICC/IccProfile.cs | 107 + .../MetaData/Profiles/ICC/IccProfileHeader.cs | 103 + .../MetaData/Profiles/ICC/IccReader.cs | 113 + .../MetaData/Profiles/ICC/IccTagDataEntry.cs | 68 + .../MetaData/Profiles/ICC/IccWriter.cs | 106 + .../IccBAcsProcessElement.cs | 23 + .../IccClutProcessElement.cs | 40 + .../IccCurveSetProcessElement.cs | 42 + .../IccEAcsProcessElement.cs | 23 + .../IccMatrixProcessElement.cs | 71 + .../IccMultiProcessElement.cs | 64 + .../IccChromaticityTagDataEntry.cs | 136 ++ .../IccColorantOrderTagDataEntry.cs | 55 + .../IccColorantTableTagDataEntry.cs | 56 + .../TagDataEntries/IccCurveTagDataEntry.cs | 122 + .../ICC/TagDataEntries/IccDataTagDataEntry.cs | 92 + .../TagDataEntries/IccDateTimeTagDataEntry.cs | 51 + .../IccFix16ArrayTagDataEntry.cs | 52 + .../TagDataEntries/IccLut16TagDataEntry.cs | 153 ++ .../ICC/TagDataEntries/IccLut8TagDataEntry.cs | 156 ++ .../TagDataEntries/IccLutAToBTagDataEntry.cs | 274 +++ .../TagDataEntries/IccLutBToATagDataEntry.cs | 274 +++ .../IccMeasurementTagDataEntry.cs | 89 + .../IccMultiLocalizedUnicodeTagDataEntry.cs | 53 + .../IccMultiProcessElementsTagDataEntry.cs | 72 + .../IccNamedColor2TagDataEntry.cs | 137 ++ .../IccParametricCurveTagDataEntry.cs | 50 + .../IccProfileSequenceDescTagDataEntry.cs | 54 + ...ccProfileSequenceIdentifierTagDataEntry.cs | 53 + .../IccResponseCurveSet16TagDataEntry.cs | 66 + .../IccSignatureTagDataEntry.cs | 51 + .../IccTextDescriptionTagDataEntry.cs | 85 + .../ICC/TagDataEntries/IccTextTagDataEntry.cs | 50 + .../IccUFix16ArrayTagDataEntry.cs | 52 + .../IccUInt16ArrayTagDataEntry.cs | 52 + .../IccUInt32ArrayTagDataEntry.cs | 52 + .../IccUInt64ArrayTagDataEntry.cs | 52 + .../IccUInt8ArrayTagDataEntry.cs | 52 + .../TagDataEntries/IccUnknownTagDataEntry.cs | 52 + .../IccViewingConditionsTagDataEntry.cs | 69 + .../ICC/TagDataEntries/IccXyzTagDataEntry.cs | 53 + .../MetaData/Profiles/ICC/Various/IccClut.cs | 175 ++ .../ICC/Various/IccColorantTableEntry.cs | 125 + .../ICC/Various/IccLocalizedString.cs | 69 + .../MetaData/Profiles/ICC/Various/IccLut.cs | 81 + .../Profiles/ICC/Various/IccNamedColor.cs | 110 + .../Profiles/ICC/Various/IccPositionNumber.cs | 91 + .../ICC/Various/IccProfileDescription.cs | 90 + .../Profiles/ICC/Various/IccProfileId.cs | 138 ++ .../Various/IccProfileSequenceIdentifier.cs | 51 + .../Profiles/ICC/Various/IccResponseNumber.cs | 96 + .../Profiles/ICC/Various/IccTagTableEntry.cs | 105 + 80 files changed, 9566 insertions(+), 18 deletions(-) create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs rename src/ImageSharp/MetaData/Profiles/ICC/{ => Enums}/IccDataType.cs (98%) create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileConversionMethod.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs rename src/ImageSharp/MetaData/Profiles/ICC/{ => Enums}/IccProfileTag.cs (95%) create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs create mode 100644 src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs 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})"; + } + } +}