Browse Source

split DataReaderWriter into multiple files

af/merge-core
Johannes Bildstein 9 years ago
parent
commit
9d95f446c6
  1. 221
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs
  2. 176
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs
  3. 65
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
  4. 87
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs
  5. 157
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs
  6. 178
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
  7. 795
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
  8. 103
      src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
  9. 179
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs
  10. 128
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
  11. 160
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
  12. 80
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs
  13. 123
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs
  14. 217
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs
  15. 913
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
  16. 234
      src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs
  17. 1711
      src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs
  18. 1970
      src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs

221
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs

@ -0,0 +1,221 @@
// <copyright file="IccDataReader.Curves.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a <see cref="IccOneDimensionalCurve"/>
/// </summary>
/// <returns>The read curve</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccResponseCurve"/>
/// </summary>
/// <param name="channelCount">The number of channels</param>
/// <returns>The read curve</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccParametricCurve"/>
/// </summary>
/// <returns>The read curve</returns>
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}");
}
}
/// <summary>
/// Reads a <see cref="IccCurveSegment"/>
/// </summary>
/// <returns>The read segment</returns>
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}");
}
}
/// <summary>
/// Reads a <see cref="IccFormulaCurveElement"/>
/// </summary>
/// <returns>The read segment</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccSampledCurveElement"/>
/// </summary>
/// <returns>The read segment</returns>
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);
}
/// <summary>
/// Reads curve data
/// </summary>
/// <param name="count">Number of input channels</param>
/// <returns>The curve data</returns>
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;
}
}
}

176
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs

@ -0,0 +1,176 @@
// <copyright file="IccDataReader.Lut.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads an 8bit lookup table
/// </summary>
/// <returns>The read LUT</returns>
public IccLut ReadLUT8()
{
return new IccLut(this.ReadBytes(256));
}
/// <summary>
/// Reads a 16bit lookup table
/// </summary>
/// <param name="count">The number of entries</param>
/// <returns>The read LUT</returns>
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);
}
/// <summary>
/// Reads a CLUT depending on type
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="isFloat">If true, it's read as CLUTf32,
/// else read as either CLUT8 or CLUT16 depending on embedded information</param>
/// <returns>The read CLUT</returns>
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);
}
}
/// <summary>
/// Reads an 8 bit CLUT
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUT8</returns>
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);
}
/// <summary>
/// Reads a 16 bit CLUT
/// </summary>
/// <param name="inChannelCount">Input channel count</param>
/// <param name="outChannelCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUT16</returns>
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);
}
/// <summary>
/// Reads a 32bit floating point CLUT
/// </summary>
/// <param name="inChCount">Input channel count</param>
/// <param name="outChCount">Output channel count</param>
/// <param name="gridPointCount">Grid point count for each CLUT channel</param>
/// <returns>The read CLUTf32</returns>
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);
}
}
}

65
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs

@ -0,0 +1,65 @@
// <copyright file="IccDataReader.Matrix.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a two dimensional matrix
/// </summary>
/// <param name="xCount">Number of values in X</param>
/// <param name="yCount">Number of values in Y</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The read matrix</returns>
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;
}
/// <summary>
/// Reads a one dimensional matrix
/// </summary>
/// <param name="yCount">Number of values</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The read matrix</returns>
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;
}
}
}

87
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs

@ -0,0 +1,87 @@
// <copyright file="IccDataReader.MultiProcessElement.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a <see cref="IccMultiProcessElement"/>
/// </summary>
/// <returns>The read <see cref="IccMultiProcessElement"/></returns>
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}");
}
}
/// <summary>
/// Reads a CurveSet <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="inChannelCount">Number of input channels</param>
/// <param name="outChannelCount">Number of output channels</param>
/// <returns>The read <see cref="IccCurveSetProcessElement"/></returns>
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);
}
/// <summary>
/// Reads a Matrix <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="inChannelCount">Number of input channels</param>
/// <param name="outChannelCount">Number of output channels</param>
/// <returns>The read <see cref="IccMatrixProcessElement"/></returns>
public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount)
{
return new IccMatrixProcessElement(
this.ReadMatrix(inChannelCount, outChannelCount, true),
this.ReadMatrix(outChannelCount, true));
}
/// <summary>
/// Reads a CLUT <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="inChCount">Number of input channels</param>
/// <param name="outChCount">Number of output channels</param>
/// <returns>The read <see cref="IccClutProcessElement"/></returns>
public IccClutProcessElement ReadCLUTProcessElement(int inChCount, int outChCount)
{
return new IccClutProcessElement(this.ReadCLUT(inChCount, outChCount, true));
}
}
}

