shithub: libgraphics

ref: a09cfff78a96a10169a30324b75554827945fed4
dir: /color.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
#include "graphics.h"
#include "internal.h"

ulong
col2ul(Color c)
{
	uchar cbuf[4];

	cbuf[0] = fclamp(c.a, 0, 1)*0xFF;
	cbuf[1] = fclamp(c.b, 0, 1)*0xFF;
	cbuf[2] = fclamp(c.g, 0, 1)*0xFF;
	cbuf[3] = fclamp(c.r, 0, 1)*0xFF;
	return cbuf[3]<<24 | cbuf[2]<<16 | cbuf[1]<<8 | cbuf[0];
}

Color
ul2col(ulong l)
{
	Color c;

	c.a = (l     & 0xff)/255.0;
	c.b = (l>>8  & 0xff)/255.0;
	c.g = (l>>16 & 0xff)/255.0;
	c.r = (l>>24 & 0xff)/255.0;
	return c;
}

int
hasalpha(ulong chan)
{
	for(; chan; chan >>= 8)
		if(TYPE(chan) == CAlpha)
			return 1;
	return 0;
}

/*
 * see also “The Importance of Being Linear”, Gritz and d'Eon, GPU Gems 3, Ch. 24, December 2007
 */

/*
 * generated with:
 * 	% seq 0 255 | awk '{$0 = $0/255; if($0 > 0.04045) $0 = (($0+0.055)/1.055)^2.4; else $0 = $0/12.92; printf("\t0x%02X,%s", int($0*255+0.5), NR%8 == 0? "\n": "")}'
 */
static uchar srgb2lineartab[] = {
	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,	0x01,
	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,	0x01,
	0x01,	0x01,	0x02,	0x02,	0x02,	0x02,	0x02,	0x02,
	0x02,	0x02,	0x03,	0x03,	0x03,	0x03,	0x03,	0x03,
	0x04,	0x04,	0x04,	0x04,	0x04,	0x05,	0x05,	0x05,
	0x05,	0x06,	0x06,	0x06,	0x06,	0x07,	0x07,	0x07,
	0x08,	0x08,	0x08,	0x08,	0x09,	0x09,	0x09,	0x0A,
	0x0A,	0x0A,	0x0B,	0x0B,	0x0C,	0x0C,	0x0C,	0x0D,
	0x0D,	0x0D,	0x0E,	0x0E,	0x0F,	0x0F,	0x10,	0x10,
	0x11,	0x11,	0x11,	0x12,	0x12,	0x13,	0x13,	0x14,
	0x14,	0x15,	0x16,	0x16,	0x17,	0x17,	0x18,	0x18,
	0x19,	0x19,	0x1A,	0x1B,	0x1B,	0x1C,	0x1D,	0x1D,
	0x1E,	0x1E,	0x1F,	0x20,	0x20,	0x21,	0x22,	0x23,
	0x23,	0x24,	0x25,	0x25,	0x26,	0x27,	0x28,	0x29,
	0x29,	0x2A,	0x2B,	0x2C,	0x2D,	0x2D,	0x2E,	0x2F,
	0x30,	0x31,	0x32,	0x33,	0x33,	0x34,	0x35,	0x36,
	0x37,	0x38,	0x39,	0x3A,	0x3B,	0x3C,	0x3D,	0x3E,
	0x3F,	0x40,	0x41,	0x42,	0x43,	0x44,	0x45,	0x46,
	0x47,	0x48,	0x49,	0x4A,	0x4C,	0x4D,	0x4E,	0x4F,
	0x50,	0x51,	0x52,	0x54,	0x55,	0x56,	0x57,	0x58,
	0x5A,	0x5B,	0x5C,	0x5D,	0x5F,	0x60,	0x61,	0x63,
	0x64,	0x65,	0x67,	0x68,	0x69,	0x6B,	0x6C,	0x6D,
	0x6F,	0x70,	0x72,	0x73,	0x74,	0x76,	0x77,	0x79,
	0x7A,	0x7C,	0x7D,	0x7F,	0x80,	0x82,	0x83,	0x85,
	0x86,	0x88,	0x8A,	0x8B,	0x8D,	0x8E,	0x90,	0x92,
	0x93,	0x95,	0x97,	0x98,	0x9A,	0x9C,	0x9D,	0x9F,
	0xA1,	0xA3,	0xA4,	0xA6,	0xA8,	0xAA,	0xAB,	0xAD,
	0xAF,	0xB1,	0xB3,	0xB5,	0xB7,	0xB8,	0xBA,	0xBC,
	0xBE,	0xC0,	0xC2,	0xC4,	0xC6,	0xC8,	0xCA,	0xCC,
	0xCE,	0xD0,	0xD2,	0xD4,	0xD6,	0xD8,	0xDA,	0xDC,
	0xDE,	0xE0,	0xE2,	0xE5,	0xE7,	0xE9,	0xEB,	0xED,
	0xEF,	0xF2,	0xF4,	0xF6,	0xF8,	0xFA,	0xFD,	0xFF,
};

