-- Introduction: https://colorspace.r-forge.r-project.org/articles/color_spaces.htmllocalp={}localfunctionisempty(v)returnv==nilorv==''endlocalfunctionhexToRgb(color)localcleanColor=color:gsub("#","#"):match('^[%s#]*(.-)[%s;]*$')if(#cleanColor==6)thenreturn{r=tonumber(string.sub(cleanColor,1,2),16),g=tonumber(string.sub(cleanColor,3,4),16),b=tonumber(string.sub(cleanColor,5,6),16)}elseif(#cleanColor==3)thenreturn{r=17*tonumber(string.sub(cleanColor,1,1),16),g=17*tonumber(string.sub(cleanColor,2,2),16),b=17*tonumber(string.sub(cleanColor,3,3),16)}enderror("Invalid hexadecimal color "..cleanColor,1)endlocalfunctionround(v)if(v<0)thenreturnmath.ceil(v-0.5)elsereturnmath.floor(v+0.5)endendlocalfunctionrgbToHex(r,g,b)returnstring.format("%02X%02X%02X",round(r),round(g),round(b))endlocalfunctionrgbToCmyk(r,g,b)if(r>255org>255orb>255orr<0org<0orb<0)thenerror("Color level out of bounds")endlocalc=1-r/255localm=1-g/255localy=1-b/255localk=math.min(c,m,y)if(k==1)thenc=0m=0y=0elselocald=1-kc=(c-k)/dm=(m-k)/dy=(y-k)/dendreturn{c=c*100,m=m*100,y=y*100,k=k*100}endlocalfunctionrgbToHsl(r,g,b)if(r>255org>255orb>255orr<0org<0orb<0)thenerror("Color level out of bounds")endlocalchannelMax=math.max(r,g,b)localchannelMin=math.min(r,g,b)localrange=channelMax-channelMinlocalh,sif(range==0)thenh=0elseif(channelMax==r)thenh=60*((g-b)/range)if(h<0)thenh=360+hendelseif(channelMax==g)thenh=60*(2+(b-r)/range)elseh=60*(4+(r-g)/range)endlocalL=channelMax+channelMinif(L==0orL==510)thens=0elses=100*range/math.min(L,510-L)endreturn{h=h,s=s,l=L*50/255}endlocalfunctionrgbToHsv(r,g,b)if(r>255org>255orb>255orr<0org<0orb<0)thenerror("Color level out of bounds")endlocalchannelMax=math.max(r,g,b)localchannelMin=math.min(r,g,b)localrange=channelMax-channelMinlocalh,sif(range==0)thenh=0elseif(channelMax==r)thenh=60*((g-b)/range)if(h<0)thenh=360+hendelseif(channelMax==g)thenh=60*(2+(b-r)/range)elseh=60*(4+(r-g)/range)endif(channelMax==0)thens=0elses=100*range/channelMaxendreturn{h=h,s=s,v=channelMax*100/255}end-- c in [0, 255], condition tweaked for no discontinuity-- http://entropymine.com/imageworsener/srgbformula/localfunctiontoLinear(c)if(c>10.314300250662591)thenreturnmath.pow((c+14.025)/269.025,2.4)elsereturnc/3294.6endendlocalfunctiontoNonLinear(c)if(c>0.00313066844250063)thenreturn269.025*math.pow(c,1.0/2.4)-14.025elsereturn3294.6*cendendlocalfunctionsrgbToCielchuvD65o2deg(r,g,b)if(r>255org>255orb>255orr<0org<0orb<0)thenerror("Color level out of bounds")endlocalR=toLinear(r)localG=toLinear(g)localB=toLinear(b)-- https://github.com/w3c/csswg-drafts/issues/5922localX=0.1804807884018343*B+0.357584339383878*G+0.41239079926595934*RlocalY=0.07219231536073371*B+0.21263900587151027*R+0.715168678767756*GlocalZ=0.01933081871559182*R+0.11919477979462598*G+0.9505321522496607*BlocalL,C,hif(Y>0.00885645167903563082)thenL=116*math.pow(Y,1/3)-16elseL=Y*903.2962962962962962963endif((r==gandg==b)orL==0)thenC=0h=0elsed=X+3*Z+15*Yif(d==0)thenC=0h=0else-- 0.19783... and 0.4631... computed with extra precision from (X,Y,Z) when (R,G,B) = (1,1,1),-- in which case (u,v) ≈ (0,0)localus=4*X/d-0.19783000664283678994localvs=9*Y/d-0.46831999493879099801h=math.atan2(vs,us)*57.2957795130823208768if(h<0)thenh=h+360elseif(h==0)thenh=0-- ensure zero is positiveendC=math.sqrt(us*us+vs*vs)*13*Lif(C==0)thenC=0h=0endendendreturn{L=L,C=C,h=h}endlocalfunctionsrgbMix(t,r0,g0,b0,r1,g1,b1)if(t>1ort<0)thenerror("Interpolation parameter out of bounds")endif(r0>255org0>255orb0>255orr1>255org1>255orb1>255orr0<0org0<0orb0<0orr1<0org1<0orb1<0)thenerror("Color level out of bounds")endlocaltc=1-treturn{r=toNonLinear(tc*toLinear(r0)+t*toLinear(r1)),g=toNonLinear(tc*toLinear(g0)+t*toLinear(g1)),b=toNonLinear(tc*toLinear(b0)+t*toLinear(b1))}endlocalfunctionformatToPrecision(value,p)returnstring.format("%."..p.."f",value)endlocalfunctiongetFractionalZeros(p)if(p>0)thenreturn"."..string.rep("0",p)elsereturn""endendfunctionp.hexToRgbTriplet(frame)localargs=frame.argsorframe:getParent().argslocalhex=args[1]if(hex)thenlocalrgb=hexToRgb(hex)returnrgb.r..', '..rgb.g..', '..rgb.belsereturn""endendfunctionp.rgbTripletToHex(frame)localargs=frame.argsorframe:getParent().argslocalr=tonumber(args[1])localg=tonumber(args[2])localb=tonumber(args[3])if(isempty(r)orisempty(g)orisempty(b))thenreturn""elsereturnrgbToHex(r,g,b)endendfunctionp.hexToCmyk(frame)localargs=frame.argsorframe:getParent().argslocalhex=args[1]if(hex)thenlocalp=tonumber(args.precision)or0locals=args.pctsignor"1"localrgb=hexToRgb(hex)localcmyk=rgbToCmyk(rgb.r,rgb.g,rgb.b)localfk=formatToPrecision(cmyk.k,p)localfc,fm,fylocalfracZeros=getFractionalZeros(p)if(fk==100..fracZeros)thenlocalfZero=0..fracZerosfc=fZerofm=fZerofy=fZeroelsefc=formatToPrecision(cmyk.c,p)fm=formatToPrecision(cmyk.m,p)fy=formatToPrecision(cmyk.y,p)endif(s~="0")thenreturnfc.."%, "..fm.."%, "..fy.."%, "..fk.."%"elsereturnfc..", "..fm..", "..fy..", "..fkendelsereturn""endendfunctionp.hexToHsl(frame)localargs=frame.argsorframe:getParent().argslocalhex=args[1]if(hex)thenlocalp=tonumber(args.precision)or0localrgb=hexToRgb(hex)localhsl=rgbToHsl(rgb.r,rgb.g,rgb.b)localfl=formatToPrecision(hsl.l,p)localfs,fhlocalfracZeros=getFractionalZeros(p)localfZero=0..fracZerosif(fl==fZeroorfl==100..fracZeros)thenfs=fZerofh=fZeroelsefs=formatToPrecision(hsl.s,p)if(fs==fZero)thenfh=fZeroelsefh=formatToPrecision(hsl.h,p)if(fh==360..fracZeros)thenfh=fZero-- handle rounding to 360endendendreturnfh.."°, "..fs.."%, "..fl.."%"elsereturn""endendfunctionp.hexToHsv(frame)localargs=frame.argsorframe:getParent().argslocalhex=args[1]if(hex)thenlocalp=tonumber(args.precision)or0localrgb=hexToRgb(hex)localhsv=rgbToHsv(rgb.r,rgb.g,rgb.b)localfv=formatToPrecision(hsv.v,p)localfs,fhlocalfracZeros=getFractionalZeros(p)localfZero=0..fracZerosif(fv==fZero)thenfh=fZerofs=fZeroelsefs=formatToPrecision(hsv.s,p)if(fs==fZero)thenfh=fZeroelsefh=formatToPrecision(hsv.h,p)if(fh==360..fracZeros)thenfh=fZero-- handle rounding to 360endendendreturnfh.."°, "..fs.."%, "..fv.."%"elsereturn""endendfunctionp.hexToCielch(frame)localargs=frame.argsorframe:getParent().argslocalhex=args[1]if(hex)thenlocalp=tonumber(args.precision)or0localrgb=hexToRgb(hex)localLCh=srgbToCielchuvD65o2deg(rgb.r,rgb.g,rgb.b)localfL=formatToPrecision(LCh.L,p)localfC,fhlocalfracZeros=getFractionalZeros(p)localfZero=0..fracZerosif(fL==fZeroorfL==100..fracZeros)thenfC=fZerofh=fZeroelsefC=formatToPrecision(LCh.C,p)if(fC==fZero)thenfh=fZeroelsefh=formatToPrecision(LCh.h,p)if(fh==360..fracZeros)thenfh=fZero-- handle rounding to 360endendendreturnfL..", "..fC..", "..fh.."°"elsereturn""endendfunctionp.hexMix(frame)localargs=frame.argsorframe:getParent().argslocalhex0=args[1]localhex1=args[2]if(isempty(hex0)orisempty(hex1))thenreturn""endlocalt=args[3]if(isempty(t))thent=0.5elset=tonumber(t)localmin=tonumber(args.min)or0localmax=tonumber(args.max)or100if(min>=max)thenerror("Minimum proportion greater than or equal to maximum")elseif(t<min)thent=0elseif(t>max)thent=1elset=(t-min)/(max-min)endendlocalrgb0=hexToRgb(hex0)localrgb1=hexToRgb(hex1)localrgb=srgbMix(t,rgb0.r,rgb0.g,rgb0.b,rgb1.r,rgb1.g,rgb1.b)returnrgbToHex(rgb.r,rgb.g,rgb.b)endreturnp