157
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs

@ -0,0 +1,157 @@
// <copyright file="IccDataReader.NonPrimitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a DateTime
/// </summary>
/// <returns>the value</returns>
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;
}
}
/// <summary>
/// Reads an ICC profile version number
/// </summary>
/// <returns>the version number</returns>
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);
}
/// <summary>
/// Reads an XYZ number
/// </summary>
/// <returns>the XYZ number</returns>
public Vector3 ReadXyzNumber()
{
return new Vector3(
x: this.ReadFix16(),
y: this.ReadFix16(),
z: this.ReadFix16());
}
/// <summary>
/// Reads a profile ID
/// </summary>
/// <returns>the profile ID</returns>
public IccProfileId ReadProfileId()
{
return new IccProfileId(
p1: this.ReadUInt32(),
p2: this.ReadUInt32(),
p3: this.ReadUInt32(),
p4: this.ReadUInt32());
}
/// <summary>
/// Reads a position number
/// </summary>
/// <returns>the position number</returns>
public IccPositionNumber ReadPositionNumber()
{
return new IccPositionNumber(
offset: this.ReadUInt32(),
size: this.ReadUInt32());
}
/// <summary>
/// Reads a response number
/// </summary>
/// <returns>the response number</returns>
public IccResponseNumber ReadResponseNumber()
{
return new IccResponseNumber(
deviceCode: this.ReadUInt16(),
measurementValue: this.ReadFix16());
}
/// <summary>
/// Reads a named color
/// </summary>
/// <param name="deviceCoordCount">Number of device coordinates</param>
/// <returns>the named color</returns>
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);
}
/// <summary>
/// Reads a profile description
/// </summary>
/// <returns>the profile description</returns>
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);
}
/// <summary>
/// Reads a colorant table entry
/// </summary>
/// <returns>the profile description</returns>
public IccColorantTableEntry ReadColorantTableEntry()
{
return new IccColorantTableEntry(
name: this.ReadAsciiString(32),
pcs1: this.ReadUInt16(),
pcs2: this.ReadUInt16(),
pcs3: this.ReadUInt16());
}
}
}

178
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs

@ -0,0 +1,178 @@
// <copyright file="IccDataReader.Primitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Text;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads an ushort
/// </summary>
/// <returns>the value</returns>
public ushort ReadUInt16()
{
return this.converter.ToUInt16(this.data, this.AddIndex(2));
}
/// <summary>
/// Reads a short
/// </summary>
/// <returns>the value</returns>
public short ReadInt16()
{
return this.converter.ToInt16(this.data, this.AddIndex(2));
}
/// <summary>
/// Reads an uint
/// </summary>
/// <returns>the value</returns>
public uint ReadUInt32()
{
return this.converter.ToUInt32(this.data, this.AddIndex(4));
}
/// <summary>
/// Reads an int
/// </summary>
/// <returns>the value</returns>
public int ReadInt32()
{
return this.converter.ToInt32(this.data, this.AddIndex(4));
}
/// <summary>
/// Reads an ulong
/// </summary>
/// <returns>the value</returns>
public ulong ReadUInt64()
{
return this.converter.ToUInt64(this.data, this.AddIndex(8));
}
/// <summary>
/// Reads a long
/// </summary>
/// <returns>the value</returns>
public long ReadInt64()
{
return this.converter.ToInt64(this.data, this.AddIndex(8));
}
/// <summary>
/// Reads a float
/// </summary>
/// <returns>the value</returns>
public float ReadSingle()
{
return this.converter.ToSingle(this.data, this.AddIndex(4));
}
/// <summary>
/// Reads a double
/// </summary>
/// <returns>the value</returns>
public double ReadDouble()
{
return this.converter.ToDouble(this.data, this.AddIndex(8));
}
/// <summary>
/// Reads an ASCII encoded string
/// </summary>
/// <param name="length">number of bytes to read</param>
/// <returns>The value as a string</returns>
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;
}
/// <summary>
/// Reads an UTF-16 big-endian encoded string
/// </summary>
/// <param name="length">number of bytes to read</param>
/// <returns>The value as a string</returns>
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);
}
/// <summary>
/// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadFix16()
{
return this.ReadInt32() / 65536f;
}
/// <summary>
/// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadUFix16()
{
return this.ReadUInt32() / 65536f;
}
/// <summary>
/// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadU1Fix15()
{
return this.ReadUInt16() / 32768f;
}
/// <summary>
/// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits
/// </summary>
/// <returns>The number as double</returns>
public float ReadUFix8()
{
return this.ReadUInt16() / 256f;
}
/// <summary>
/// Reads a number of bytes and advances the index
/// </summary>
/// <param name="count">The number of bytes to read</param>
/// <returns>The read bytes</returns>
public byte[] ReadBytes(int count)
{
byte[] bytes = new byte[count];
Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count);
return bytes;
}
}
}

