mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1738 lines
62 KiB
1738 lines
62 KiB
// <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.Globalization;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using ImageSharp.IO;
|
|
|
|
/// <summary>
|
|
/// Provides methods to read ICC data types
|
|
/// </summary>
|
|
internal sealed 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);
|
|
}
|
|
|
|
#region Read Primitives
|
|
|
|
/// <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 16bit value ignoring endianness
|
|
/// </summary>
|
|
/// <returns>the value</returns>
|
|
public short ReadDirect16()
|
|
{
|
|
return BitConverter.ToInt16(this.data, this.AddIndex(2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 32bit value ignoring endianness
|
|
/// </summary>
|
|
/// <returns>the value</returns>
|
|
public int ReadDirect32()
|
|
{
|
|
return BitConverter.ToInt32(this.data, this.AddIndex(4));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 64bit value ignoring endianness
|
|
/// </summary>
|
|
/// <returns>the value</returns>
|
|
public long ReadDirect64()
|
|
{
|
|
return BitConverter.ToInt64(this.data, this.AddIndex(8));
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Read Non-Primitives
|
|
|
|
/// <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.ReadDirect32();
|
|
|
|
int major = version >> 24;
|
|
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.ReadDirect64();
|
|
IccProfileTag technologyInfo = (IccProfileTag)this.ReadUInt32();
|
|
this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
|
|
IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = this.ReadMultiLocalizedUnicodeTagDataEntry();
|
|
this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
|
|
IccMultiLocalizedUnicodeTagDataEntry modelInfo = this.ReadMultiLocalizedUnicodeTagDataEntry();
|
|
|
|
return new IccProfileDescription(
|
|
manufacturer,
|
|
model,
|
|
attributes,
|
|
technologyInfo,
|
|
manufacturerInfo.Texts,
|
|
modelInfo.Texts);
|
|
}
|
|
|
|
/// <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());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Read Tag Data Entries
|
|
|
|
/// <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.ReadDirect32();
|
|
uint colorCount = this.ReadUInt32();
|
|
uint coordCount = this.ReadUInt32();
|
|
string prefix = this.ReadAsciiString(32);
|
|
string suffix = this.ReadAsciiString(32);
|
|
|
|
IccNamedColor[] colors = new IccNamedColor[colorCount];
|
|
for (int i = 0; i < colorCount; i++)
|
|
{
|
|
colors[i] = this.ReadNamedColor(coordCount);
|
|
}
|
|
|
|
return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors);
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Read Matrix
|
|
|
|
/// <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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Read (C)LUT
|
|
|
|
/// <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);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Read MultiProcessElement
|
|
|
|
/// <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));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Read Curves
|
|
|
|
/// <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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Subroutines
|
|
|
|
/// <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;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|