/*
 * generated with:
 * 	% seq 0 255 | awk '{$0 = $0/255.0; if($0 > 0.0031308) $0 = 1.055*$0^(1.0/2.4)-0.055; else $0 = $0*12.92; printf("\t0x%02X,%s", int($0*255+0.5), NR%8 == 0? "\n": "")}'
 */
static uchar linear2srgbtab[] = {
	0x00,	0x0D,	0x16,	0x1C,	0x22,	0x26,	0x2A,	0x2E,
	0x32,	0x35,	0x38,	0x3B,	0x3D,	0x40,	0x42,	0x45,
	0x47,	0x49,	0x4B,	0x4D,	0x4F,	0x51,	0x53,	0x55,
	0x56,	0x58,	0x5A,	0x5C,	0x5D,	0x5F,	0x60,	0x62,
	0x63,	0x65,	0x66,	0x68,	0x69,	0x6A,	0x6C,	0x6D,
	0x6E,	0x70,	0x71,	0x72,	0x73,	0x75,	0x76,	0x77,
	0x78,	0x79,	0x7A,	0x7C,	0x7D,	0x7E,	0x7F,	0x80,
	0x81,	0x82,	0x83,	0x84,	0x85,	0x86,	0x87,	0x88,
	0x89,	0x8A,	0x8B,	0x8C,	0x8D,	0x8E,	0x8F,	0x90,
	0x91,	0x92,	0x93,	0x94,	0x94,	0x95,	0x96,	0x97,
	0x98,	0x99,	0x9A,	0x9B,	0x9B,	0x9C,	0x9D,	0x9E,
	0x9F,	0x9F,	0xA0,	0xA1,	0xA2,	0xA3,	0xA3,	0xA4,
	0xA5,	0xA6,	0xA7,	0xA7,	0xA8,	0xA9,	0xAA,	0xAA,
	0xAB,	0xAC,	0xAD,	0xAD,	0xAE,	0xAF,	0xAF,	0xB0,
	0xB1,	0xB2,	0xB2,	0xB3,	0xB4,	0xB4,	0xB5,	0xB6,
	0xB6,	0xB7,	0xB8,	0xB9,	0xB9,	0xBA,	0xBB,	0xBB,
	0xBC,	0xBD,	0xBD,	0xBE,	0xBE,	0xBF,	0xC0,	0xC0,
	0xC1,	0xC2,	0xC2,	0xC3,	0xC4,	0xC4,	0xC5,	0xC5,
	0xC6,	0xC7,	0xC7,	0xC8,	0xC8,	0xC9,	0xCA,	0xCA,
	0xCB,	0xCB,	0xCC,	0xCD,	0xCD,	0xCE,	0xCE,	0xCF,
	0xD0,	0xD0,	0xD1,	0xD1,	0xD2,	0xD2,	0xD3,	0xD4,
	0xD4,	0xD5,	0xD5,	0xD6,	0xD6,	0xD7,	0xD7,	0xD8,
	0xD8,	0xD9,	0xDA,	0xDA,	0xDB,	0xDB,	0xDC,	0xDC,
	0xDD,	0xDD,	0xDE,	0xDE,	0xDF,	0xDF,	0xE0,	0xE0,
	0xE1,	0xE2,	0xE2,	0xE3,	0xE3,	0xE4,	0xE4,	0xE5,
	0xE5,	0xE6,	0xE6,	0xE7,	0xE7,	0xE8,	0xE8,	0xE9,
	0xE9,	0xEA,	0xEA,	0xEB,	0xEB,	0xEC,	0xEC,	0xED,
	0xED,	0xEE,	0xEE,	0xEE,	0xEF,	0xEF,	0xF0,	0xF0,
	0xF1,	0xF1,	0xF2,	0xF2,	0xF3,	0xF3,	0xF4,	0xF4,
	0xF5,	0xF5,	0xF6,	0xF6,	0xF6,	0xF7,	0xF7,	0xF8,
	0xF8,	0xF9,	0xF9,	0xFA,	0xFA,	0xFB,	0xFB,	0xFB,
	0xFC,	0xFC,	0xFD,	0xFD,	0xFE,	0xFE,	0xFF,	0xFF,
};

