Moduuli:Värit
Tämän moduulin ohjeistuksen voi tehdä sivulle Moduuli:Värit/ohje
local export = {}
local EPSILON = 1E-12
local function clamp(min, val, max)
return math.max(min, math.min(max, val))
end
---
-- @param val: liukuluku
-- @param prec: (valinnainen) suurin desimaalien määrä
-- @return Annettu liukuluku merkkijonona ilman ylimääräisiä loppunollia.
-- Esim. "0.79", "1.0".
local function format_float(val, prec)
if prec then
prec = "0." .. prec
end
return string.format("%" .. (prec or "") .. "f", val):gsub("([01]%.[0-9][1-9]*)0+$", "%1")
end
local function parse_change_str(change_str)
local match, _, dir, amount = string.find(change_str, "^([+-]?)(%d+)°$")
if match then
amount = tonumber(amount)
return dir, amount
end
match, _, dir, amount = string.find(change_str, "^([+-]?)(%d+) ?%%$")
if match then
amount = tonumber(amount) / 100
assert(amount >= 0.0 and amount <= 1.0, "Määrän pitää olla välillä 0 %–100 %")
return dir, amount
end
error("Tuntematon muoto: " .. change_str)
end
local function invert_change_str(change_str)
local func = function (op)
if op == '-' then
return '+'
else
return '-'
end
end
return change_str:gsub("^([-+])", func)
end
local function round(val)
return math.floor(val + 0.5)
end
---
-- @param R [0..255]
-- @param G [0..255]
-- @param B [0..255]
-- @return H [0..360|nil], S [0..1], L [0..1]
-- Arvoilla joilla H = 0 ja S = 0, on erityismerkitys, että sekä H että S ovat määrittelemättömiä.
function export.rgb_to_hsl(R, G, B)
assert(R >= 0, "Virheellinen parametrin R arvo: " .. R)
assert(R < 256, "Virheellinen parametrin R arvo: " .. R)
assert(G >= 0, "Virheellinen parametrin G arvo: " .. G)
assert(G < 256, "Virheellinen parametrin G arvo: " .. G)
assert(B >= 0, "Virheellinen parametrin B arvo: " .. B)
assert(B < 256, "Virheellinen parametrin B arvo: " .. B)
local r = R / 255
local g = G / 255
local b = B / 255
local min = math.min(r, g, b)
local max = math.max(r, g, b)
local C = max - min
local L = (min + max) / 2
if C == 0 then
return 0, 0, L
end
local S
if L == 0 or L == 1 then
S = 0
else
S = C / (1 - math.abs(2*max - C - 1))
end
local H
if max == r then
H = (g - b) / C + 6
elseif max == g then
H = (b - r) / C + 2
else -- max == b
H = (r - g) / C + 4
end
H = H % 6
H = H * 60 % 360
return H, S, L
end
---
-- @param H [0..360|nil]
-- @param S [0..1]
-- @param L [0..1]
-- @return R [0..255], G [0..255], B [0..255]
function export.hsl_to_rgb_(H, S, L)
assert(H >= 0, "Virheellinen parametrin H arvo: " .. H)
assert(H < 360, "Virheellinen parametrin H arvo: " .. H)
assert(S >= 0, "Virheellinen parametrin S arvo: " .. S)
assert(S <= 1 + EPSILON, "Virheellinen parametrin S arvo: " .. S)
assert(L >= 0, "Virheellinen parametrin L arvo: " .. L)
assert(L <= 1 + EPSILON, "Virheellinen parametrin L arvo: " .. L)
local C = (1 - math.abs(2 * L - 1)) * S
local Hp = H / 60
local X = C * (1 - math.abs(Hp % 2 - 1))
local R1, G1, B1
assert(Hp >= 0, "Hp < 0")
assert(Hp < 6, "Hp >= 6")
if Hp < 1 then
R1 = C
G1 = X
B1 = 0
elseif Hp < 2 then
R1 = X
G1 = C
B1 = 0
elseif Hp < 3 then
R1 = 0
G1 = C
B1 = X
elseif Hp < 4 then
R1 = 0
G1 = X
B1 = C
elseif Hp < 5 then
R1 = X
G1 = 0
B1 = C
elseif Hp < 6 then
R1 = C
G1 = 0
B1 = X
end
local m = L - C / 2
local r = R1 + m
local g = G1 + m
local b = B1 + m
return r * 255, g * 255, b * 255
end
function export.hsl_to_rgb(H, S, L)
local R, G, B = export.hsl_to_rgb_(H, S, L)
return math.floor(R), math.floor(G), math.floor(B)
end
---
-- Tummentaa tai vaalentaa annetun värin.
-- @param p: negatiivinen tai positiivinen prosenttiluku
function export.change_luminosity(r, g, b, diff)
local h, s, l = export.rgb_to_hsl(r, g, b)
return export.hsl_to_rgb(h, s, clamp(0, l + diff, 1))
end
---
-- Tummentaa tai vaalentaa annetun värin.
-- @param p: negatiivinen tai positiivinen prosenttiluku
function export.set_luminosity(r, g, b, new_l)
local h, s, l = export.rgb_to_hsl(r, g, b)
return export.hsl_to_rgb(h, s, clamp(0, new_l, 1))
end
---
-- Himmentää tai kirkastaa
-- @param p: negatiivinen tai positiivinen prosenttiluku
function export.change_saturation(r, g, b, diff)
local h, s, l = export.rgb_to_hsl(r, g, b)
return export.hsl_to_rgb(h, clamp(0, l + diff, 1), l)
end
---
-- Himmentää tai kirkastaa
-- @param p: negatiivinen tai positiivinen prosenttiluku
function export.set_saturation(r, g, b, new_s)
local h, s, l = export.rgb_to_hsl(r, g, b)
return export.hsl_to_rgb(h, new_s, l)
end
---
-- Himmentää tai kirkastaa
-- @param deg: negatiivinen tai positiivinen asteluku
function export.change_hue(r, g, b, deg)
deg = deg % 360
assert(deg >= 0 and deg < 360, "Virheellinen parametrin deg arvo")
local h, s, l = export.rgb_to_hsl(r, g, b)
return export.hsl_to_rgb((h + deg) % 360, s, l)
end
---
-- Himmentää tai kirkastaa
-- @param deg: negatiivinen tai positiivinen asteluku
function export.set_hue(r, g, b, new_h)
local h, s, l = export.rgb_to_hsl(r, g, b)
return export.hsl_to_rgb(new_h, s, l)
end
function export.parse_color(color_str)
local match, _, r, g, b, a = string.find(color_str, "^rgba%((%d+), *(%d+), *(%d+), *([01])%)$")
if not match then
match, _, r, g, b, a = string.find(color_str, "^rgba%((%d+), *(%d+), *(%d+), *([01]%.%d+)%)$")
end
if match then
r = tonumber(r)
g = tonumber(g)
b = tonumber(b)
a = tonumber(a)
assert(r >= 0 and r <= 255, "Komponentin arvon pitää olla välillä 0–255")
assert(g >= 0 and g <= 255, "Komponentin arvon pitää olla välillä 0–255")
assert(b >= 0 and b <= 255, "Komponentin arvon pitää olla välillä 0–255")
assert(a >= 0 and a <= 1, "Alfa-arvon pitää olla välillä 0.0–1.0")
return "rgba", r, g, b, a
end
match, _, r, g, b = string.find(color_str, "^rgb%((%d+), *(%d+), *(%d+)%)$")
if match then
r = tonumber(r)
g = tonumber(g)
b = tonumber(b)
assert(r >= 0 and r <= 255, "Komponentin arvon pitää olla välillä 0–255")
assert(g >= 0 and g <= 255, "Komponentin arvon pitää olla välillä 0–255")
assert(b >= 0 and b <= 255, "Komponentin arvon pitää olla välillä 0–255")
return "rgb", r, g, b, nil
end
local r_hex, g_hex, b_hex
match, _, r_hex, g_hex, b_hex = string.find(color_str, "^#(%x%x)(%x%x)(%x%x)$")
if match then
return "hex6", tonumber(r_hex, 16), tonumber(g_hex, 16), tonumber(b_hex, 16), nil
end
match, _, r_hex, g_hex, b_hex = string.find(color_str, "^#(%x)(%x)(%x)$")
if match then
return "hex3", tonumber(r_hex, 16)*16, tonumber(g_hex, 16)*16, tonumber(b_hex, 16)*16, nil
end
local val
match, _, val = string.find(color_str, "^gray%((%d+%.%d+)%)$")
if match then
val = tonumber(val)
assert(val and val >= 0.0 and val <= 1.0, "Harmaasävyn arvon pitää olla väliltä 0.0–1.0")
return "gray0.1", math.floor(val * 255), math.floor(val * 255), math.floor(val * 255), nil
end
error("Tuntematon muoto: " .. color_str)
end
function export.format_color_string(r, g, b, a, format)
if format == "rgba" then
a = a or 1.0
return string.format("rgba(%d, %d, %d, %s)", r, g, b, format_float(a))
end
if format == "rgb" then
return string.format("rgb(%d, %d, %d)", r, g, b)
end
if format == "hex3" then
return string.format("#%x%x%x", round(r/16), round(g/16), round(b/16))
end
if format == "hex6" then
return string.format("#%02x%02x%02x", r, g, b)
end
if format == "rgb0.1" then
-- Timelinen käyttämä muoto.
return string.format("rgb(%s,%s,%s)", format_float(r/255, 3), format_float(g/255, 3), format_float(b/255, 3))
end
error("Tuntematon muoto: " .. format)
end
function export.to_format(color_str, output_format)
local format, r, g, b, a = export.parse_color(color_str)
return export.format_color_string(r, g, b, a, output_format)
end
function export.alter_color(args)
local color_str = args.color
local hue = args.hue
local luminosity = args.luminosity
local saturation = args.saturation
local output_format = args.output
local format, r, g, b, a = export.parse_color(color_str)
if hue then
local dir, amount = parse_change_str(hue)
if dir == '+' then
r, g, b = export.change_hue(r, g, b, amount)
elseif dir == '-' then
r, g, b = export.change_hue(r, g, b, -amount)
else
r, g, b = export.set_hue(r, g, b, amount)
end
end
if saturation then
local dir, amount = parse_change_str(saturation)
if dir == '+' then
r, g, b = export.change_saturation(r, g, b, amount)
elseif dir == '-' then
r, g, b = export.change_saturation(r, g, b, -amount)
else
r, g, b = export.set_saturation(r, g, b, amount)
end
end
if luminosity then
local dir, amount = parse_change_str(luminosity)
if dir == '+' then
r, g, b = export.change_luminosity(r, g, b, amount)
elseif dir == '-' then
r, g, b = export.change_luminosity(r, g, b, -amount)
else
r, g, b = export.set_luminosity(r, g, b, amount)
end
end
return export.format_color_string(r, g, b, a, output_format or format)
end
export['väri'] = function(frame)
local pframe = frame:getParent()
local vaaleus
if pframe.args.tummuus then
vaaleus = invert_change_str(pframe.args.tummuus)
end
local args = {
color = pframe.args[1],
hue = pframe.args.kierto,
luminosity = vaaleus,
saturation = pframe.args['kylläisyys'],
output = pframe.args.muoto
}
if not args.hue and not args.luminosity and not args.saturation then
assert(args.output, "Anna vähintään yksi parametreista tummuus, kylläisyys, kierto tai muoto")
return export.to_format(args.color, args.output)
end
return export.alter_color(args)
end
return export