Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

// request.cf · coarse context

A page that knows where it met you.

Only coarse request metadata is shown. This demo does not display or persist visitor IP addresses.

Country
US
Cloudflare location
CMH
Connection
HTTP/2
Language
Not provided

Ray ID: a2295678b8b1ea0a

Jump to content

Module:Color temperature

From Wikipedia, the free encyclopedia

-- Module:Color temperature

local p = {}

local function blackbody_xy(T)
	-- Formulas from https://github.com/crosscountrycoder/color-converter/blob/main/README.md#polynomial_fitpy-and-polynomial_testpy
	-- T must be greater than or equal to 800 (this is enforced elsewhere in the code)
	local x, y

	if T < 1661 then
		x = 0.12790668 + 1311.3574 / T - 1335109.8 / T^2 + 6.9429482e8 / T^3 - 1.4571589e11 / T^4
		y = 0.35946988 + 477.95393 / T - 1062716.0 / T^2 + 7.5671541e8 / T^3 - 1.8700243e11 / T^4

	elseif T < 4328 then
		x = 0.21594484 + 460.83273 / T + 1497568.7 / T^2 - 3.3177634e9 / T^3 + 1.9306612e12 / T^4
		y = 0.15480888 + 1394.6527 / T - 2259297.6 / T^2 + 8.4916936e8 / T^3 + 3.1769991e11 / T^4

	else
		x = 0.23994527 + 246.81976 / T + 1707275.0 / T^2 - 5.4391967e8 / T^3 - 5.0736771e12 / T^4
		y = 0.23417818 + 359.64997 / T + 2585341.6 / T^2 - 8.1436004e9 / T^3 + 4.5501218e12 / T^4
	end

	return x, y
end

local function xy_to_XYZ(x, y)
	if y == 0 then
		return 0, 0, 0
	end

	local Y = 1
	local X = x / y
	local Z = (1 - x - y) / y

	return X, Y, Z
end

local function XYZ_to_linear_sRGB(X, Y, Z)
	local r =  3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z
	local g = -0.9692660 * X + 1.8760108 * Y + 0.0415560 * Z
	local b =  0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z

	return r, g, b
end

local function normalize_rgb(r, g, b)
	-- First normalize so the brightest linear RGB channel is 1.
	local max_channel = math.max(r, g, b)

	if max_channel <= 0 then
		return 0, 0, 0
	end

	r = r / max_channel
	g = g / max_channel
	b = b / max_channel

	-- Then, if needed, compress toward D65 white.
	--
	-- In linear sRGB, D65 white is (1, 1, 1). This transformation moves
	-- the color along the line toward white until the lowest channel reaches 0,
	-- while keeping the highest channel at 1.
	--
	-- Example:
	--   (1.0, 0.25, -0.5) -> (1.0, 0.5, 0.0)
	local min_channel = math.min(r, g, b)

	if min_channel < 0 then
		local denominator = 1 - min_channel

		r = (r - min_channel) / denominator
		g = (g - min_channel) / denominator
		b = (b - min_channel) / denominator
	end

	return r, g, b
end

local function linear_to_srgb(c)
	if c <= 0.0031308 then
		return 12.92 * c
	end

	return 1.055 * c^(1 / 2.4) - 0.055
end

local function to_8bit(c)
	return math.floor(255 * linear_to_srgb(c) + 0.5)
end

local function rgb_to_hex(r, g, b)
	return string.format("%02x%02x%02x", r, g, b)
end

local function temperature_to_rgb(T)
	local x, y = blackbody_xy(T)
	local X, Y, Z = xy_to_XYZ(x, y)
	local r, g, b = XYZ_to_linear_sRGB(X, Y, Z)

	r, g, b = normalize_rgb(r, g, b)

	return to_8bit(r), to_8bit(g), to_8bit(b)
end

local function get_temperature(frame)
	local parent = frame:getParent()
	local raw = frame.args[1]

	if (not raw or raw == "") and parent then
		raw = parent.args[1]
	end

	return tonumber(raw), raw
end

local function get_arg(frame, name)
	local parent = frame:getParent()
	local value = frame.args[name]

	if (not value or value == "") and parent then
		value = parent.args[name]
	end

	return value
end

function p.hex(frame)
	local T, raw = get_temperature(frame)

	if not T then
		return ""
	end

	if T < 800 then
		return "Error: Temperature must be at least 800 K"
	end

	local r, g, b = temperature_to_rgb(T)
	return rgb_to_hex(r, g, b)
end

function p.rgb(frame)
	local T, raw = get_temperature(frame)

	if not T then
		return ""
	end

	if T < 800 then
		return "Error: Temperature must be at least 800 K"
	end

	local r, g, b = temperature_to_rgb(T)
	return string.format("%d, %d, %d", r, g, b)
end

function p.xy(frame)
	local T, raw = get_temperature(frame)

	if not T then
		return ""
	end

	if T < 800 then
		return "Error: Temperature must be at least 800 K"
	end

	local x, y = blackbody_xy(T)
	return string.format("%.8f, %.8f", x, y)
end

function p.swatch(frame)
	local T, raw = get_temperature(frame)

	if not T then
		return ""
	end

	if T < 800 then
		return "Error: Temperature must be at least 800 K"
	end

	local text = get_arg(frame, "text")
	local r, g, b = temperature_to_rgb(T)
	local hex = rgb_to_hex(r, g, b)

	if not text or text == "" then
		text = mw.language.getContentLanguage():formatNum(T) .. " K"
	end

	return string.format(
		'<span class="mw-no-invert" style="background:gray;padding:1px 1px 1px 0;color:white;border:solid 2px gray;margin:0 0.1em;text-align:center;vertical-align:middle;display:inline;font-family:sans-serif"><span style="padding:1px;margin:0 3px 0 0;background:#%s">&nbsp;&nbsp;&nbsp;&nbsp;</span><small>%s</small></span>',
		hex,
		text
	)
end

return p