/*
 * Equations 5.32 and 5.30 from “Display Encoding”, Real-Time Rendering 4th ed. § 5.6
 */
//static double
//_srgb2linear(double c)
//{
//	if(c > 0.04045)
//		return pow((c + 0.055)/1.055, 2.4);
//	return c/12.92;
//}
//
//static double
//_linear2srgb(double c)
//{
//	if(c > 0.0031308)
//		return 1.055*pow(c, 1.0/2.4) - 0.055;
//	return 12.92*c;
//}

static double
_srgb2linear(double c)
{
	c = fclamp(c, 0, 1);
	return srgb2lineartab[(int)(c*255)]/255.0;
}

static double
_linear2srgb(double c)
{
	c = fclamp(c, 0, 1);
	return linear2srgbtab[(int)(c*255)]/255.0;
}

Color
srgb2linear(Color c)
{
	c.r = _srgb2linear(c.r);
	c.g = _srgb2linear(c.g);
	c.b = _srgb2linear(c.b);
	return c;
}

Color
linear2srgb(Color c)
{
	c.r = _linear2srgb(c.r);
	c.g = _linear2srgb(c.g);
	c.b = _linear2srgb(c.b);
	return c;
}


/* tone mapping */

/*
 * simplified ACES filmic tone mapping curve by Krzysztof Narkowicz
 * https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
 */
static double
_aces(double value)
{
	double a = 2.51;
	double b = 0.03;
	double c = 2.43;
	double d = 0.59;
	double e = 0.14;
	value = (value * (a * value + b)) / (value * (c * value + d) + e);
	return fclamp(value, 0, 1);
}

Color
aces(Color c)
{
	c.r = _aces(c.r);
	c.g = _aces(c.g);
	c.b = _aces(c.b);
	return c;
}

static double
RRTAndODTFit(double v)
{
	double a = v * (v + 0.0245786f) - 0.000090537f;
	double b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
	return a / b;
}

/*
 * a better ACES curve fit by Stephen Hill (@self_shadow)
 * https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl
 */
Color
aces2(Color c)
{
	/* sRGB → XYZ → D65_2_D60 → AP1 → RRT_SAT */
	Matrix3 ACESInputMat = {
		0.59719, 0.35458, 0.04823, 0,
		0.07600, 0.90834, 0.01566, 0,
		0.02840, 0.13383, 0.83777, 0,
		0, 0, 0, 1,
	};
	/* ODT_SAT → XYZ → D60_2_D65 → sRGB */
	Matrix3 ACESOutputMat = {
		 1.60475, -0.53108, -0.07367, 0,
		-0.10208,  1.10813, -0.00605, 0,
		-0.00327, -0.07276,  1.07602, 0,
		0, 0, 0, 1,
	};

	c = xform3(c, ACESInputMat);
	/* Apply RRT and ODT */
	c.r = RRTAndODTFit(c.r);
	c.g = RRTAndODTFit(c.g);
	c.b = RRTAndODTFit(c.b);

	c = xform3(c, ACESOutputMat);
	c.r = fclamp(c.r, 0, 1);
	c.g = fclamp(c.g, 0, 1);
	c.b = fclamp(c.b, 0, 1);
	return c;
}