Files

286 lines
11 KiB
C#

using System.IO;
namespace novideo_srgb
{
public class ICCMatrixProfile
{
public Matrix matrix = Matrix.Zero3x3();
public ToneCurve[] trcs = new ToneCurve[3];
public ToneCurve[]? vcgt;
private ICCMatrixProfile()
{
}
public static ICCMatrixProfile FromFile(string path)
{
var result = new ICCMatrixProfile();
using (var reader = new ICCBinaryReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
var stream = reader.BaseStream;
{
stream.Seek(0x24, SeekOrigin.Begin);
var magic = new string(reader.ReadChars(4));
if (magic != "acsp")
{
throw new ICCProfileException("Not an ICC profile");
}
}
{
stream.Seek(0xC, SeekOrigin.Begin);
var type = new string(reader.ReadChars(4));
if (type != "mntr")
{
throw new ICCProfileException("Not a display device profile");
}
}
{
stream.Seek(0x10, SeekOrigin.Begin);
var spaces = new string(reader.ReadChars(8));
if (spaces != "RGB XYZ ")
{
throw new ICCProfileException("Not an RGB profile with XYZ PCS");
}
}
stream.Seek(0x80, SeekOrigin.Begin);
var tagCount = reader.ReadUInt32();
var seenTags = 0;
var useCLUT = false;
for (uint i = 0; i < tagCount; i++)
{
stream.Seek(0x80 + 4 + 12 * i, SeekOrigin.Begin);
var tagSig = new string(reader.ReadChars(4));
var offset = reader.ReadUInt32();
var size = reader.ReadUInt32();
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
var index = Array.IndexOf(new[] { 'r', 'g', 'b' }, tagSig[0]);
if (tagSig == "A2B1")
{
useCLUT = true;
var typeSig = new string(reader.ReadChars(4));
if (typeSig != "mft2")
{
throw new ICCProfileException(tagSig + " is not of lut16Type");
}
reader.ReadUInt32();
var inputChannels = reader.ReadByte();
if (inputChannels != 3)
{
throw new ICCProfileException(tagSig + " must have 3 input channels");
}
var outputChannels = reader.ReadByte();
if (outputChannels != 3)
{
throw new ICCProfileException(tagSig + " must have 3 output channels");
}
var lutPoints = reader.ReadByte();
reader.ReadByte();
for (var j = 0; j < 9; j++)
{
reader.ReadS15Fixed16();
}
var inputEntries = reader.ReadUInt16();
var outputEntries = reader.ReadUInt16();
var input = new ToneCurve[3];
for (var j = 0; j < 3; j++)
{
var table = new ushort[inputEntries];
for (var k = 0; k < inputEntries; k++)
{
table[k] = reader.ReadUInt16();
}
input[j] = new LutToneCurve(table);
}
var clut = new ushort[lutPoints, lutPoints, lutPoints, 3];
for (var r = 0; r < lutPoints; r++)
{
for (var g = 0; g < lutPoints; g++)
{
for (var b = 0; b < lutPoints; b++)
{
for (var j = 0; j < 3; j++)
{
clut[r, g, b, j] = reader.ReadUInt16();
}
}
}
}
var output = new LutToneCurve[3];
for (var j = 0; j < 3; j++)
{
var table = new ushort[outputEntries];
for (var k = 0; k < outputEntries; k++)
{
table[k] = reader.ReadUInt16();
}
output[j] = new LutToneCurve(table, 32768);
}
var lut16 = new Lut16(input, clut, output);
var black = lut16.SampleGrayscaleAt(0);
var primaries = new[]
{
lut16.SampleAt(1, 0, 0),
lut16.SampleAt(0, 1, 0),
lut16.SampleAt(0, 0, 1)
};
var Mprime = Matrix.Zero3x3();
for (var j = 0; j < 3; j++)
{
var purePrimary = primaries[j] - black;
for (var k = 0; k < 3; k++)
{
Mprime[k, j] = purePrimary[k] / purePrimary[1];
}
}
var M = Mprime * Matrix.FromDiagonal(Mprime.Inverse() * Colorimetry.D50);
var Minv = M.Inverse();
result.matrix = M;
const int trcSize = 4096;
var trcs = new double[3][];
for (var j = 0; j < 3; j++)
{
trcs[j] = new double[trcSize];
}
for (var j = 0; j < trcSize - 1; j++)
{
var values = lut16.SampleGrayscaleAt(j / (double)(trcSize - 1));
var toneResponse = Minv * values;
for (var k = 0; k < 3; k++)
{
trcs[k][j] = Math.Min(Math.Max(toneResponse[k], 0), 1);
}
}
for (var j = 0; j < 3; j++)
{
trcs[j][trcSize - 1] = 1;
result.trcs[j] = new DoubleToneCurve(trcs[j]);
}
}
else if (tagSig.EndsWith("TRC") && !useCLUT)
{
var typeSig = new string(reader.ReadChars(4));
if (typeSig != "curv")
{
throw new ICCProfileException(tagSig + " is not of curveType");
}
reader.ReadUInt32();
var numEntries = reader.ReadUInt32();
ToneCurve curve;
if (numEntries == 1)
{
var gamma = reader.ReadU8Fixed8();
curve = new GammaToneCurve(gamma);
}
else
{
var entries = new ushort[numEntries];
for (uint j = 0; j < numEntries; j++)
{
entries[j] = reader.ReadUInt16();
}
curve = new LutToneCurve(entries);
}
result.trcs[index] = curve;
seenTags++;
}
else if (tagSig.EndsWith("XYZ") && !useCLUT)
{
reader.ReadUInt32();
reader.ReadUInt32();
for (var j = 0; j < 3; j++)
{
result.matrix[j, index] = reader.ReadS15Fixed16();
}
seenTags++;
}
else if (tagSig == "vcgt")
{
reader.ReadChars(4);
reader.ReadUInt32();
var type = reader.ReadUInt32();
if (type != 0) throw new ICCProfileException("Only VCGT type 0 is supported");
var numChannels = reader.ReadUInt16();
var numEntries = reader.ReadUInt16();
var entrySize = reader.ReadUInt16();
if (numChannels != 3) throw new ICCProfileException("Only VCGT with 3 channels is supported");
result.vcgt = new ToneCurve[3];
for (var j = 0; j < 3; j++)
{
var values = new ushort[numEntries];
for (var k = 0; k < numEntries; k++)
{
switch (entrySize)
{
case 1:
values[k] = (ushort)(reader.ReadByte() * ushort.MaxValue / byte.MaxValue);
break;
case 2:
values[k] = reader.ReadUInt16();
break;
default:
throw new ICCProfileException("Only 8 and 16 bit VCGT is supported");
}
}
result.vcgt[j] = new LutToneCurve(values);
}
}
}
if (!useCLUT)
{
if (seenTags != 6)
{
throw new ICCProfileException("Missing required tags for curves + matrix profile");
}
result.matrix = Colorimetry.XYZScaleToD50(result.matrix);
}
}
return result;
}
}
}