795
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs

@ -0,0 +1,795 @@
// <copyright file="IccDataReader.TagDataEntry.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Globalization;
using System.Numerics;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
/// <summary>
/// Reads a tag data entry
/// </summary>
/// <param name="info">The table entry with reading information</param>
/// <returns>the tag data entry</returns>
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);
}
}
/// <summary>
/// Reads the header of a <see cref="IccTagDataEntry"/>
/// </summary>
/// <returns>The read signature</returns>
public IccTypeSignature ReadTagDataEntryHeader()
{
IccTypeSignature type = (IccTypeSignature)this.ReadUInt32();
this.AddIndex(4); // 4 bytes are not used
return type;
}
/// <summary>
/// Reads the header of a <see cref="IccTagDataEntry"/> and checks if it's the expected value
/// </summary>
/// <param name="expected">expected value to check against</param>
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}");
}
}
/// <summary>
/// Reads a <see cref="IccTagDataEntry"/> with an unknown <see cref="IccTypeSignature"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
{
int count = (int)size - 8; // 8 is the tag header size
return new IccUnknownTagDataEntry(this.ReadBytes(count));
}
/// <summary>
/// Reads a <see cref="IccChromaticityTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
}
/// <summary>
/// Reads a <see cref="IccColorantOrderTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
{
uint colorantCount = this.ReadUInt32();
byte[] number = this.ReadBytes((int)colorantCount);
return new IccColorantOrderTagDataEntry(number);
}
/// <summary>
/// Reads a <see cref="IccColorantTableTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccCurveTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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).
}
/// <summary>
/// Reads a <see cref="IccDataTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccDateTimeTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry()
{
return new IccDateTimeTagDataEntry(this.ReadDateTime());
}
/// <summary>
/// Reads a <see cref="IccLut16TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccLut8TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccLutAToBTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccLutBToATagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccMeasurementTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry()
{
return new IccMeasurementTagDataEntry(
observer: (IccStandardObserver)this.ReadUInt32(),
xyzBacking: this.ReadXyzNumber(),
geometry: (IccMeasurementGeometry)this.ReadUInt32(),
flare: this.ReadUFix16(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
}
/// <summary>
/// Reads a <see cref="IccMultiLocalizedUnicodeTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccMultiProcessElementsTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccNamedColor2TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccParametricCurveTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry()
{
return new IccParametricCurveTagDataEntry(this.ReadParametricCurve());
}
/// <summary>
/// Reads a <see cref="IccProfileSequenceDescTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccProfileSequenceIdentifierTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccResponseCurveSet16TagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccSignatureTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
public IccSignatureTagDataEntry ReadSignatureTagDataEntry()
{
return new IccSignatureTagDataEntry(this.ReadAsciiString(4));
}
/// <summary>
/// Reads a <see cref="IccTextTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccTextTagDataEntry ReadTextTagDataEntry(uint size)
{
return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
}
/// <summary>
/// Reads a <see cref="IccUFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccUInt16ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccUInt32ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccUInt64ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccUInt8ArrayTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccViewingConditionsTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry(uint size)
{
return new IccViewingConditionsTagDataEntry(
illuminantXyz: this.ReadXyzNumber(),
surroundXyz: this.ReadXyzNumber(),
illuminant: (IccStandardIlluminant)this.ReadUInt32());
}
/// <summary>
/// Reads a <see cref="IccXyzTagDataEntry"/>
/// </summary>
/// <param name="size">The size of the entry in bytes</param>
/// <returns>The read entry</returns>
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);
}
/// <summary>
/// Reads a <see cref="IccTextDescriptionTagDataEntry"/>
/// </summary>
/// <returns>The read entry</returns>
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);
}
}
}

