|
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
|
<!DOCTYPE muclient>
|
|
|
<!-- Saved on Saturday, May 12, 2018, 12:51 AM -->
|
|
|
<!-- MuClient version 5.06-pre -->
|
|
|
|
|
|
<!-- Plugin "Contrast_Picker" generated by Plugin Wizard -->
|
|
|
|
|
|
<muclient>
|
|
|
<plugin
|
|
|
name="Contrast_Picker"
|
|
|
author="Crowley"
|
|
|
id="977138a7afb5ab493795edb7"
|
|
|
language="Lua"
|
|
|
purpose="Checks contrast ratios to pick better contrasting colors."
|
|
|
save_state="y"
|
|
|
date_written="2018-05-12 00:48:25"
|
|
|
requires="4.80"
|
|
|
version="1.0"
|
|
|
>
|
|
|
|
|
|
</plugin>
|
|
|
|
|
|
|
|
|
<!-- Aliases -->
|
|
|
|
|
|
<aliases>
|
|
|
<alias
|
|
|
script="onCommand"
|
|
|
match="^contrast (.*?)(?:\s(.*?))?(?:\s(.*?))?$"
|
|
|
enabled="y"
|
|
|
group="ContrastChecker"
|
|
|
regexp="y"
|
|
|
send_to="12"
|
|
|
sequence="100"
|
|
|
>
|
|
|
</alias>
|
|
|
</aliases>
|
|
|
|
|
|
<!-- Script -->
|
|
|
|
|
|
|
|
|
<script>
|
|
|
<![CDATA[
|
|
|
-- APCA Contrast Plugin
|
|
|
|
|
|
--[[ 1) HTML color list ]]
|
|
|
local color_table = {
|
|
|
"black","navy","darkblue","mediumblue","blue","royalblue","cornflowerblue",
|
|
|
"deepskyblue","lightskyblue","skyblue","lightblue","powderblue","lightsteelblue",
|
|
|
"steelblue","cadetblue","darkcyan","teal","aqua","cyan","lightcyan",
|
|
|
"mediumspringgreen","springgreen","mediumseagreen","seagreen","forestgreen",
|
|
|
"green","darkgreen","lawngreen","chartreuse","greenyellow","lime","limegreen",
|
|
|
"palegreen","lightgreen","darkolivegreen","olivedrab","yellow","lightyellow",
|
|
|
"lemonchiffon","lightgoldenrodyellow","papayawhip","moccasin","peachpuff",
|
|
|
"palegoldenrod","khaki","darkkhaki","gold","orange","darkorange","coral",
|
|
|
"tomato","orangered","red","darkred","firebrick","crimson","indianred",
|
|
|
"lightcoral","darksalmon","salmon","lightsalmon","sandybrown","peru","chocolate",
|
|
|
"darkgoldenrod","goldenrod","lightgray","silver","gray","darkgray","dimgray",
|
|
|
"lightslategray","slategray","darkslategray","white","snow","ghostwhite",
|
|
|
"whitesmoke","gainsboro","floralwhite","oldlace","linen","antiquewhite","beige",
|
|
|
"ivory","azure","aliceblue","lavender","lavenderblush","mistyrose","thistle",
|
|
|
"plum","violet","orchid","fuchsia","magenta","mediumorchid","darkorchid",
|
|
|
"darkviolet","blueviolet","purple","indigo"
|
|
|
}
|
|
|
|
|
|
--[[ 2) APCA constants & helpers ]]
|
|
|
local SA98G = {
|
|
|
mainTRC = 2.4,
|
|
|
sRco = 0.2126729,
|
|
|
sGco = 0.7151522,
|
|
|
sBco = 0.0721750,
|
|
|
normBG = 0.56,
|
|
|
normTXT = 0.57,
|
|
|
revBG = 0.62,
|
|
|
revTXT = 0.65,
|
|
|
loBoWoffset = 0.027,
|
|
|
loWoBoffset = 0.027,
|
|
|
deltaYmin = 0.0005,
|
|
|
loClip = 0.1,
|
|
|
scaleBoW = 1.14,
|
|
|
scaleWoB = 1.14,
|
|
|
}
|
|
|
|
|
|
local function clamp(x, lo, hi)
|
|
|
if x < lo then return lo end
|
|
|
if x > hi then return hi end
|
|
|
return x
|
|
|
end
|
|
|
|
|
|
-- unpack a 24-bit ColourNameToRGB() integer into r,g,b
|
|
|
local function unpackRGB(n)
|
|
|
local b = n % 256; n = math.floor(n/256)
|
|
|
local g = n % 256; n = math.floor(n/256)
|
|
|
local r = n % 256
|
|
|
return r, g, b
|
|
|
end
|
|
|
|
|
|
-- sRGB int or {r,g,b,a} → perceptual Y [0..1]
|
|
|
local function sRGBtoY(c)
|
|
|
local r,g,b
|
|
|
if type(c) == "table" then
|
|
|
r,g,b = c[1], c[2], c[3]
|
|
|
else
|
|
|
r,g,b = unpackRGB(tonumber(c))
|
|
|
end
|
|
|
local rn = (r/255)^SA98G.mainTRC
|
|
|
local gn = (g/255)^SA98G.mainTRC
|
|
|
local bn = (b/255)^SA98G.mainTRC
|
|
|
return SA98G.sRco*rn + SA98G.sGco*gn + SA98G.sBco*bn
|
|
|
end
|
|
|
|
|
|
-- blend fg {r,g,b,a} over bg {r,g,b,a}
|
|
|
local function alphaBlend(fg,bg)
|
|
|
local a = clamp(fg[4] or 1,0,1)
|
|
|
return {
|
|
|
fg[1]*a + bg[1]*(1-a),
|
|
|
fg[2]*a + bg[2]*(1-a),
|
|
|
fg[3]*a + bg[3]*(1-a),
|
|
|
1
|
|
|
}
|
|
|
end
|
|
|
|
|
|
-- APCA contrast: txtY & bgY in [0..1], returns signed Lᶜ
|
|
|
local function APCAcontrast(txtY, bgY, places)
|
|
|
txtY, bgY = clamp(txtY,0,1.1), clamp(bgY,0,1.1)
|
|
|
if math.abs(bgY - txtY) < SA98G.deltaYmin then return 0 end
|
|
|
|
|
|
local out
|
|
|
if bgY > txtY then
|
|
|
out = (bgY^SA98G.normBG - txtY^SA98G.normTXT) * SA98G.scaleBoW
|
|
|
if bgY < SA98G.loClip then out = out - SA98G.loBoWoffset end
|
|
|
else
|
|
|
out = (bgY^SA98G.revBG - txtY^SA98G.revTXT) * SA98G.scaleWoB
|
|
|
if txtY < SA98G.loClip then out = out + SA98G.loWoBoffset end
|
|
|
out = -out
|
|
|
end
|
|
|
|
|
|
if places and places >= 0 then
|
|
|
out = tonumber(string.format("%."..places.."f", out))
|
|
|
end
|
|
|
return out
|
|
|
end
|
|
|
|
|
|
-- convenience: takes two 24-bit ints or RGBA tables, handles alpha, returns Lᶜ
|
|
|
local function calcAPCA(fg, bg, places)
|
|
|
local fgC, bgC = fg, bg
|
|
|
if type(fgC)=="table" and fgC[4] then fgC = alphaBlend(fgC, bgC) end
|
|
|
return APCAcontrast( sRGBtoY(fgC), sRGBtoY(bgC), places or -1 )
|
|
|
end
|
|
|
|
|
|
--[[ 3) Precompute color codes ]]
|
|
|
local contrast_colors = {}
|
|
|
for _, name in ipairs(color_table) do
|
|
|
contrast_colors[#contrast_colors+1] = {
|
|
|
name = name,
|
|
|
code = ColourNameToRGB(name)
|
|
|
}
|
|
|
end
|
|
|
|
|
|
--[[ 4) The improved contrast command ]]
|
|
|
local function checkColorAPCA(fg_name, start_i, end_i)
|
|
|
local fg = (fg_name or ""):lower()
|
|
|
local fg_code = ColourNameToRGB(fg)
|
|
|
if fg == "" or fg_code == 0 then
|
|
|
Note("Unknown or missing color: "..tostring(fg_name))
|
|
|
return
|
|
|
end
|
|
|
|
|
|
-- build list with signed contrast and absolute score
|
|
|
local list = {}
|
|
|
for _, entry in ipairs(contrast_colors) do
|
|
|
local c = calcAPCA(fg_code, entry.code, 2)
|
|
|
list[#list+1] = {
|
|
|
bg_name = entry.name,
|
|
|
contrast = c,
|
|
|
score = math.abs(c),
|
|
|
}
|
|
|
end
|
|
|
|
|
|
-- sort by descending absolute contrast
|
|
|
table.sort(list, function(a,b) return a.score > b.score end)
|
|
|
|
|
|
-- sanitize slice bounds
|
|
|
local cnt = #list
|
|
|
start_i = clamp(tonumber(start_i) or 1, 1, cnt)
|
|
|
end_i = clamp(tonumber(end_i) or math.min(20, cnt), start_i, cnt)
|
|
|
|
|
|
Note("APCA contrast rankings for “"..fg.."”:")
|
|
|
for i = start_i, end_i do
|
|
|
local e = list[i]
|
|
|
local mode = (e.contrast > 0) and "dark-on-light" or "light-on-dark"
|
|
|
-- report the background name explicitly:
|
|
|
ColourNote(
|
|
|
fg,
|
|
|
e.bg_name,
|
|
|
string.format(
|
|
|
"%2d) on %-20s L = %6.2f (%s)",
|
|
|
i, e.bg_name, e.contrast, mode
|
|
|
)
|
|
|
)
|
|
|
end
|
|
|
end
|
|
|
|
|
|
--[[ 5) Hook up to MUSHClient ]]
|
|
|
function onCommand(name, line, args)
|
|
|
if args[1] and args[1]:upper()=="HELP" then
|
|
|
Note("APCA Contrast Help:")
|
|
|
Note(" contrast help this text")
|
|
|
Note(" contrast <fg> top 20 backgrounds")
|
|
|
Note(" contrast <fg> <start> <end> show ranks start–end")
|
|
|
Note("* <fg> must be a standard HTML color name")
|
|
|
else
|
|
|
checkColorAPCA(args[1], args[2], args[3])
|
|
|
end
|
|
|
end
|
|
|
|
|
|
function OnPluginInstall()
|
|
|
if not GetVariable("apca_shown") then
|
|
|
Note("Type “contrast help” for next-gen APCA-based contrast rankings.")
|
|
|
SetVariable("apca_shown", "1")
|
|
|
end
|
|
|
end
|
|
|
|
|
|
|
|
|
]]>
|
|
|
</script>
|
|
|
|
|
|
|
|
|
</muclient>
|