diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs
new file mode 100644
index 0000000000..e9b20414c3
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs
@@ -0,0 +1,221 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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()
+ {
+ IccFormulaCurveType type = (IccFormulaCurveType)this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ double gamma, a, b, c, d, e;
+ gamma = a = b = c = d = e = 0;
+
+ if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2)
+ {
+ gamma = this.ReadSingle();
+ }
+
+ a = this.ReadSingle();
+ b = this.ReadSingle();
+ c = this.ReadSingle();
+
+ if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3)
+ {
+ d = this.ReadSingle();
+ }
+
+ if (type == IccFormulaCurveType.Type3)
+ {
+ 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs
new file mode 100644
index 0000000000..1f62271f89
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs
@@ -0,0 +1,176 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
new file mode 100644
index 0000000000..23ad9feb22
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
@@ -0,0 +1,65 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs
new file mode 100644
index 0000000000..acb2645f79
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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));
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs
new file mode 100644
index 0000000000..8d25904db9
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs
@@ -0,0 +1,157 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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.ReadInt32();
+
+ int major = (version >> 24) & 0xFF;
+ 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.ReadInt64();
+ 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());
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
new file mode 100644
index 0000000000..85d80c7a84
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
@@ -0,0 +1,178 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Text;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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)
+ {
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ Guard.MustBeGreaterThan(length, 0, nameof(length));
+ string value = AsciiEncoding.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)
+ {
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ Guard.MustBeGreaterThan(length, 0, nameof(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 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
new file mode 100644
index 0000000000..d1754b0f78
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
@@ -0,0 +1,795 @@
+//
+// 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;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// 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.ReadInt32();
+ 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);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
new file mode 100644
index 0000000000..8a1dab81b3
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.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.Text;
+ using ImageSharp.IO;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
+ private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
+
+ ///
+ /// 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)
+ {
+ Guard.NotNull(data, nameof(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);
+ }
+
+ ///
+ /// 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs
new file mode 100644
index 0000000000..120a6f299d
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs
@@ -0,0 +1,179 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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)
+ {
+ ushort typeValue = (ushort)value.Type;
+ int count = this.WriteUInt16(typeValue);
+ count += this.WriteEmpty(2);
+
+ if (typeValue >= 0 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.G);
+ }
+
+ if (typeValue > 0 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.A);
+ count += this.WriteFix16(value.B);
+ }
+
+ if (typeValue > 1 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.C);
+ }
+
+ if (typeValue > 2 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.D);
+ }
+
+ if (typeValue == 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((ushort)value.Type);
+ count += this.WriteEmpty(2);
+
+ if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2)
+ {
+ count += this.WriteSingle((float)value.Gamma);
+ }
+
+ count += this.WriteSingle((float)value.A);
+ count += this.WriteSingle((float)value.B);
+ count += this.WriteSingle((float)value.C);
+
+ if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3)
+ {
+ count += this.WriteSingle((float)value.D);
+ }
+
+ if (value.Type == IccFormulaCurveType.Type3)
+ {
+ 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
new file mode 100644
index 0000000000..70325eca32
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
@@ -0,0 +1,128 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
new file mode 100644
index 0000000000..b238648659
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
@@ -0,0 +1,160 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs
new file mode 100644
index 0000000000..39b393abb7
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs
new file mode 100644
index 0000000000..2aa1272623
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs
@@ -0,0 +1,123 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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.WriteInt64((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));
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs
new file mode 100644
index 0000000000..d6e9952b9c
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs
@@ -0,0 +1,217 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Text;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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)
+ {
+ byte[] data = AsciiEncoding.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));
+
+ byte[] textData = AsciiEncoding.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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
new file mode 100644
index 0000000000..a87a0a187e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
@@ -0,0 +1,913 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// 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.WriteInt32(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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs
new file mode 100644
index 0000000000..fbd5803b69
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs
@@ -0,0 +1,234 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.IO;
+ using System.Text;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
+ private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
+
+ 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;
+ }
+
+ ///
+ /// 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;
+ }
+
+ ///
+ /// 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;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs
deleted file mode 100644
index 30e31d3584..0000000000
--- a/src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs
+++ /dev/null
@@ -1,1711 +0,0 @@
-//
-// 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;
- private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
-
- ///
- /// 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)
- {
- Guard.NotNull(data, nameof(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)
- {
- if (length == 0)
- {
- return string.Empty;
- }
-
- Guard.MustBeGreaterThan(length, 0, nameof(length));
- string value = AsciiEncoding.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)
- {
- if (length == 0)
- {
- return string.Empty;
- }
-
- Guard.MustBeGreaterThan(length, 0, nameof(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 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.ReadInt32();
-
- int major = (version >> 24) & 0xFF;
- 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.ReadInt64();
- 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.ReadInt32();
- 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()
- {
- IccFormulaCurveType type = (IccFormulaCurveType)this.ReadUInt16();
- this.AddIndex(2); // 2 bytes reserved
- double gamma, a, b, c, d, e;
- gamma = a = b = c = d = e = 0;
-
- if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2)
- {
- gamma = this.ReadSingle();
- }
-
- a = this.ReadSingle();
- b = this.ReadSingle();
- c = this.ReadSingle();
-
- if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3)
- {
- d = this.ReadSingle();
- }
-
- if (type == IccFormulaCurveType.Type3)
- {
- 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
deleted file mode 100644
index f2a048fbce..0000000000
--- a/src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs
+++ /dev/null
@@ -1,1970 +0,0 @@
-//
-// 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 Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
-
- 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)
- {
- byte[] data = AsciiEncoding.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));
-
- byte[] textData = AsciiEncoding.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;
- }
-
- #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.WriteInt64((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.WriteInt32(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)
- {
- ushort typeValue = (ushort)value.Type;
- int count = this.WriteUInt16(typeValue);
- count += this.WriteEmpty(2);
-
- if (typeValue >= 0 && typeValue <= 4)
- {
- count += this.WriteFix16(value.G);
- }
-
- if (typeValue > 0 && typeValue <= 4)
- {
- count += this.WriteFix16(value.A);
- count += this.WriteFix16(value.B);
- }
-
- if (typeValue > 1 && typeValue <= 4)
- {
- count += this.WriteFix16(value.C);
- }
-
- if (typeValue > 2 && typeValue <= 4)
- {
- count += this.WriteFix16(value.D);
- }
-
- if (typeValue == 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((ushort)value.Type);
- count += this.WriteEmpty(2);
-
- if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2)
- {
- count += this.WriteSingle((float)value.Gamma);
- }
-
- count += this.WriteSingle((float)value.A);
- count += this.WriteSingle((float)value.B);
- count += this.WriteSingle((float)value.C);
-
- if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3)
- {
- count += this.WriteSingle((float)value.D);
- }
-
- if (value.Type == IccFormulaCurveType.Type3)
- {
- 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
- }
-}