103
src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs

@ -0,0 +1,103 @@
// <copyright file="IccDataReader.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Text;
using ImageSharp.IO;
/// <summary>
/// Provides methods to read ICC data types
/// </summary>
internal sealed partial class IccDataReader
{
private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// The data that is read
/// </summary>
private readonly byte[] data;
/// <summary>
/// The current reading position
/// </summary>
private int index;
private EndianBitConverter converter = new BigEndianBitConverter();
/// <summary>
/// Initializes a new instance of the <see cref="IccDataReader"/> class.
/// </summary>
/// <param name="data">The data to read</param>
public IccDataReader(byte[] data)
{
Guard.NotNull(data, nameof(data));
this.data = data;
}
/// <summary>
/// Sets the reading position to the given value
/// </summary>
/// <param name="index">The new index position</param>
public void SetIndex(int index)
{
this.index = index.Clamp(0, this.data.Length);
}
/// <summary>
/// Returns the current <see cref="index"/> without increment and adds the given increment
/// </summary>
/// <param name="increment">The value to increment <see cref="index"/></param>
/// <returns>The current <see cref="index"/> without the increment</returns>
private int AddIndex(int increment)
{
int tmp = this.index;
this.index += increment;
return tmp;
}
/// <summary>
/// Calculates the 4 byte padding and adds it to the <see cref="index"/> variable
/// </summary>
private void AddPadding()
{
this.index += this.CalcPadding();
}
/// <summary>
/// Calculates the 4 byte padding
/// </summary>
/// <returns>the number of bytes to pad</returns>
private int CalcPadding()
{
int p = 4 - (this.index % 4);
return p >= 4 ? 0 : p;
}
/// <summary>
/// Gets the bit value at a specified position
/// </summary>
/// <param name="value">The value from where the bit will be extracted</param>
/// <param name="position">Position of the bit. Zero based index from left to right.</param>
/// <returns>The bit value at specified position</returns>
private bool GetBit(byte value, int position)
{
return ((value >> (7 - position)) & 1) == 1;
}
/// <summary>
/// Gets the bit value at a specified position
/// </summary>
/// <param name="value">The value from where the bit will be extracted</param>
/// <param name="position">Position of the bit. Zero based index from left to right.</param>
/// <returns>The bit value at specified position</returns>
private bool GetBit(ushort value, int position)
{
return ((value >> (15 - position)) & 1) == 1;
}
}
}

179
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs

@ -0,0 +1,179 @@
// <copyright file="IccDataWriter.Curves.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a <see cref="IccOneDimensionalCurve"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccResponseCurve"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccParametricCurve"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccCurveSegment"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
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}");
}
}
/// <summary>
/// Writes a <see cref="IccFormulaCurveElement"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccSampledCurveElement"/>
/// </summary>
/// <param name="value">The curve to write</param>
/// <returns>The number of bytes written</returns>
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;
}
}
}

128
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs

@ -0,0 +1,128 @@
// <copyright file="IccDataWriter.Lut.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes an 8bit lookup table
/// </summary>
/// <param name="value">The LUT to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes an 16bit lookup table
/// </summary>
/// <param name="value">The LUT to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes an color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
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}");
}
}
/// <summary>
/// Writes a 8bit color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a 16bit color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a 32bit float color lookup table
/// </summary>
/// <param name="value">The CLUT to write</param>
/// <returns>The number of bytes written</returns>
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;
}
}
}

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

@ -0,0 +1,160 @@
// <copyright file="IccDataWriter.Matrix.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a two dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a two dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrix(Fast2DArray<float> 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;
}
/// <summary>
/// Writes a two dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a one dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a one dimensional matrix
/// </summary>
/// <param name="value">The matrix to write</param>
/// <param name="isSingle">True if the values are encoded as Single; false if encoded as Fix16</param>
/// <returns>The number of bytes written</returns>
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;
}
}
}

80
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs

