local SA98G = { mainTRC = 2.4, mainTRCencode = 1 / 2.4, sRco = 0.2126729, sGco = 0.7151522, sBco = 0.0721750, normBG = 0.56, normTXT = 0.57, revTXT = 0.62, revBG = 0.65, blkThrs = 0.022, blkClmp = 1.414, scaleBoW = 1.14, scaleWoB = 1.14, loBoWoffset = 0.027, loWoBoffset = 0.027, deltaYmin = 0.0005, loClip = 0.1, mFactor = 1.94685544331710, mFactInv = 1 / 1.94685544331710, mOffsetIn = 0.03873938165714010, mExpAdj = 0.2833433964208690, mExp = 0.2833433964208690 / 1.414, mOffsetOut = 0.3128657958707580, } local function isNaN(n) return n ~= n end local function reverseAPCA(contrast, knownY, knownType, returnAs) if contrast == nil then contrast = 0 end if knownY == nil then knownY = 1.0 end if knownType == nil then knownType = "bg" end if returnAs == nil then returnAs = "hex" end if math.abs(contrast) < 9 then return false end local unknownY = knownY local knownExp local unknownExp --/// APCA 0.0.98G - 4g - W3 Compatible Constants //////////////////// local scale = if contrast > 0 then SA98G.scaleBoW else SA98G.scaleWoB local offset = if contrast > 0 then SA98G.loBoWoffset else -SA98G.loWoBoffset contrast = (assert(tonumber(contrast)) * 0.01 + offset) / scale -- Soft clamps Y if it is near black. knownY = if (knownY > SA98G.blkThrs) then knownY else knownY + math.pow(SA98G.blkThrs - knownY, SA98G.blkClmp) -- set the known and unknown exponents if knownType == "bg" or knownType == "background" then knownExp = if contrast > 0 then SA98G.normBG else SA98G.revBG unknownExp = if contrast > 0 then SA98G.normTXT else SA98G.revTXT unknownY = math.pow(math.pow(knownY, knownExp) - contrast, 1 / unknownExp) if isNaN(unknownY) then return false end elseif knownType == "txt" or knownType == "text" then knownExp = if contrast > 0 then SA98G.normTXT else SA98G.revTXT unknownExp = if contrast > 0 then SA98G.normBG else SA98G.revBG unknownY = math.pow(contrast + math.pow(knownY, knownExp), 1 / unknownExp) if isNaN(unknownY) then return false end else return false end --return contrast +'----'+unknownY; if unknownY > 1.06 or unknownY < 0 then return false end -- if (unknownY < 0) { return false } // return false on underflow --unknownY = math.max(unknownY,0.0); -- unclamp unknownY = if (unknownY > SA98G.blkThrs) then unknownY else (math.pow(((unknownY + SA98G.mOffsetIn) * SA98G.mFactor), SA98G.mExp) * SA98G.mFactInv) - SA98G.mOffsetOut -- unknownY - 0.22 * math.pow(unknownY*0.5, 1/blkClmp); unknownY = math.max(math.min(unknownY, 1.0), 0.0) if returnAs == "color" then local colorB = math.round(math.pow(unknownY, SA98G.mainTRCencode) * 255) local retUse = if (knownType == "bg") then "txtColor" else "bgColor" return { colorB, colorB, colorB, 1, retUse } elseif returnAs == "Y" or returnAs == "y" then return math.max(0.0, unknownY) else return false end end local function sRGBtoY(sRGBcolor: Color3) local r = sRGBcolor.R local g = sRGBcolor.G local b = sRGBcolor.B local function simpleExp(chan) return math.pow(chan, SA98G.mainTRC) end return SA98G.sRco * simpleExp(r) + SA98G.sGco * simpleExp(g) + SA98G.sBco * simpleExp(b) end local function displayP3toY(rgb: Color3) local mainTRC = 2.4 local sRco, sGco, sBco = 0.2289829594805780, 0.6917492625852380, 0.0792677779341829 local function simpleExp(chan) return math.pow(chan, mainTRC) end return sRco * simpleExp(rgb.R) + sGco * simpleExp(rgb.G) + sBco * simpleExp(rgb.B) end local function adobeRGBtoY(rgb: Color3) local mainTRC = 2.35 local sRco = 0.2973550227113810 local sGco = 0.6273727497145280 local sBco = 0.0752722275740913 local function simpleExp(chan) return math.pow(chan / 255.0, mainTRC) end return sRco * simpleExp(rgb.R) + sGco * simpleExp(rgb.G) + sBco * simpleExp(rgb.B) end local function APCAcontrast(txtY, bgY, places) places = places or -1 local icp = { 0, 1.1 } if math.min(txtY, bgY) < icp[1] or math.max(txtY, bgY) > icp[2] then return 0 end local SAPC = 0 local outputContrast = 0 local polCat = "BoW" txtY = (txtY > SA98G.blkThrs) and txtY or txtY + math.pow(SA98G.blkThrs - txtY, SA98G.blkClmp) bgY = (bgY > SA98G.blkThrs) and bgY or bgY + math.pow(SA98G.blkThrs - bgY, SA98G.blkClmp) if math.abs(bgY - txtY) < SA98G.deltaYmin then return 0 end if bgY > txtY then -- black text on white SAPC = (math.pow(bgY, SA98G.normBG) - math.pow(txtY, SA98G.normTXT)) * SA98G.scaleBoW outputContrast = (SAPC < SA98G.loClip) and 0.0 or SAPC - SA98G.loBoWoffset else -- should always return negative polCat = "WoB" -- white on black SAPC = (math.pow(bgY, SA98G.revBG) - math.pow(txtY, SA98G.revTXT)) * SA98G.scaleWoB outputContrast = (SAPC > -SA98G.loClip) and 0.0 or SAPC + SA98G.loWoBoffset end if places < 0 then return outputContrast * 100.0 elseif places == 0 then return math.round(math.abs(outputContrast) * 100.0) --+ "" + polCat + "" -- why is there html elseif places // 1 == places then return (outputContrast * 100.0) * places // 1 / places else return 0.0 end end local function alphaBlend(rgbFG: Color3, aFG: number, rgbBG: Color3, round: boolean?) round = if round == nil then true else round aFG = aFG or 1 local compBlend = 1 - aFG local rgbOut = {0, 0, 0} rgbOut[1] = rgbBG.R * compBlend + rgbFG.R * aFG if round then rgbOut[1] = math.min(math.round(rgbOut[1]), 255) end rgbOut[2] = rgbBG.G * compBlend + rgbFG.G * aFG if round then rgbOut[2] = math.min(math.round(rgbOut[2]), 255) end rgbOut[3] = rgbBG.B * compBlend + rgbFG.B * aFG if round then rgbOut[3] = math.min(math.round(rgbOut[3]), 255) end return Color3.new(rgbOut[1], rgbOut[2], rgbOut[3]) end local function calcAPCA(textcolor: Color3, bgColor: Color3, textalpha: number?, places: number?, round: boolean?) places = -1 --todo: alpha blending if textalpha then textcolor = alphaBlend(textcolor, textalpha, bgColor, round) end return APCAcontrast(sRGBtoY(textcolor), sRGBtoY(bgColor), places) end local function fontLookupAPCA(contrast, places: number?) places = places or 2 -- Font size interpolations. Here the chart was re-ordered to put -- the main contrast levels each on one line, instead of font size per line. -- First column is LC value, then each following column is font size by weight -- G G G G G G Public Beta 0.1.7 (G) • MAY 28 2022 -- Lc values under 70 should have Lc 15 ADDED if used for body text -- All font sizes are in px and reference font is Barlow -- 999: prohibited - too low contrast -- 777: NON TEXT at this minimum weight stroke -- 666 - this is for spot text, not fluent-Things like copyright or placeholder. -- 5xx - minimum font at this weight for content, 5xx % 500 for font-size -- 4xx - minimum font at this weight for any purpose], 4xx % 400 for font-size -- MAIN FONT SIZE LOOKUP ---- ASCENDING SORTED Public Beta 0.1.7 (G) • MAY 28 2022 //// ---- Lc 45 * 0.2 = 9 which is the index for the row for Lc 45 -- MAIN FONT LOOKUP May 28 2022 EXPANDED -- Sorted by Lc Value -- First row is standard weights 100-900 -- First column is font size in px -- All other values are the Lc contrast -- 999 = too low. 777 = non-text and spot text only local fontMatrixAscend = { {'Lc',100,200,300,400,500,600,700,800,900}, {0,999,999,999,999,999,999,999,999,999}, {10,999,999,999,999,999,999,999,999,999}, {15,777,777,777,777,777,777,777,777,777}, {20,777,777,777,777,777,777,777,777,777}, {25,777,777,777,120,120,108,96,96,96}, {30,777,777,120,108,108,96,72,72,72}, {35,777,120,108,96,72,60,48,48,48}, {40,120,108,96,60,48,42,32,32,32}, {45,108,96,72,42,32,28,24,24,24}, {50,96,72,60,32,28,24,21,21,21}, {55,80,60,48,28,24,21,18,18,18}, {60,72,48,42,24,21,18,16,16,18}, {65,68,46,32,21.75,19,17,15,16,18}, {70,64,44,28,19.5,18,16,14.5,16,18}, {75,60,42,24,18,16,15,14,16,18}, {80,56,38.25,23,17.25,15.81,14.81,14,16,18}, {85,52,34.5,22,16.5,15.625,14.625,14,16,18}, {90,48,32,21,16,15.5,14.5,14,16,18}, {95,45,28,19.5,15.5,15,14,13.5,16,18}, {100,42,26.5,18.5,15,14.5,13.5,13,16,18}, {105,39,25,18,14.5,14,13,12,16,18}, {110,36,24,18,14,13,12,11,16,18}, {115,34.5,22.5,17.25,12.5,11.875,11.25,10.625,14.5,16.5}, {120,33,21,16.5,11,10.75,10.5,10.25,13,15}, {125,32,20,16,10,10,10,10,12,14}, } local fontDeltaAscend = { {'∆Lc',100,200,300,400,500,600,700,800,900}, {0,0,0,0,0,0,0,0,0,0}, {10,0,0,0,0,0,0,0,0,0}, {15,0,0,0,0,0,0,0,0,0}, {20,0,0,0,0,0,0,0,0,0}, {25,0,0,0,12,12,12,24,24,24}, {30,0,0,12,12,36,36,24,24,24}, {35,0,12,12,36,24,18,16,16,16}, {40,12,12,24,18,16,14,8,8,8}, {45,12,24,12,10,4,4,3,3,3}, {50,16,12,12,4,4,3,3,3,3}, {55,8,12,6,4,3,3,2,2,0}, {60,4,2,10,2.25,2,1,1,0,0}, {65,4,2,4,2.25,1,1,0.5,0,0}, {70,4,2,4,1.5,2,1,0.5,0,0}, {75,4,3.75,1,0.75,0.188,0.188,0,0,0}, {80,4,3.75,1,0.75,0.188,0.188,0,0,0}, {85,4,2.5,1,0.5,0.125,0.125,0,0,0}, {90,3,4,1.5,0.5,0.5,0.5,0.5,0,0}, {95,3,1.5,1,0.5,0.5,0.5,0.5,0,0}, {100,3,1.5,0.5,0.5,0.5,0.5,1,0,0}, {105,3,1,0,0.5,1,1,1,0,0}, {110,1.5,1.5,0.75,1.5,1.125,0.75,0.375,1.5,1.5}, {115,1.5,1.5,0.75,1.5,1.125,0.75,0.375,1.5,1.5}, {120,1,1,0.5,1,0.75,0.5,0.25,1,1}, {125,0,0,0,0,0,0,0,0,0}, }; local weightArray = {0, 100, 200, 300, 400, 500, 600, 700, 800, 900} local weightArrayLen = #weightArray local returnArray = {tostring(contrast * places // 1 / places), 0, 0, 0, 0, 0, 0, 0, 0, 0} local returnArrayLen = #returnArray local contrastArrayAscend = {'lc',0,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100,105,110,115,120,125,} local contrastArrayLenAsc = #contrastArrayAscend -- Lc 45 * 0.2 = 9 and 9 is the index for the row for lc 45 local tempFont = 777 local contrast = math.abs(contrast) local factor = 0.2 local index = contrast == 0 and 1 or bit32.bor(contrast * factor, 0) local w = 0 local scoreAdj = (contrast - fontMatrixAscend[index + 1][w + 1]) * factor w += 1 while w < weightArrayLen do w += 1 tempFont = fontMatrixAscend[index+1][w+1] if tempFont > 400 then returnArray[w + 1] = tempFont elseif contrast < 14.5 then returnArray[w + 1] = 999 elseif contrast < 29.5 then returnArray[w + 1] = 777 else --- interpolation of font size if tempFont > 24 then returnArray[w + 1] = math.round(tempFont - (fontDeltaAscend[index + 1][w + 1] * scoreAdj)) else returnArray[w + 1] = tempFont - ((2 * fontDeltaAscend[index + 1][w + 1] * scoreAdj // 1) * 0.5) end end end return returnArray end return { APCAcontrast = APCAcontrast, reverseAPCA = reverseAPCA, calcAPCA = calcAPCA, fontLookupAPCA = fontLookupAPCA, sRGBtoY = sRGBtoY, displayP3toY = displayP3toY, adobeRGBtoY = adobeRGBtoY, alphaBlend = alphaBlend }