mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 00:22:18 -05:00
286 lines
11 KiB
C#
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;
|
|
}
|
|
}
|
|
} |