@ -0,0 +1,80 @@
// <copyright file="IccDataWriter.MultiProcessElement.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
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}");
}
}
/// <summary>
/// Writes a CurveSet <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteCurveSetProcessElement(IccCurveSetProcessElement value)
{
int count = 0;
foreach (IccOneDimensionalCurve curve in value.Curves)
{
count += this.WriteOneDimensionalCurve(curve);
count += this.WritePadding();
}
return count;
}
/// <summary>
/// Writes a Matrix <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteMatrixProcessElement(IccMatrixProcessElement value)
{
return this.WriteMatrix(value.MatrixIxO, true)
+ this.WriteMatrix(value.MatrixOx1, true);
}
/// <summary>
/// Writes a CLUT <see cref="IccMultiProcessElement"/>
/// </summary>
/// <param name="value">The element to write</param>
/// <returns>The number of bytes written</returns>
public int WriteCLUTProcessElement(IccClutProcessElement value)
{
return this.WriteCLUT(value.ClutValue);
}
}
}

123
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs

@ -0,0 +1,123 @@
// <copyright file="IccDataWriter.NonPrimitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a DateTime
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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);
}
/// <summary>
/// Writes an ICC profile version number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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);
}
/// <summary>
/// Writes an XYZ number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteXYZNumber(Vector3 value)
{
return this.WriteFix16(value.X)
+ this.WriteFix16(value.Y)
+ this.WriteFix16(value.Z);
}
/// <summary>
/// Writes a profile ID
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteProfileId(IccProfileId value)
{
return this.WriteUInt32(value.Part1)
+ this.WriteUInt32(value.Part2)
+ this.WriteUInt32(value.Part3)
+ this.WriteUInt32(value.Part4);
}
/// <summary>
/// Writes a position number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WritePositionNumber(IccPositionNumber value)
{
return this.WriteUInt32(value.Offset)
+ this.WriteUInt32(value.Size);
}
/// <summary>
/// Writes a response number
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteResponseNumber(IccResponseNumber value)
{
return this.WriteUInt16(value.DeviceCode)
+ this.WriteFix16(value.MeasurementValue);
}
/// <summary>
/// Writes a named color
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteNamedColor(IccNamedColor value)
{
return this.WriteASCIIString(value.Name, 32, '\0')
+ this.WriteArray(value.PcsCoordinates)
+ this.WriteArray(value.DeviceCoordinates);
}
/// <summary>
/// Writes a profile description
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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));
}
}
}

217
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs

@ -0,0 +1,217 @@
// <copyright file="IccDataWriter.Primitives.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Text;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a byte
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public int WriteByte(byte value)
{
this.dataStream.WriteByte(value);
return 1;
}
/// <summary>
/// Writes an ushort
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteUInt16(ushort value)
{
return this.WriteBytes((byte*)&value, 2);
}
/// <summary>
/// Writes a short
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteInt16(short value)
{
return this.WriteBytes((byte*)&value, 2);
}
/// <summary>
/// Writes an uint
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteUInt32(uint value)
{
return this.WriteBytes((byte*)&value, 4);
}
/// <summary>
/// Writes an int
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteInt32(int value)
{
return this.WriteBytes((byte*)&value, 4);
}
/// <summary>
/// Writes an ulong
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteUInt64(ulong value)
{
return this.WriteBytes((byte*)&value, 8);
}
/// <summary>
/// Writes a long
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteInt64(long value)
{
return this.WriteBytes((byte*)&value, 8);
}
/// <summary>
/// Writes a float
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteSingle(float value)
{
return this.WriteBytes((byte*)&value, 4);
}
/// <summary>
/// Writes a double
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
public unsafe int WriteDouble(double value)
{
return this.WriteBytes((byte*)&value, 8);
}
/// <summary>
/// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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));
}
/// <summary>
/// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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));
}
/// <summary>
/// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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));
}
/// <summary>
/// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits
/// </summary>
/// <param name="value">The value to write</param>
/// <returns>the number of bytes written</returns>
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));
}
/// <summary>
/// Writes an ASCII encoded string
/// </summary>
/// <param name="value">the string to write</param>
/// <returns>the number of bytes written</returns>
public int WriteASCIIString(string value)
{
byte[] data = AsciiEncoding.GetBytes(value);
this.dataStream.Write(data, 0, data.Length);
return data.Length;
}
/// <summary>
/// Writes an ASCII encoded string resizes it to the given length
/// </summary>
/// <param name="value">The string to write</param>
/// <param name="length">The desired length of the string including 1 padding character</param>
/// <param name="paddingChar">The character to pad to the given length</param>
/// <returns>the number of bytes written</returns>
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;
}
/// <summary>
/// Writes an UTF-16 big-endian encoded string
/// </summary>
/// <param name="value">the string to write</param>
/// <returns>the number of bytes written</returns>
public int WriteUnicodeString(string value)
{
byte[] data = Encoding.BigEndianUnicode.GetBytes(value);
this.dataStream.Write(data, 0, data.Length);
return data.Length;
}
}
}

