Files

180 lines
5.6 KiB
C#

// credit to https://mina86.com/2019/srgb-xyz-matrix/ and http://www.brucelindbloom.com/ for the math
namespace novideo_srgb
{
public static class Colorimetry
{
public struct Point
{
public bool Equals(Point other)
{
return X.Equals(other.X) && Y.Equals(other.Y);
}
public override bool Equals(object? obj)
{
return obj is Point other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
return (X.GetHashCode() * 397) ^ Y.GetHashCode();
}
}
public double X;
public double Y;
}
public struct ColorSpace
{
public bool Equals(ColorSpace other)
{
return Red.Equals(other.Red) && Green.Equals(other.Green) && Blue.Equals(other.Blue) &&
White.Equals(other.White);
}
public override bool Equals(object? obj)
{
return obj is ColorSpace other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Red.GetHashCode();
hashCode = (hashCode * 397) ^ Green.GetHashCode();
hashCode = (hashCode * 397) ^ Blue.GetHashCode();
hashCode = (hashCode * 397) ^ White.GetHashCode();
return hashCode;
}
}
public Point Red;
public Point Green;
public Point Blue;
public Point White;
}
public static Point D65 = new Point { X = 0.3127, Y = 0.3290 };
public static ColorSpace sRGB = new ColorSpace
{
Red = new Point { X = 0.64, Y = 0.33 },
Green = new Point { X = 0.3, Y = 0.6 },
Blue = new Point { X = 0.15, Y = 0.06 },
White = D65
};
public static ColorSpace DisplayP3 = new ColorSpace
{
Red = new Point { X = 0.68, Y = 0.32 },
Green = new Point { X = 0.265, Y = 0.69 },
Blue = new Point { X = 0.15, Y = 0.06 },
White = D65
};
public static ColorSpace AdobeRGB = new ColorSpace
{
Red = new Point { X = 0.64, Y = 0.33 },
Green = new Point { X = 0.21, Y = 0.71 },
Blue = new Point { X = 0.15, Y = 0.06 },
White = D65
};
public static ColorSpace BT2020 = new ColorSpace
{
Red = new Point { X = 0.708, Y = 0.292 },
Green = new Point { X = 0.17, Y = 0.797 },
Blue = new Point { X = 0.131, Y = 0.046 },
White = D65
};
public static ColorSpace[] ColorSpaces => new[] { sRGB, DisplayP3, AdobeRGB, BT2020 };
public static Matrix D50 = Matrix.FromValues(new[,] { { 0.9642 }, { 1 }, { 0.8249 } });
public static Matrix RGBToXYZ(ColorSpace colorSpace)
{
var red = colorSpace.Red;
var green = colorSpace.Green;
var blue = colorSpace.Blue;
var white = colorSpace.White;
var whiteXYZ = Matrix.FromValues(new[,]
{ { white.X / white.Y }, { 1 }, { (1 - white.X - white.Y) / white.Y } });
var Mprime = Matrix.FromValues(new[,]
{
{ red.X / red.Y, green.X / green.Y, blue.X / blue.Y },
{ 1, 1, 1 },
{ (1 - red.X - red.Y) / red.Y, (1 - green.X - green.Y) / green.Y, (1 - blue.X - blue.Y) / blue.Y }
});
return Mprime * Matrix.FromDiagonal(Mprime.Inverse() * whiteXYZ);
}
public static Matrix XYZToRGB(ColorSpace colorSpace)
{
return RGBToXYZ(colorSpace).Inverse();
}
public static Matrix RGBToRGB(ColorSpace from, ColorSpace to)
{
var result = XYZToRGB(to) * RGBToXYZ(from);
return result;
}
public static Matrix RGBToAdaptedXYZ(ColorSpace colorspace, Matrix whiteXYZ)
{
var xyz = RGBToXYZ(colorspace);
var bradford = Matrix.FromValues(new[,]
{
{ 0.8951, 0.2664, -0.1614 },
{ -0.7502, 1.7135, 0.0367 },
{ 0.0389, -0.0685, 1.0296 }
});
var ws = colorspace.White;
var aws = bradford * Matrix.FromValues(new[,]
{
{ ws.X / ws.Y }, { 1 }, { (1 - ws.X - ws.Y) / ws.Y }
});
var awd = bradford * whiteXYZ;
var m = bradford.Inverse() * Matrix.FromDiagonal(new[]
{ awd[0] / aws[0], awd[1] / aws[1], awd[2] / aws[2] }) * bradford;
return m * xyz;
}
public static Matrix RGBToPCSXYZ(ColorSpace colorspace)
{
return RGBToAdaptedXYZ(colorspace, D50);
}
public static Matrix PCSXYZToRGB(ColorSpace colorspace)
{
return RGBToPCSXYZ(colorspace).Inverse();
}
public static Matrix XYZScale(Matrix matrix, Matrix target)
{
var result = Matrix.Zero3x3();
var white = matrix * Matrix.One3x1();
for (var i = 0; i < 3; i++)
{
for (var j = 0; j < 3; j++)
{
result[i, j] = matrix[i, j] * target[i] / white[i];
}
}
return result;
}
public static Matrix XYZScaleToD50(Matrix matrix)
{
return XYZScale(matrix, D50);
}
}
}