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 - } -}