913
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs

@ -0,0 +1,913 @@
// <copyright file="IccDataWriter.TagDataEntry.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
internal sealed partial class IccDataWriter
{
/// <summary>
/// Writes a tag data entry
/// </summary>
/// <param name="data">The entry to write</param>
/// <param name="table">The table entry for the written data entry</param>
/// <returns>The number of bytes written (excluding padding)</returns>
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;
}
/// <summary>
/// Writes a tag data entry (without padding)
/// </summary>
/// <param name="entry">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes the header of a <see cref="IccTagDataEntry"/>
/// </summary>
/// <param name="signature">The signature of the entry</param>
/// <returns>The number of bytes written</returns>
public int WriteTagDataEntryHeader(IccTypeSignature signature)
{
return this.WriteUInt32((uint)signature)
+ this.WriteEmpty(4);
}
/// <summary>
/// Writes a <see cref="IccUnknownTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value)
{
return this.WriteArray(value.Data);
}
/// <summary>
/// Writes a <see cref="IccChromaticityTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccColorantOrderTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value)
{
return this.WriteUInt32((uint)value.ColorantNumber.Length)
+ this.WriteArray(value.ColorantNumber);
}
/// <summary>
/// Writes a <see cref="IccColorantTableTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccCurveTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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).
}
/// <summary>
/// Writes a <see cref="IccDataTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteDataTagDataEntry(IccDataTagDataEntry value)
{
return this.WriteEmpty(3)
+ this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00))
+ this.WriteArray(value.Data);
}
/// <summary>
/// Writes a <see cref="IccDateTimeTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value)
{
return this.WriteDateTime(value.Value);
}
/// <summary>
/// Writes a <see cref="IccLut16TagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccLut8TagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccLutAToBTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccLutBToATagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccMeasurementTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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);
}
/// <summary>
/// Writes a <see cref="IccMultiLocalizedUnicodeTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccMultiProcessElementsTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccNamedColor2TagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccParametricCurveTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value)
{
return this.WriteParametricCurve(value.Curve);
}
/// <summary>
/// Writes a <see cref="IccProfileSequenceDescTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccProfileSequenceIdentifierTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccResponseCurveSet16TagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccSignatureTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value)
{
return this.WriteASCIIString(value.SignatureData, 4, ' ');
}
/// <summary>
/// Writes a <see cref="IccTextTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteTextTagDataEntry(IccTextTagDataEntry value)
{
return this.WriteASCIIString(value.Text);
}
/// <summary>
/// Writes a <see cref="IccUFix16ArrayTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccUInt16ArrayTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value)
{
return this.WriteArray(value.Data);
}
/// <summary>
/// Writes a <see cref="IccUInt32ArrayTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value)
{
return this.WriteArray(value.Data);
}
/// <summary>
/// Writes a <see cref="IccUInt64ArrayTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value)
{
return this.WriteArray(value.Data);
}
/// <summary>
/// Writes a <see cref="IccUInt8ArrayTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value)
{
return this.WriteArray(value.Data);
}
/// <summary>
/// Writes a <see cref="IccViewingConditionsTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value)
{
return this.WriteXYZNumber(value.IlluminantXyz)
+ this.WriteXYZNumber(value.SurroundXyz)
+ this.WriteUInt32((uint)value.Illuminant);
}
/// <summary>
/// Writes a <see cref="IccXyzTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes a <see cref="IccTextDescriptionTagDataEntry"/>
/// </summary>
/// <param name="value">The entry to write</param>
/// <returns>The number of bytes written</returns>
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;
}
}
}

234
src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs

@ -0,0 +1,234 @@
// <copyright file="IccDataWriter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.IO;
using System.Text;
/// <summary>
/// Provides methods to write ICC data types
/// </summary>
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 } };
/// <summary>
/// The underlying stream where the data is written to
/// </summary>
private readonly MemoryStream dataStream;
/// <summary>
/// Initializes a new instance of the <see cref="IccDataWriter"/> class.
/// </summary>
public IccDataWriter()
{
this.dataStream = new MemoryStream();
}
/// <summary>
/// Gets the currently written length in bytes
/// </summary>
public uint Length
{
get { return (uint)this.dataStream.Length; }
}
/// <summary>
/// Gets the written data bytes
/// </summary>
/// <returns>The written data</returns>
public byte[] GetData()
{
return this.dataStream.ToArray();
}
/// <summary>
/// Sets the writing position to the given value
/// </summary>
/// <param name="index">The new index position</param>
public void SetIndex(int index)
{
this.dataStream.Position = index;
}
/// <summary>
/// Writes a byte array
/// </summary>
/// <param name="data">The array to write</param>
/// <returns>The number of bytes written</returns>
public int WriteArray(byte[] data)
{
this.dataStream.Write(data, 0, data.Length);
return data.Length;
}
/// <summary>
/// Writes a ushort array
/// </summary>
/// <param name="data">The array to write</param>
/// <returns>The number of bytes written</returns>
public int WriteArray(ushort[] data)
{
for (int i = 0; i < data.Length; i++)
{
this.WriteUInt16(data[i]);
}
return data.Length * 2;
}
/// <summary>
/// Writes a short array
/// </summary>
/// <param name="data">The array to write</param>
/// <returns>The number of bytes written</returns>
public int WriteArray(short[] data)
{
for (int i = 0; i < data.Length; i++)
{
this.WriteInt16(data[i]);
}
return data.Length * 2;
}
/// <summary>
/// Writes a uint array
/// </summary>
/// <param name="data">The array to write</param>
/// <returns>The number of bytes written</returns>
public int WriteArray(uint[] data)
{
for (int i = 0; i < data.Length; i++)
{
this.WriteUInt32(data[i]);
}
return data.Length * 4;
}
/// <summary>
/// Writes an int array
/// </summary>
/// <param name="data">The array to write</param>
/// <returns>The number of bytes written</returns>
public int WriteArray(int[] data)
{
for (int i = 0; i < data.Length; i++)
{
this.WriteInt32(data[i]);
}
return data.Length * 4;
}
/// <summary>
/// Writes a ulong array
/// </summary>
/// <param name="data">The array to write</param>
/// <returns>The number of bytes written</returns>
public int WriteArray(ulong[] data)
{
for (int i = 0; i < data.Length; i++)
{
this.WriteUInt64(data[i]);
}
return data.Length * 8;
}
/// <summary>
/// Write a number of empty bytes
/// </summary>
/// <param name="length">The number of bytes to write</param>
/// <returns>The number of bytes written</returns>
public int WriteEmpty(int length)
{
for (int i = 0; i < length; i++)
{
this.dataStream.WriteByte(0);
}
return length;
}
/// <summary>
/// Writes empty bytes to a 4-byte margin
/// </summary>
/// <returns>The number of bytes written</returns>
public int WritePadding()
{
int p = 4 - ((int)this.dataStream.Position % 4);
return this.WriteEmpty(p >= 4 ? 0 : p);
}
/// <summary>
/// Writes given bytes from pointer
/// </summary>
/// <param name="data">Pointer to the bytes to write</param>
/// <param name="length">The number of bytes to write</param>
/// <returns>The number of bytes written</returns>
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;
}
/// <summary>
/// Writes given bytes from pointer ignoring endianness
/// </summary>
/// <param name="data">Pointer to the bytes to write</param>
/// <param name="length">The number of bytes to write</param>
/// <returns>The number of bytes written</returns>
private unsafe int WriteBytesDirect(byte* data, int length)
{
for (int i = 0; i < length; i++)
{
this.dataStream.WriteByte(data[i]);
}
return length;
}
/// <summary>
/// Writes curve data
/// </summary>
/// <param name="curves">The curves to write</param>
/// <returns>The number of bytes written</returns>
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;
}
}
}

1711
src/ImageSharp/MetaData/Profiles/ICC/IccDataReader.cs

File diff suppressed because it is too large

1970
src/ImageSharp/MetaData/Profiles/ICC/IccDataWriter.cs

File diff suppressed because it is too large
Loading…
Cancel
Save