--DawnBringer function library v1.14 (some drawing replaced) --** THIS IS NOT A RUNNABLE SCRIPT! ** --by Richard Fhager -- http://hem.fyristorg.com/dawnbringer/ -- Email: dawnbringer@hem.utfors.se -- MSN: annassar@hotmail.com -- -- Many functions in here was adopted from Evalion, a Javascript codecrafting/imageprocessing project -- http://goto.glocalnet.net/richard_fhager/evalion/evalion.html -- -- -- You may access these functions in your own scripts by loading this library, -- just add the follwing line as one of the first instructions: -- -- run("dawnbringer_lib") -- -- or -- -- run("../libs/dawnbringer_lib.lua") -- -- -- Note that the functions must be called with the full library object-name, "db.function_name..." -- -- Global library object db = {} -- ************************************* -- *** Text & Conversions *** -- ************************************* -- -- function db.rgb2HEX(r,g,b,prefix) local c,n,s,t,z c = {r,g,b} z = {"0",""} t = "" for n = 1, 3, 1 do s = string.upper(string.format("%x",c[n])) t = t..z[#s]..s --s = tonumber(c[n],16) --t = t..s end return prefix..t end -- -- ... eof Text & Conversions ... -- -- ************************************* -- *** Custom Math Functions *** -- ************************************* -- -- function db.sign(v) local s s = 0 if v > 0 then s = 1; end if v < 0 then s = -1; end return s end -- function db.distance(ax,ay,bx,by) return math.sqrt((ax-bx)^2 + (ay-by)^2); end -- function db.rotation (rot_ang,hub_x,hub_y,x,y) -- Rotate coordinates x & y relative hub local new_ang,dist,m,xd,yd,v; m = math xd=hub_x-x; yd=hub_y-y; if (not(xd==0 and yd==0)) then v = -90; if xd < 0 then v = 90; end new_ang = m.atan(yd/xd) - (v+rot_ang) * m.pi/180; dist = m.sqrt(xd*xd+yd*yd); x = hub_x - m.sin(new_ang)*dist; y = hub_y + m.cos(new_ang)*dist; end return math.floor(x),math.floor(y) -- For drawing purposes end -- -- function db.recursiveSum(div,rlev) -- divisons per recursion,recursion levels local s,i,m s,m = 1,1 for i = 1, rlev, 1 do m = m*div s = s + m end return s end -- -- -- ... eof Custom Math Functions ... -- -- ************************************* -- *** Fractional Scenery *** -- ************************************* -- function db.setSceneryPalette() db.colorCigarr(10,28,true) -- 250 colors setcolor(250, 208,48,48) setcolor(251, 48,208,48) setcolor(252, 48,48,208) setcolor(253, 224,224,64) setcolor(254, 224,64,224) setcolor(255, 64,224,224) end -- -- function db.star(xf,yf,sx,sy,rgb,haz,out,lum) local n,c,dist; c={} dist = haz + out * math.sqrt((xf-sx)^2+(yf-sy)^2); for n = 1, 3, 1 do c[n] = (rgb[n] * lum) / dist; end return c; end -- -- function db.zoom(xf,yf,zoom,panx,pany) -- Zoom and Pan in a fractional coord-system xf = (xf-0.5)/zoom + 0.5 + panx; yf = (yf-0.5)/zoom + 0.5 + pany; return xf,yf end -- -- function db.rotationFrac(rot_ang,hub_x,hub_y,x,y) -- Rotate coordinates x & y relative hub local new_ang,dist,m,xd,yd,v; m = math xd=hub_x-x; yd=hub_y-y; if (not(xd==0 and yd==0)) then v = -90; if xd < 0 then v = 90; end new_ang = m.atan(yd/xd) - (v+rot_ang) * m.pi/180; dist = m.sqrt(xd*xd+yd*yd); x = hub_x - m.sin(new_ang)*dist; y = hub_y + m.cos(new_ang)*dist; end return x,y end -- -- function db.twirl(x,y,arms,trot,tpow,tang) local b,ang,vx,vy,vr,m,deg,tw m=math; deg=math.pi/180; tw=.5; if (not(x==.5 and y==.5)) then ang = m.atan((.5-y)/(.5-x)); b = 0; if (x>.5) then b = m.pi; end vx = .5-x; vy = .5-y; vr = m.pow(m.sqrt(vx*vx+vy*vy),tpow); tw = .5+m.sin(-tang*deg+vr*trot+(ang + b)*arms)*.5; end return tw; end -- --- Alpha filters -- function db.alpha1(x,y,amp) -- Coord, Amplify: 0..n local p,a,xh,yh,m xh=0.5-x; yh=0.5-y; m = math p = m.pow(xh*xh+yh*yh,0.7); a = m.cos(32*m.pi*p)*m.sin(8*m.pi*(xh+yh)); return 1 + (a * amp) end -- -- -- ... eof Fractional Scenery ... -- -- ************************************* -- *** Custom Array Functions *** -- ************************************* -- -- Ok, I don't know Lua that well (still unsure about some scopes & refs etc.) -- And some features may not be active in Grafx2. So, some of the follwing functions -- may exist in Lua/Grafx2...but since I'm not sure if and how they work - I'll prefer -- to add a set of my own of known performance. -- function db.newArrayInit(xs,val) local x,ary; ary = {} for x = 1, xs, 1 do ary[x] = val end return ary end -- -- function db.newArrayInit2Dim(xs,ys,val) local x,y,ary; ary = {} for y = 1, ys, 1 do ary[y] = {} for x = 1, xs, 1 do ary[y][x] = val end end return ary end -- -- -- Merge two arrays into a NEW one: array_c = db.newArrayMerge(array_b,array_b) -- function db.newArrayMerge(a,b) local n,ary; ary = {} for n = 1, #a, 1 do ary[n] = a[n] end for n = 1, #b, 1 do ary[n+#a] = b[n] end return ary end -- -- -- Generate a copy of an array with a new value added Last -- function db.newArrayInsertLast(a,val) local n,ary; ary = {} for n = 1, #a, 1 do ary[n] = a[n] end ary[#a+1] = val return ary end -- -- -- Generate a copy of an array with a new value added First -- function db.newArrayInsertFirst(a,val) local n,ary; ary = {} ary[1] = val for n = 2, #a+1, 1 do ary[n] = a[n-1] end return ary end -- -- function db.ary2txt(ary) -- One & two dimensions supported [a,b] -> "a,b". [[a,b],[c,d]] -> "a-b, c-d" local t,n,m,v t = "" for n = 1, #ary, 1 do if type(ary[n]) == "table" then t = t..ary[n][1] for m = 2, #ary[n], 1 do t = t.."-"..ary[n][m] end else t = t..ary[n]; end t = t..", " end return t end -- -- -- *** Array data manipulation *** -- -- -- InsertionSort Array, this is chaos...I'm confused and stomped...don't understand how Lua works... -- ...sorting seem be to ok but this code is ugly... -- Sort LO-HI -- -- Screwed up or confused thing here I think, perhaps lo-hi/hi-lo. This is working lo-hi but the code -- looks like hi-lo...edit this some day -- function db.sorti(d,idx) local a,j,tmp,l,e l = #d for a=2, l, 1 do tmp = d[a]; e = a for j=a, 2, -1 do e = j if d[j-1][idx] > tmp[idx] then d[j] = d[j-1]; e = j-1; else break; end; end; d[e] = tmp; -- WHY THE F**K CAN'T YOU READ j HERE!?! STUPID ASSUCKING LANGUAGE end; --return d end -- -- function db.shuffle(list) local i,n,t for n = #list, 2, -1 do i = 1+math.floor(math.random() * n) t = list[n]; list[n] = list[i]; list[i] = t end end -- -- -- ... eof Custom Array Functions ... -- -- ************************************* -- *** Misc. Logical Operations *** -- ************************************* -- -- palList [r,g,b,palindex] is expected only to contain unique colors -- index = -1 --> index of list -- function db.makeIndexList(list,index) local n,ilist ilist = {} for n = 1, #list, 1 do if (index > 0) then ilist[n] = list[n][index]; end if (index == -1) then ilist[n] = n; end end return ilist end -- -- -- Return a list of all possible (non-same) pairs from the entries in a list -- [a,b,c] --> [[a,b],[a,c],[b,c]] -- (All entries are treated as unique. i.e it's only the INDEX that counts) -- mode = 0: Only unique pairs (m = (n^2 - n)/2), [a,b] --> [[a,b]] -- mode = 1: All pairs, i.e mirror versions as well. (m = n^2 - n), [a,b] --> [[a,b], [b,a]] -- function db.pairsFromList(list,mode) local a,b,l,n,pairs pairs = {} l = #list n = 1 for a = 1, l, 1 do for b = a+1, l, 1 do pairs[n] = {list[a],list[b]}; n = n + 1 if mode == 1 then pairs[n] = {list[b],list[a]}; n = n + 1; end end end return pairs end -- function db.valueInArray(ary,val) local n,res res = false for n = 1, #ary, 1 do if ary[n] == val then res = true; break; end end return res end -- RAMP specific -- Remove initial pair (palList) colors from pallist function db.initiateRamp(pair,pallist,pal_index) local n,found,plist plist = {} found = 1 for n = 1, #pallist, 1 do if db.valueInArray(pair,pallist[n]) == false then plist[found] = pallist[n]; found = found + 1; end end pair[pal_index] = plist -- ex: ["pal"] return pair -- Is now a 2 color RAMP end -- -- Remove new col entry from ramp's pallist and add it to the ramp, returns an updated ramp -- RampList = [1,2] ["pal"] = palList = [3,4,5], addindex = 3 -- --> [1,2,3] palList = [4,5] function db.updateRamp(ramp,addindex,pal_index) local n,found,pallist,plist,newramp plist = {} pallist = ramp[pal_index] -- New palList without added color to IndexList found = 1 for n = 1, #pallist, 1 do if pallist[n] ~= addindex then plist[found] = pallist[n]; found = found + 1; end end newramp = db.newArrayInsertLast(ramplist,addindex) newramp[pal_index] = plist return rlist end -- -- Returns a list of all inital ramps from color pairs -- -- Weeds out bad pairs, attaches remaining palette colors and the first rgb-vector -- -- function db.initiateRampList(pairs,pallist,pal_index,vec_index,min,maxmult,rw,gw,bw) local n,ramplist,newpairs,accept,dist,c1,c2,max,rD,gD,bD ramplist = {} max = min + (142 / math.sqrt(#pallist)) * maxmult -- min ex: 8-12 accept = 0 for n = 1, #pairs, 1 do c1 = pallist[pairs[n][1]] c2 = pallist[pairs[n][2]] rD = c2[1] - c1[1] gD = c2[2] - c1[2] bD = c2[3] - c1[3] dist = math.sqrt( (rw*rD)^2 + (gw*gD)^2 + (bw*bD)^2 ) if dist >= min and dist <= max then accept = accept + 1; ramplist[accept] = db.initiateRamp(pairs[n],pallist,pal_index); ramplist[accept][vec_index] = {rD, gD, bD, dist}; -- Add first color vector, ONLY KEEP DISTANCE? end end return ramplist end function db.findRampExpansionColors(ramp) local clist clist = {} -- Calculate vectors here? return clist end function db.findRAMPS(min_len, max_len) local i,n,c,pallist,ramp,ramplist,pairs,spairs,palindex,vecindex,found,donelist,newlist,dones local colorlist palindex = "pal" vecindex = "vector" pallist = db.fixPalette(db.makePalList(256), 0) pairs = db.pairsFromList(db.makeIndexList(pallist,-1), 0) ramplist = db.initiateRampList(pairs,pallist,palindex,vecindex, 8,0.75, 0.26,0.55,0.19) -- MIN_LEN = 5 -- MAX_LEN = 10 -- Split Ramp-build into two parts: -- 1. Build ramps >= MIN_LEN, NONE added to 'Done' -- 2. Run til no more ramps can be expanded or reaches MAX_LEN, ALL ramps added to 'Done' for i = 1, (min_len - 2), 1 do -- Assuming 2 for inital pairs (2 color ramps) newlist = {} found = 0 for n = 1, #ramplist, 1 do ramp = ramplist[n] colorlist = db.findRampExpansionColors(ramp) -- Colors that can split the current ramp into new expanded ramps for c = 1, #colorlist, 1 do found = found + 1; newlist[found] = db.updateRamp(ramp,colorlist[c],palindex); -- Ramp is expanded by 1 color end end ramplist = newlist end donelist = {}; dones = 0 repeat newlist = {} found = 0 for n = 1, #ramplist, 1 do ramp = ramplist[n] if true == false then found = found + 1; newlist[found] = db.updateRamp(ramp,color,palindex); else dones = dones + 1; donelist[dones] = ramp; end end --ramplist = newlist until found == 0 return #pairs.." - "..#ramplist end -- -- ... eof Misc. Logical Operations ... -- -- *************************************** -- *** General RGB-Color Modifications *** -- *************************************** -- function db.makeComplementaryColor(r,g,b,brikeeplev) -- Lev: 0 = Normal, 1 = Loose, 2 = Strict local bri_o,bri_n,bdiff function cap(v) return math.max(0,math.min(v,255)); end bri_o = db.getBrightness(r,g,b) r,g,b = db.shiftHUE(r,g,b,180) if brikeeplev > 0 then for n = 0, brikeeplev*3-1, 1 do -- Must iterate to reduce brightness error bri_n = db.getBrightness(r,g,b) bdiff = (bri_o - bri_n) / 2 * brikeeplev r = cap(r + bdiff) g = cap(g + bdiff) b = cap(b + bdiff) end end return r,g,b end -- -- *** Color balance *** -- -- bri_flag: Preserve brightness -- loose_flag: Loose preservation restrictions for brightness and balance -- -- Jeez, was this a tricky sucker; color-balance is just adding and capping... -- but trying color-balance with preserved perceptual brightness is a different monster... -- ...so bad I could only solve it by iterative error correction. -- function db.ColorBalance(r,g,b,rd,gd,bd,bri_flag,loose_flag) -- preserve brightness local rw,gw,bw,ri,gi,bi,itot,rni,gni,bni,ro,go,bo,ovscale,lev,count,rt,gt,bt,rf,gf,bf,bri -- Dawn 3.0, [0.26,0.55,0.19], 0-255 bri-colorscale adjust = 1.56905 rw,gw,bw = 0.26, 0.55, 0.19 function cap(v) return math.min(255,math.max(v,0)); end bri = db.getBrightness(r,g,b) -- Loose brightness & balance preservation, a good compromise. if bri_flag == true and loose_flag == true then lev = (rd + gd + bd) / 3 rd = rd - lev gd = gd - lev bd = bd - lev brin = db.getBrightness(cap(r+rd),cap(g+gd),cap(b+bd)) itot = brin - bri rd = rd - itot gd = gd - itot bd = bd - itot end if bri_flag == true and loose_flag == false then itot = 255 count = 0 -- Normalize (Yup, it's right only to normalize once first..cont.norm. will have some counter-effect) lev = (rd + gd + bd) / 3 rd = rd - lev gd = gd - lev bd = bd - lev repeat --messagebox("Norm:"..rd..", "..gd..", "..bd) -- Calculate total brightness change -- Note: Perceptual Brightness is exponential, and can't be delta-adjusted for anything other than greyscales. -- Although the formula for the new brightness corrected normalization level can can be derived... -- ...it doesn't do much good since the bigger problem is overflow outside the 0-255 boundary. -- As for now, I see no other means to solve this issue than with iterative error-correction. rt = r+rd gt = g+gd bt = b+bd itot = 9e99 rni = rd gni = gd bni = bd -- We can get brightness of negative values etc. So bri-correction is put on hold until values are scaled down if (rt>=0 and gt>=0 and bt>=0) and (rt<256 and gt<256 and bt<256) then brin = db.getBrightness(rt,gt,bt) itot = brin - bri --messagebox("Bri Diff: "..itot) -- Brightness adjusted balance rni = rd - itot gni = gd - itot bni = bd - itot end --messagebox("Bri Adj Bal:"..rni..", "..gni..", "..bni) -- Apply balance to find overflow (as fraction of the channel change) ro = math.max( math.max((r + rni)-255,0), math.abs(math.min((r + rni),0)) ) / math.max(math.abs(rni),1) go = math.max( math.max((g + gni)-255,0), math.abs(math.min((g + gni),0)) ) / math.max(math.abs(gni),1) bo = math.max( math.max((b + bni)-255,0), math.abs(math.min((b + bni),0)) ) / math.max(math.abs(bni),1) ovscale = 1 - math.max(ro,go,bo) -- Scaling balances might be logically incorrect (as they can be seen as constant differences) -- But scaling DOWN is quite harmless and I don't see how it could be done otherwise... -- ex: +10 red, +5 blue: Scale x2 = +20 red, +10 blue -> More red over blue than ordered, a contrast behaviour. -- +10 red, +5 blue: Scale x0.5 = +5 red, +2.5 blue -> Less of everything, but a part of the order. Harmless? -- rd = rni * ovscale gd = gni * ovscale bd = bni * ovscale count = count + 1 --messagebox("Final bal:"..rd..", "..gd..", "..bd) until math.abs(itot) < 1 or count > 5 end rf = r + rd gf = g + gd bf = b + bd --messagebox("Result color:"..rf..", "..gf..", "..bf) return rf,gf,bf end -- -- -- bri_flag: Preserve brightness -- cap_flag: Cap new color at 0-255, has a desaturating effect for large values. -- function db.ColorBalanceXXX(r,g,b,rd,gd,bd,bri_flag,cap_flag) -- preserve brightness local rf,gf,bf if cap_flag == true then rd = math.min(255,math.max(0, r+rd)) - r gd = math.min(255,math.max(0, g+gd)) - g bd = math.min(255,math.max(0, b+bd)) - b end local rw,gw,bw,ri,gi,bi,itot,rni,gni,bni,ro,go,bo,ovscale -- Dawn 3.0, [0.26,0.55,0.19], 0-255 bri-colorscale adjust = 1.56905 rw,gw,bw = 0.26, 0.55, 0.19 if bri_flag == true then -- Calculate total brightness change --ri = rd * rw --gi = gd * gw --bi = bd * bw --itot = math.sqrt(ri^2 + gi^2 + bi^2) bri = db.getBrightness(r,g,b) brin = db.getBrightness(r+rd,g+gd,b+bd) itot = brin - bri -- Normalized and Brightness adjusted balance rni = rd - itot gni = gd - itot bni = bd - itot -- Apply balance to find overflow (as fraction of the channel change) ro = math.max( math.max((r + rni)-255,0), math.abs(math.min((r + rni),0)) ) / math.max(math.abs(rni),1) go = math.max( math.max((g + gni)-255,0), math.abs(math.min((g + gni),0)) ) / math.max(math.abs(gni),1) bo = math.max( math.max((b + bni)-255,0), math.abs(math.min((b + bni),0)) ) / math.max(math.abs(bni),1) ovscale = 1 - math.max(ro,go,bo) rd = rni * ovscale gd = gni * ovscale bd = bni * ovscale end rf = r + rd gf = g + gd bf = b + bd return rf,gf,bf end -- -- function db.getContrast(ch) -- Channel, returns fraction -1..0..1, negative for ch < 127.5 --return math.abs((ch / 127.5) - 1) return (ch / 127.5) - 1 end -- -- function db.getAvgContrast(r,g,b) return (math.abs(db.getContrast(r)) + math.abs(db.getContrast(g)) + math.abs(db.getContrast(b))) / 3 end -- -- -- Mode = 0: Proportional - all colors reach max contrast at 100% -- -- Mode = 1: Linear - percentage simply added -- function db.changeContrastOLD(r,g,b,prc,mode) local m,rd,gd,bd,rv,gv,bv,rc,gc,bc,base,sign base = 1; sign = 1 if prc < 0 then base = 0; sign = -1; end -- decontrast m = prc / 100 * sign -- mode 0 rc = db.getContrast(r) rd = (base - math.abs(rc)) * m * db.sign(rc) rv = (rc+rd+1) * 127.5 gc = db.getContrast(g) gd = (base - math.abs(gc)) * m * db.sign(gc) gv = (gc+gd+1) * 127.5 bc = db.getContrast(b) bd = (base - math.abs(bc)) * m * db.sign(bc) bv = (bc+bd+1) * 127.5 return rv,gv,bv end -- function db.changeContrast(r,g,b,prc) -- Photoshop style local m,rd,gd,bd,rv,gv,bv,rc,gc,bc m = 1 + math.pow((255 / 100 * prc),3) / (255*255) -- decontrast if prc < 0 then m = 1 - math.abs(prc)/100 end rc = db.getContrast(r) rd = rc * m rv = (rd+1) * 127.5 gc = db.getContrast(g) gd = gc * m gv = (gd+1) * 127.5 bc = db.getContrast(b) bd = bc * m bv = (bd+1) * 127.5 return rv,gv,bv end -- function db.getBrightness(r,g,b) -- 0-255 local bri --bri = (r+g+b)/3 --bri = r*0.3 + g*0.59 + b*0.11 -- Luma Y'601 --bri = math.sqrt((r*0.3)^2 + (g*0.59)^2 + (b*0.11)^2) -- Luma Y'601 --bri = r*0.245 + g*0.575 + b*0.18 -- Dawn 2.0 bri = math.sqrt((r*0.26)^2 + (g*0.55)^2 + (b*0.19)^2) * 1.56905 -- Dawn 3.0 return bri end -- -- -- Note on desaturation: These functions are all junk, the only way to desaturate -- is to fade a color into it's corresponding greyscale. -- -- function db.desaturate(percent,r,g,b) -- V1.0 by Richard Fhager local a,p p = percent / 100 a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 * p r = r + (a-r*p) -- a+r*(1-p) g = g + (a-g*p) b = b + (a-b*p) return r,g,b end -- -- function db.desaturateA(percent,c) -- array version local r,g,b,a r = c[1] g = c[2] b = c[3] p = percent / 100 a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 * p r = r + (a-r*p) g = g + (a-g*p) b = b + (a-b*p) return {r,g,b} end -- -- function db.desatAVG(desat,c) -- Desaturation, simpe average r = c[1] g = c[2] b = c[3] p = desat / 100 a = (r+g+b)/3 r = r + p*(a-r) g = g + p*(a-g) b = b + p*(a-b) return {r,g,b} end -- -- function db.getSaturation(r,g,b) -- HSL local M,m,c,s,l M = math.max(r,g,b) m = math.min(r,g,b) c = (M - m)/255 s = 0 if c ~= 0 then --l = (0.3*r + 0.59*g + 0.11*b)/255 -- HSLuma: Y'601 l = (M+m)/510 -- This produces a quite "correct looking" divison of saturation if l <= 0.5 then s = c / (2*l); end if l > 0.5 then s = c / (2-2*l); end end return math.min(255,s * 255) end -- -- function db.getTrueSaturationX(r,g,b) -- Distance from grayscale axis. Not HSV/HSL local sat,bri bri = (r+g+b) / 3 sat = math.min(255, math.sqrt((r-bri)^2 + (g-bri)^2 + (b-bri)^2) * 1.224744875) return sat end -- -- WIP. Trying to find a more natural model for estimating Saturation -- Current: (HSL + True) / 2 function db.getAppSaturation(r,g,b) return math.min(255, (db.getSaturation(r,g,b) + db.getTrueSaturationX(r,g,b)) / 2) end -- -- function db.saturate(percent,r,g,b) local a,m,p,mc a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 m = math.min(255-math.max(r,g,b), math.min(r,g,b)) p = percent * (m / 100) mc = math.max((r-a),(g-a),(b-a)) -- Can this be derived elsewhere? if mc ~= 0 then r = r + (r-a) * p / mc g = g + (g-a) * p / mc b = b + (b-a) * p / mc end return r,g,b end -- -- -- Super Saturate: Better than Photoshop etc. -- -- Higher than 100% power is ok -- function db.saturateAdv(percent,r,g,b,brikeeplev,greydamp) -- brikeep = 0 - 2 local a,m,p,mc,bri_o,bri_n,bdiff,mx,mi,adj,q,n function cap(v) return math.max(0,math.min(v,255)); end mx = math.max(r,g,b) mi = math.min(r,g,b) bri_o = db.getBrightness(r,g,b) a = (math.min(mx,255) + math.max(mi,0)) * 0.5 m = math.min(255-mx, mi) p = percent * (m / 100) mc = math.max((r-a),(g-a),(b-a)) -- Can this be derived elsewhere? if mc ~= 0 and m ~= 0 then adj = math.min(1,(mx - mi) / m) -- Reduce effect on low saturation if greydamp == false then adj = 1; end q = p / mc * adj r = cap( r + (r-a) * q ) g = cap( g + (g-a) * q ) b = cap( b + (b-a) * q ) end for n = 0, brikeeplev*2, 1 do -- Must iterate to reduce brightness error bri_n = db.getBrightness(r,g,b) bdiff = (bri_o - bri_n) / 2 * brikeeplev r = cap(r + bdiff) g = cap(g + bdiff) b = cap(b + bdiff) end return r,g,b end -- -- -- Lightness: Darken / Brighten color (Argument and returnvalue is a rgb-list) -- Rate of change is inversely proportional to the distance of the max/min. -- i.e. all colors/channels will reach max/min at the same time (at 0 or 100 %) -- (As opposed to 'Brightness' where all channels are changed by a constant value) -- function db.lightness(percent,c) local v,r,g,b,p r = c[1] g = c[2] b = c[3] p = math.abs(percent/100) v = 255 if percent < 0 then v = 0; end r = r + (v - r)*p g = g + (v - g)*p b = b + (v - b)*p return {r,g,b} end -- -- function db.changeLightness(r,g,b,percent) local v v = db.lightness(percent,{r,g,b}) return v[1],v[2],v[3] end -- -- function db.getLightness(r,g,b) -- HSL bi-hexcone return (math.max(r,g,b) + math.min(r,g,b)) / 2 end -- -- function db.shiftHUE(r,g,b,deg) -- V1.3 R.Fhager 2007, (Heavily derived code, hehe...) local c,h,mi,mx,d,s,p,i,f,q,t c = {g,b,r} mi = math.min(r,g,b) mx = math.max(r,g,b); v = mx; d = mx - mi; s = 0; if mx ~= 0 then s = d/mx; end p = 1; if g ~= mx then p = 2; if b ~= mx then p = 0; end; end if s~=0 then h=(deg/60+(6+p*2+(c[1+p]-c[1+(p+1)%3])/d))%6; i=math.floor(h); f=h-i; p=v*(1-s); q=v*(1-s*f); t=v*(1-s*(1-f)); c={v,q,p,p,t,v} r = c[1+i] g = c[1+(i+4)%6] b = c[1+(i+2)%6] end return r,g,b end -- -- function db.getHUE(r,g,b,greytol) -- 0-6 (6.5 = Greyscale), mult. with 60 for degrees -- 1 Color diff is roughly detected by Tolerance = 0.0078125 (Tol. incr. with lightness etc.) local c,h,mi,mx,d,s,p,i,f,q,t c = {g,b,r} mi = math.min(r,g,b) mx = math.max(r,g,b); v = mx; d = mx - mi; s = 0; if mx ~= 0 then s = d/mx; end p = 1; if g ~= mx then p = 2; if b ~= mx then p = 0; end; end h = 6.5 -- for custom graphical purposes if s>greytol then -- can't use >= h=(6+p*2+(c[1+p]-c[1+(p+1)%3])/d)%6; end return h end -- -- -- ... eof RGB color modifications ... -- -- **************************************** -- *** Custom Color / Palette functions *** -- **************************************** --# of Unique colors in palette: --#db.fixPalette(db.makePalList(256)) --# of Colors in Image: --#db.makePalListFromHistogram(db.makeHistogram()) --# of Unique colors in Image: --#db.fixPalette(db.makePalListFromHistogram(db.makeHistogram())) -- function db.rgbcap(r,g,b,mx,mi) local m = math return m.max(mi,m.min(r,mx)), m.max(mi,m.min(g,mx)), m.max(mi,m.min(b,mx)) end -- -- function db.rgbcapInt(r,g,b,mx,mi) local m = math return m.floor(m.max(mi,m.min(r,mx))), m.floor(m.max(mi,m.min(g,mx))), m.floor(m.max(mi,m.min(b,mx))) end -- -- function db.makePalList(cols) local pal,n,r,g,b pal = {} for n = 0, cols-1, 1 do r,g,b = getcolor(n) pal[n+1] = {r,g,b,n} end return pal end -- -- function db.makeSparePalList(cols) local pal,n,r,g,b pal = {} for n = 0, cols-1, 1 do r,g,b = getsparecolor(n) pal[n+1] = {r,g,b,n} end return pal end -- -- -- Use to remove the black colors (marks unused colors) from palette-list -- if it's known that no black color exists in the image. function db.stripBlackFromPalList(pallist) local i,u,c,dummy; i = 257 -- Do 'nothing' If using a full 256 col palette with no blacks for u = 1, #pallist, 1 do c = pallist[u] if (c[1]+c[2]+c[3]) == 0 then i = u; end end dummy = table.remove(pallist,i) return pallist end -- -- function db.stripIndexFromPalList(pallist,colindex) local i,u,c,dummy for u = 1, #pallist, 1 do c = pallist[u] if c[4] == colindex then i = u; end end dummy = table.remove(pallist,i) return pallist end -- -- function db.addHSBtoPalette(pallist) local n,hue,sat,rgb for n=1, #pallist, 1 do rgb = pallist[n] pallist[n][5] = db.getHUE(rgb[1],rgb[2],rgb[3],0) pallist[n][6] = db.getSaturation(rgb[1],rgb[2],rgb[3]) pallist[n][7] = db.getBrightness(rgb[1],rgb[2],rgb[3]) end return pallist -- {r,g,b,n,bri,hue,sat} end -- -- function db.makePalListRange(start,ends) local pal,n,r,g,b,a pal = {} a = 1 for n = start, ends, 1 do r,g,b = getcolor(n) pal[a] = {r,g,b,n}; a = a + 1; end return pal end -- -- function db.makePalListShade(cols,sha) -- Convert colors to less bits, colorcube operations etc. local pal,n,r,g,b,mf,div mf = math.floor div = 256 / sha pal = {} for n = 0, cols-1, 1 do r,g,b = getcolor(n) pal[n+1] = {mf(r/div),mf(g/div),mf(b/div),n} end return pal end -- -- function db.makePalListShadeSPARE(cols,sha) -- Convert colors to less bits, colorcube operations etc. local pal,n,r,g,b,mf,div mf = math.floor div = 256 / sha pal = {} for n = 0, cols-1, 1 do r,g,b = getsparecolor(n) pal[n+1] = {mf(r/div),mf(g/div),mf(b/div),n} end return pal end -- -- function db.getColorDistance_weight(r1,g1,b1,r2,g2,b2,rw,gw,bw) return math.sqrt( (rw*(r1-r2))^2 + (gw*(g1-g2))^2 + (bw*(b1-b2))^2 ) end -- -- -- Since brightness is exponential, each channel may work as a "star" drowning the color -- of a lesser channel. This algorithm is an approximation to adjust distances for this phenomenon. -- Ex: Adding 32 red to black is visually obvious, but adding 64 red to full green is almost -- impossible to detect by the naked eye. -- -- However this isn't a complete solution so we may weigh in brightness as well... -- -- If cv = 0 (0..1) then prox acts as ordinary perceptual colordistance -- if bri = 1 (0..1) then distance is only that of brightness function db.getColorDistanceProx(r1,g1,b1,r2,g2,b2,rw,gw,bw,normalizer, cv,briweight) local rp1,gp1,bp1,rp2,gp2,bp2,v,m1,m2,prox,bdiff; v = 2*255*255 m1 = math.max(r1,g1,b1) m2 = math.max(r2,g2,b2) rp1 = 1 - math.sqrt((r1-m1)^2 / v) * cv gp1 = 1 - math.sqrt((g1-m1)^2 / v) * cv bp1 = 1 - math.sqrt((b1-m1)^2 / v) * cv rp2 = 1 - math.sqrt((r2-m2)^2 / v) * cv gp2 = 1 - math.sqrt((g2-m2)^2 / v) * cv bp2 = 1 - math.sqrt((b2-m2)^2 / v) * cv bdiff = math.abs(db.getBrightness(r1,g1,b1) - db.getBrightness(r2,g2,b2)) -- weights are hardcoded in function prox = math.sqrt( (rw*(r1*rp1-r2*rp2))^2 + (gw*(g1*gp1-g2*gp2))^2 + (bw*(b1*bp1-b2*bp2))^2 ) * normalizer return prox * (1-briweight) + bdiff * briweight end -- -- function db.getBestPalMatch(r,g,b,pal,index_flag) -- pal = [r,g,b,palindex], index_flag -> return palindex if pal is sorted or reduced local diff,best,bestcol,cols,n,c,p cols = #pal bestcol = -1 best = 9e99 for n=1, cols, 1 do p = pal[n] diff = db.getColorDistance_weight(r,g,b,p[1],p[2],p[3],0.26,0.55,0.19) * 1.569 if diff < best then bestcol = n; best = diff; end end if index_flag == true then bestcol = pal[bestcol][4] + 1 end return bestcol-1 -- palList index start at 1, image-palette at 0 end -- -- Normally this function will return the (image)palette index of best color -- ...but if the palette has been sorted with 'fixPalette' it will return the index -- of the custom palList, setting index_flag will convert this value to image-palette index -- -- HYBRID means the colormatch is a combo of color and (perceptual)brightness -- -- function db.getBestPalMatchHYBRID(rgb,pal,briweight,index_flag) -- Now correctly balanced local diff,diffC,diffB,best,bestcol,cols,n,c,r,g,b,p,obri,pbri cols = #pal bestcol = -1 best = 9e99 --messagebox(briweight) -- Note: Not secured against negative values (this algorithm is SLOW, we cannot afford it) r = rgb[1] g = rgb[2] b = rgb[3] obri = db.getBrightness(r,g,b) -- 0-255 for n=1, cols, 1 do p = pal[n] pbri = db.getBrightness(p[1],p[2],p[3]) diffB = math.abs(obri - pbri) -- we need to normalize the distance by the weights diffC = db.getColorDistance_weight(r,g,b,p[1],p[2],p[3],0.26,0.55,0.19) * 1.569 diff = briweight * (diffB - diffC) + diffC if diff < best then bestcol = n; best = diff; end end if index_flag == true then bestcol = pal[bestcol][4] + 1 -- Since we detract 1 on return, God Lua is stupid end return bestcol-1 -- palList index start at 1, image-palette at 0 end -- -- -- Special version of Hybrid-remapping for mixPalette list -- -- mixpal: {score,col#1,col#2,dist,rm,gm,bm, c1_r,c1_g,c1_b, c2_r,c2_g,c2_b} -- -- returns: {col#1,col#2} (index of palette) -- function db.getBestPalMatchHybridMIX(rgb,mixpal,briweight,mixreduction) local diff,diffC,diffB,best,bestcol,cols,n,c,r,g,b,p,obri,pbri, distmult cols = #mixpal bestcol = -1 best = 9e99 -- We will simply add the the distance to the mix with the distance between the mixcolors and -- employ a user tolerance to much the latter will matter. --distmult = 255 / 9.56 / 100 * mixreduction -- 16 shades distmult = 1.56902 / 100 * mixreduction -- 24-bit, Dawn3.0 colormodel -- Note: Not secured against negative values (this algorithm is SLOW, we cannot afford it) r = rgb[1] g = rgb[2] b = rgb[3] obri = db.getBrightness(r,g,b) -- 0-255 for n=1, cols, 1 do p = mixpal[n] --pbri = db.getBrightness(p[5],p[6],p[7]) -- *** DawnBringer's exponetial color brightness dither resolution phenomena theorem *** -- Bri = color value ^ 2 -- Two adjacent pixels displayed with "normal high resolution" will NOT have the perceptual -- brightness of the resulting mixcolor. The brightness lies closer to that of the brightest pixel. -- Bri[(C1+C2)/2] = SQRT( (C1bri^2 + C2bri^2) / 2 ) -- (Brightness according to Dawn-model: bri = SQRT( (r*.26)^2 + (g*.55)^2 + (b*.19)^2 ) ) pbri = math.sqrt((db.getBrightness(p[8],p[9],p[10])^2 + db.getBrightness(p[11],p[12],p[13])^2) / 2) diffB = math.abs(obri - pbri) -- we need to normalize the distance by the weights diffC = db.getColorDistance_weight(r,g,b,p[5],p[6],p[7],0.26,0.55,0.19) * 1.569 + p[4]*distmult diff = briweight * (diffB - diffC) + diffC if diff <= best then bestcol = n; best = diff; end end return {mixpal[bestcol][2], mixpal[bestcol][3]} --return {mixpal[bestcol][2], 0} end -- -- function db.matchcolorHSB(h,s,b,pallist,index_flag) -- -- why don't we just convert HSB-diagram to RGB and do normal colormatching? -- Not the same... -- local n,c,best,bestcol,pb,ph,ps,diff,huediff,huecorr,hue_adj,sat_adj,bri_adj bestcol = -1 best = 9e99 -- higher adjust means more impact (higher hue gives more interpolation ) hue_adj = 4 sat_adj = 0.075 bri_adj = 2 huecorr = 255 / 6 -- Our Hue goes from 0.0 - 5.999 for n=1, #pallist, 1 do c = pallist[n] ph = c[5] ps = c[6] pb = c[7] huediff = math.abs(h-ph*huecorr) if huediff > 127 then huediff = huediff - (huediff % 127) * 2; end --if ph == 6.5 then huediff = 0; end -- With less saturation, exact hue becomes less important and brightness more usefull -- This allows for greyscales and low saturation colors to work smoothly. huediff = huediff * (ps /255) diff = hue_adj*huediff^2 + (s-ps)^2 * sat_adj + (b-pb)^2 * bri_adj if diff <= best then bestcol = n; best = diff; end end if index_flag == true then bestcol = pallist[bestcol][4] + 1 -- Since we detract 1 on return, God Lua is stupid end return bestcol-1 end -- -- -- Used by PaletteAnalysis.lua, FindRamps(), MixColors() etc. -- Assigns is used by ApplySpare script -- function db.fixPalette(pal,sortflag) -- Arrange palette & only keep unique colors local n,l,rgb,i,unique,bri,hue,sat,ulist,indexpal,newpal,dtot ulist = {} indexpal = {} newpal = {} local doubles,assign doubles = {}; assign = {} l = #pal unique = 1 -- ok, see how stupid lua is dtot = 0 for n=1, l, 1 do rgb = pal[n]; -- actually rgbn i = 1 + rgb[1] * 65536 + rgb[2] * 256 + rgb[3]; bri = db.getBrightness(rgb[1],rgb[2],rgb[3]) if indexpal[i] == nil then indexpal[i] = rgb; ulist[unique] = {i,bri}; unique = unique+1; assign[rgb[4]+1] = rgb[4] -- really n, but can we be sure? else doubles[rgb[4]] = true; -- Mark as double (This is wrong; starts at 0...but col 0 cannot be a double so...) dtot = dtot + 1 assign[rgb[4]+1] = indexpal[i][4] -- Reassign removed color end end -- sort ulist if sortflag == 1 then db.sorti(ulist,2); end -- sort by brightness l = #ulist for n=1, l, 1 do newpal[n] = indexpal[ulist[n][1]] end newpal["assigns"] = assign -- Complete list of image color assigns (removed cols will point to 1st occurence) newpal["doubles"] = doubles newpal.double_total = dtot --messagebox("unique colors", unique-1) return newpal end -- -- function db.drawColorspace12bit(x,y,cols,size) local r,g,b,c,rows,row,col,s16,rx,ry,xx,yy s16 = size*16 rows = math.floor(16/cols) for g = 0, 15, 1 do col = g % cols row = math.floor(g / cols) for r = 0, 15, 1 do for b = 0, 15, 1 do c = matchcolor(r*17,g*17,b*17) xx = x+col*s16+r*size yy = y+row*s16+b*size for ry = 0, size-1, 1 do for rx = 0, size-1, 1 do putpicturepixel(xx+rx,yy+ry,c) end;end end end end end -- -- function db.drawHSBdiagram(pallist,posx,posy,width,height,size,sat) --db.addHSBtoPalette(palList) local x,y,c for y = 0, height-1, 1 do for x = 0, width-1, 1 do hue = 255/width * x bri = 255/height * y c = db.matchcolorHSB(hue,sat,bri,pallist,true) db.drawRectangle(posx + x*size, posy + y*size,size,size, c) end end end -- -- function db.polarHSBdiagram(ox,oy,radius,pol,brilev,huelev,saturation,dark2bright_flag) local pal,bstep,bstep2,hstep,hstep2,bri,hue,sx,sy,cx,cy,x,y,p1,p2,c pal = db.addHSBtoPalette(db.fixPalette(db.makePalList(256))) bstep = radius / (brilev + pol) bstep2 = bstep / 2 hstep = -360 / huelev hstep2 = hstep / 2 c = 255; if dark2bright_flag then c = 0; end drawdisk(ox,oy,math.ceil(pol*bstep),matchcolor(c,c,c)) for y=pol, brilev+pol-1,1 do bri = (brilev - y + pol) * (256 / brilev) if dark2bright_flag then bri = (brilev - (brilev - y + pol)) * (256 / brilev) end for x=0, huelev-1,1 do hue = x * (360 / huelev) * 255/360 c = db.matchcolorHSB(hue,saturation,bri,pal,true) sx = ox sy = oy - y*bstep cx,cy = db.rotationFrac(x*hstep,ox,oy,sx,sy) x1,y1 = db.rotation(x*hstep-hstep2,ox,oy,ox, sy-bstep2) x2,y2 = db.rotation(x*hstep+hstep2,ox,oy,ox, sy-bstep2) x3,y3 = db.rotation(x*hstep-hstep2,ox,oy,ox, sy+bstep2) x4,y4 = db.rotation(x*hstep+hstep2,ox,oy,ox, sy+bstep2) p1 = {{x1,y1},{x2,y2},{x3,y3}} p2 = {{x3,y3},{x4,y4},{x2,y2}} db.fillTriangle(p1,c,0,true,false) -- triangle, fillcol, linecol, fill, wire db.fillTriangle(p2,c,0,true,false) end updatescreen(); if (waitbreak(0)==1) then return; end end end -- polarHSB -- -- -- Histograms, remapping etc. -- -- function db.makeHistogram() local n,y,x,c,w,h,list; list = {} w, h = getpicturesize() for n = 1, 256, 1 do list[n] = 0; end for y = 0, h - 1, 1 do for x = 0, w - 1, 1 do c = getpicturepixel(x,y) list[c+1] = list[c+1] + 1 end end return list end -- -- function db.makeHistogramIndexed() -- With color index so it can be sorted etc. local n,y,x,c,w,h,r,g,b,list; list = {} w, h = getpicturesize() for n = 1, 256, 1 do r,g,b = getcolor(n-1) list[n] = {0,n-1,r,g,b}; end for y = 0, h - 1, 1 do for x = 0, w - 1, 1 do c = getpicturepixel(x,y) list[c+1][1] = list[c+1][1] + 1 end end return list end -- -- function db.makeSpareHistogram() local n,y,x,c,w,h,list; list = {} w, h = getsparepicturesize() --w,h = 512,360 for n = 1, 256, 1 do list[n] = 0; end for y = 0, h - 1, 1 do for x = 0, w - 1, 1 do c = getsparepicturepixel(x,y) list[c+1] = list[c+1] + 1 end end return list end -- -- -- Makes a palette-list from only the colors (histogram) that occurs in the image -- Assumes image/palette has not changed since histogram was created function db.makePalListFromHistogram(hist) local n,r,g,b,list,count list = {} count = 1 for n = 1, #hist, 1 do if hist[n] > 0 then r,g,b = getcolor(n-1) list[count] = {r,g,b,n-1} count = count + 1 end end return list end -- function db.makePalListFromSpareHistogram(hist) local n,r,g,b,list,count list = {} count = 1 for n = 1, #hist, 1 do if hist[n] > 0 then r,g,b = getsparecolor(n-1) list[count] = {r,g,b,n-1} count = count + 1 end end return list end -- -- function db.remap(org) -- Working with a remap-list there's no need of reading backuppixel --messagebox("Remapping") local x,y,c,i,w,h,s,f,col f = getpicturepixel s = false w, h = getpicturesize() for y = 0, h - 1, 1 do for x = 0, w - 1, 1 do c = f(x,y) i = org[c+1] if i == null then i = matchcolor(getbackupcolor(getbackuppixel(x,y))); s = true; col = c; end -- Find color for a removed double putpicturepixel(x,y,i) end end if s then messagebox("Remapping: Not all image colors were found in remap-list (re-assign), probably due to duplicate removal. Matchcolor was used, ex: col# "..col); end end -- -- -- Same as db.remap but no comments -- function db.remapImage(colassignlist) -- assignment list is optional local x,y,c,w,i,h,assign assign = false if colassignlist ~= null then assign = true; end w,h = getpicturesize() for y = 0, h-1, 1 do for x = 0, w-1, 1 do c = getbackuppixel(x,y) i = null; if assign then i = colassignlist[c+1]; end if not assign or i == null then i = matchcolor(getbackupcolor(c)) end putpicturepixel(x,y,i) end end end -- -- -- Palette DeCluster: Color-reduction by fusing similar colors into new ones, using a desired tolerance. -- This is a method similar to Median-Cut, but more surgical. -- -- pallist: Palette list {r,g,b,palette_index} -- hist: Histogram {color 0 pixels, color 1 pixels...etc} always a full 256 color list -- crad: Cluster radius treshold in % of distance between black & white -- A value of 0 will only remove identical colors -- A value of 3-4 will usally fuse redundant colors without causing notice -- prot_pow: (0..10) Protect common colors in histogram. Distances are increased by ocurrence. -- Also gives protection to fused colors even if not using histogram (combined nominal weights) -- pixels: Pixels in image (so protection can be calculated) -- rw,gw,bw: Color weights (rw+gw+bw = 1, 0.33,0.33,0.33 is nominal) -- -- Returns: -- a new (c)palette list {r,g,b,{original palette_indices},fused flag, histogram_weight} -- a remap list (org) [image color + 1] = remap color (in the new palette) function db.deCluster(pallist, hist, crad, prot_pow, pixels, rw,gw,bw) --messagebox(pixels) local u,c,a,i,o,j,n,c1,c2,r,g,b,r1,g1,b1,r2,g2,b2,wt,rt,gt,bt,tot,pd local worst,wtot,maxdist,maxDist,distfrac,clusterExists,clustVal,count,crad1 local cList,cPalList,clusterList,fuseCol,orgcols,newPalList,org maxdist = math.sqrt(rw*rw*65025 + gw*gw*65025 + bw*bw*65025) distfrac = 100 / maxdist -- Let's just make a slightly more suitable format of the pallist (List for original color(s)) cPalList = {} for u = 1, #pallist, 1 do c = pallist[u] cPalList[u] = {c[1],c[2],c[3],{c[4]},false,hist[c[4]+1]} -- r,g,b,{original colors},fuse_marker,histogram_weight end --table.insert(cPalList,{255,255,0,{257},false,1}) clusterExists = true while clusterExists do clusterExists = false clusterList = {} crad1 = crad + 1 -- avoid divison by zero worst = 9999 for a = 1, #cPalList, 1 do c1 = cPalList[a] r1,g1,b1 = c1[1],c1[2],c1[3] wtot = c1[6] cList = {a} maxDist = 0 for b = 1, #cPalList, 1 do if (b ~= a) then c2 = cPalList[b] r2,g2,b2 = c2[1],c2[2],c2[3] wt = c2[6] pd = math.pow((1 + wt / pixels), prot_pow) -- Protection, increase distance dist = db.getColorDistance_weight(r1,g1,b1,r2,g2,b2,rw,gw,bw) * distfrac * pd if dist <= crad then wtot = wtot + wt table.insert(cList,b) maxDist = math.max(dist,maxDist) end end end -- b if #cList > 1 then clustVal = maxDist / (crad1 * #cList) * (wtot / #cList) if clustVal < worst then worst = clustVal clusterList = cList end end end -- a --t = db.ary2txt(clusterList) --messagebox("Worst cluster is "..t) -- Fuse if #clusterList > 1 then clusterExists = true -- Run another iteration and look for more clusters fuseCol = {0,0,0,{}} rt,gt,bt,tot = 0,0,0,0 for n = 1, #clusterList, 1 do i = clusterList[n] c = cPalList[i] --o = c[4][1] -- Original color (always #1 in list since fused colors can't re-fuse) o = c[4] -- Original color list --if c[5] == true then messagebox("Re-Fusing..."); end r,g,b = c[1],c[2],c[3] --wt = hist[o+1] -- Org. colors are 0-255 wt = c[6] rt = rt + r * wt gt = gt + g * wt bt = bt + b * wt tot = tot + wt cPalList[i] = -1 -- Erase color --table.insert(fuseCol[4],o) orgcols = fuseCol[4] for j = 1, #o, 1 do table.insert(orgcols,o[j]) end fuseCol[4] = orgcols end rt = rt / tot gt = gt / tot bt = bt / tot fuseCol[1] = rt fuseCol[2] = gt fuseCol[3] = bt fuseCol[5] = true -- fusecol marker fuseCol[6] = tot table.insert(cPalList,fuseCol) --messagebox(#clusterList.." Colors was fused, resulting in "..rt..", "..gt..", "..bt) newPalList = {} for n = 1, #cPalList, 1 do if cPalList[n] ~= -1 then table.insert(newPalList,cPalList[n]) --newPalList = db.newArrayInsertLast(newPalList,cPalList[n]) end end cPalList = newPalList --messagebox("Pal length: "..#cPalList) statusmessage("DC - Image colors: "..#cPalList.." "); waitbreak(0) end -- fuse end -- while -- Create remap-list org = {} count = 0 for u = 1, #cPalList, 1 do c = cPalList[u] for n = 1, #c[4], 1 do i = c[4][n] org[i+1] = count -- quick way to remap without matchcolor end count = count + 1 end return org,cPalList end; -- decluster -- ------------- MEDIAN CUT V1.0 ------------ -- -- 256 color Palette Lua-version (converted from Evalion JS-script) -- -- by Richard 'DawnBringer' Fhager -- -- -- pal: [[r,g,b,i]] Pallist -- cnum: Target number of colors in reduced palette -- (step:) 1.. Pixel picks for processing, 1 = all pixels in image, best and slowest. 2 = 25% of pixels -- qual: Flag Qualitative color selection (Normal mode) -- quant: Flag Quantative color/pixel selection (Histogram) 100% mean that it count as much as quality -- (One of or both qual/quant must be selected) -- rgbw: [3] RGB-weights []. Weigh the color channels. ex: [1,1.333,0.75] -- bits: 1-8 Bits used for each color channel in palette -- quantpow: 0..1 -- -- return: A palette! A list of [r,g,b] values -- -- NOTE: Quantity will act as a counterforce to altered colorspace (weights)... -- Ex: if Green is considered bigger, it will be split into more blocks -- but each blocks will have less colors and thus less quantity. -- -- Perceptual colorspace (rgb-weights) will generally produce the best quality of palettes, but if -- It's desirable to preserve stronger colors, esp. in small palettes, -- it can be good to just use nominal space: 1,1,1 -- -- Histogram may be useful to assign more colors to an object that already covers most of the image, -- however; if the object of interest is small in relation to a large (unimportant) background, it's -- usually best not to have any histogram at all. Histogram will dampen strong infrequent colors. function db.medianCut(pal,cnum,qual,quant,rgbw,bits,quantpow) local n,x,y,xs,ys,rgb,blocklist,blocks local len,res,chan,diff,maxdiff,maxblock,split local qualnorm, quantnorm -- Normalize 256 for quality/quantity relationship qualnorm = 1 / math.sqrt(rgbw[1]^2 + rgbw[2]^2 + rgbw[3]^2) quantnorm = 256 / #pal blocklist = {} blocklist[1] = {}; blocks = 1 for n=1, #pal, 1 do blocklist[1][n] = pal[n]; end analyzeBlock(blocklist[1],qual,quant,rgbw,qualnorm,quantnorm,quantpow) failsafe = 0 while (blocks < cnum and failsafe < 256) do failsafe = failsafe + 1 maxdiff = -1 maxblock = -1 for n=1, blocks, 1 do diff = blocklist[n].diff if (diff > maxdiff) then maxdiff = diff; maxblock = n; end -- maxchan is stored as .chan in block end split = splitBlock(blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow) --if (split == false){ alert("Only found " + blocks + " (24-bit) colors!"); break; } blocks = #blocklist --status.value = "MC: " +blocks end -- while return blocks2Palette(blocklist,bits) end -- -- function blocks2Palette(blocklist,bits) local n,r,g,b,c,pal,block,rgb,blen,M,dB,cB,rf,gf,bf M = math pal = {} --bits = 1 dB = M.pow(2,8-bits) cB = M.ceil(255 / (M.pow(2,bits) - 1)) for n=1, #blocklist, 1 do block = blocklist[n] r,g,b = 0,0,0 blen = #block for c=1, blen, 1 do rgb = block[c] r = r + rgb[1] g = g + rgb[2] b = b + rgb[3] end rf = M.floor(M.min(255,M.max(0,M.floor(r/blen))) / dB) * cB gf = M.floor(M.min(255,M.max(0,M.floor(g/blen))) / dB) * cB bf = M.floor(M.min(255,M.max(0,M.floor(b/blen))) / dB) * cB pal[n] = {rf, gf, bf, 0} -- col is avg. of all colors in block (index is set (to 0) for compatibility) end -- blocklist return pal end -- -- function analyzeBlock(block,qual,quant,rgbw,qualnorm,quantnorm,quantpow) local r,g,b,n,rmin,gmin,bmin,rmax,gmax,bmax,rdif,gdif,bdif,chan,d,median,diff local len,Mm,Mx,rgb,kv,qu Mx,Mm = math.max, math.min len = #block rmin,gmin,bmin = 255,255,255 rmax,gmax,bmax = 0,0,0 for n=1, len, 1 do rgb = block[n] r = rgb[1] * rgbw[1] g = rgb[2] * rgbw[2] b = rgb[3] * rgbw[3] --if (!isNaN(r) and !isNaN(g) and !isNaN(b)) then -- Ignore any erroneous data rmin = Mm(rmin,r) gmin = Mm(gmin,g) bmin = Mm(bmin,b) rmax = Mx(rmax,r) gmax = Mx(gmax,g) bmax = Mx(bmax,b) --end end rdif = (rmax - rmin) -- * rgbw[1] gdif = (gmax - gmin) -- * rgbw[2] bdif = (bmax - bmin) -- * rgbw[3] d = {{rmin,rdif,rmax},{gmin,gdif,gmax},{bmin,bdif,bmax}} chan = 1 -- Widest channel if (gdif > rdif) then chan = 2; end if (bdif > rdif and bdif > gdif) then chan = 3; end -- Ok, this is the average of the max/min value rather than an actual median -- I guess this will fill the colorspace more uniformly and perhaps select extremes to a greater extent? -- Which is better? --median = d[chan][1] + d[chan][2] / 2 -- OLD same as median with nominal weights median = (d[chan][1] + d[chan][3]) / 2 -- quantity and quality are normalized to 256 (256 is the total of colors in the set for quantity) -- Note that, regardless of forumla, quality (distance) must always be greater in any block than quantity (colors/pixels) -- Coz a block may contain many of only 1 unique color, thus rendering it impossible to split if selected. kv = 1 qu = 1 if (quant) then kv = 1 + len*quantnorm*quantpow; end if (qual) then qu = d[chan][2] * qualnorm; end diff = qu + qu*kv^2.5 block.chan = chan block.diff = diff block.median = median return {chan,diff,median,len} end -- function splitBlock(blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow) local n,cmax,median,blockA,blockB,len,cB,block,rgb,res blockA,blockB = {},{} block = blocklist[maxblock] res = true chan = block.chan median = block.median cB = blocklist[maxblock] -- maxblock starts at 1 when called so it should not hava a +1 len = #cB for n=1, len, 1 do rgb = cB[n] --if (rgb[chan] >= median) then blockA.push(rgb); end --if (rgb[chan] < median) then blockB.push(rgb); end if (rgb[chan]*rgbw[chan] >= median) then table.insert(blockA,rgb); end if (rgb[chan]*rgbw[chan] < median) then table.insert(blockB,rgb); end end blocklist[maxblock] = blockA -- Can't be empty right? analyzeBlock(blocklist[maxblock],qual,quant,rgbw,qualnorm,quantnorm,quantpow) if (#blockB > 0) then table.insert(blocklist,blockB) analyzeBlock(blocklist[#blocklist],qual,quant,rgbw,qualnorm,quantnorm,quantpow) -- no -1 on blocklist else res = false end return res -- false = no split end ------------ eof MEDIAN CUT -------------------------- -- ------------- MEDIAN REDUX V1.0 ------------ -- -- Divide space by greatest distance of any two colors (rather than MC-method of any given channel) -- Basically it allows colorspace to be sliced at any angles rather than the "boxing" of MC. -- -- -- by Richard 'DawnBringer' Fhager -- -- -- pal: [[r,g,b,i,h]] Pallist (h = histogram/pixelcount) -- cnum: Target number of colors in reduced palette -- (step:) 1.. Pixel picks for processing, 1 = all pixels in image, best and slowest. 2 = 25% of pixels -- qual: Flag Qualitative color selection (Normal mode) -- quant: Flag Quantative color/pixel selection (Histogram) 100% mean that it count as much as quality -- (One of or both qual/quant must be selected) -- rgbw: [3] RGB-weights []. Weigh the color channels. ex: [0.26, 0.55, 0.19] -- bits: 1-8 Bits used for each color channel in palette -- quantpow: 0..1 Quantity vs Quality (put weight into histogram/pixelcount) -- briweight: 0..1 Brightness distance weight in colordistance -- proxweight: 0..1 Primary Proximity distance weight in colordistance (ColorTheory-WIP: compensate for brightness of individual channels, the "extra power" of primary colors) -- -- return: A palette! A list of [r,g,b] values -- -- NOTE: Quantity will act as a counterforce to altered colorspace (weights)... -- Ex: if Green is considered bigger, it will be split into more blocks -- but each blocks will have less colors and thus less quantity. -- -- Perceptual colorspace (rgb-weights) will generally produce the best quality of palettes, but if -- It's desirable to preserve stronger colors, esp. in small palettes, -- it can be good to just use nominal space: 0.33, 0.33, 0.33 -- -- Histogram may be useful to assign more colors to an object that already covers most of the image, -- however; if the object of interest is small in relation to a large (unimportant) background, it's -- usually best not to have any histogram at all. Histogram will dampen strong infrequent colors. function db.medianRedux(pal,cnum,qual,quant,rgbw,bits,quantpow, briweight, proxweight) -- pal[r,g,b,i,pixelcount] local n,x,y,xs,ys,rgb,blocklist,blocks local len,res,chan,diff,maxdiff,maxblock,split local qualnorm, quantnorm,count blocklist = {} blocklist[1] = {}; blocks = 1 count = 0 for n=1, #pal, 1 do blocklist[1][n] = pal[n]; count = count + pal[n][5] end -- Normalize 256 for quality/quantity relationship qualnorm = 1 / math.sqrt(rgbw[1]^2 + rgbw[2]^2 + rgbw[3]^2) quantnorm = 256 / count -- Dist table statusmessage("MR: Making Distance Table..."); updatescreen(); if (waitbreak(0)==1) then return; end local dy,c,r1,g1,b1,i1,i2 dt = {} for n=1, #pal, 1 do c = pal[n] r1,g1,b1,i1 = c[1],c[2],c[3],c[4] dt[i1+1] = {} for m=1, #pal, 1 do dt[i1+1][pal[m][4]+1] = db.getColorDistanceProx(r1,g1,b1,pal[m][1],pal[m][2],pal[m][3],rgbw[1],rgbw[2],rgbw[3],qualnorm, proxweight, briweight) -- pri/bri end end -- statusmessage("MR: Analyzing Block 1..."); updatescreen(); if (waitbreak(0)==1) then return; end r_analyzeBlock(dt,blocklist[1],qual,quant,rgbw,qualnorm,quantnorm,quantpow) statusmessage("MR: Analyzing Blocks..."); updatescreen(); if (waitbreak(0)==1) then return; end failsafe = 0 while (blocks < cnum and failsafe < 256) do failsafe = failsafe + 1 maxdiff = -1 maxblock = -1 for n=1, blocks, 1 do diff = blocklist[n].diff if (diff > maxdiff) then maxdiff = diff; maxblock = n; end -- maxchan is stored as .chan in block end split = r_splitBlock(dt,blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow) if (split == false) then messagebox("Only found "..blocks.." (24-bit) colors!"); break; end blocks = #blocklist statusmessage("MR: "..blocks); updatescreen(); if (waitbreak(0)==1) then return; end end -- while return r_blocks2Palette(blocklist,bits) end -- -- function r_blocks2Palette(blocklist,bits) local n,r,g,b,c,pal,block,rgb,blen,M,dB,cB,rf,gf,bf M = math pal = {} --bits = 1 dB = M.pow(2,8-bits) cB = M.ceil(255 / (M.pow(2,bits) - 1)) for n=1, #blocklist, 1 do block = blocklist[n] r,g,b = 0,0,0 blen = #block for c=1, blen, 1 do rgb = block[c] r = r + rgb[1] g = g + rgb[2] b = b + rgb[3] end rf = M.floor(M.min(255,M.max(0,M.floor(r/blen))) / dB) * cB gf = M.floor(M.min(255,M.max(0,M.floor(g/blen))) / dB) * cB bf = M.floor(M.min(255,M.max(0,M.floor(b/blen))) / dB) * cB pal[n] = {rf, gf, bf} -- col is avg. of all colors in block end -- blocklist return pal end -- -- function r_analyzeBlock(dt,block,qual,quant,rgbw,qualnorm,quantnorm,quantpow) local r,g,b,n,m,rmin,gmin,bmin,rmax,gmax,bmax,rdif,gdif,bdif,chan,d,median,diff local len,Mm,Mx,rgb,kv,qu local maxdist,dist,r1,g1,b1,r2,g2,b2,c1,c2,count Mx,Mm = math.max, math.min len = #block rmin,gmin,bmin = 255,255,255 rmax,gmax,bmax = 0,0,0 maxdist,c1,c2,count = 0,-1,-1,0 for n=1, len, 1 do rgb1 = block[n] count = count + rgb1[5] -- pixelcount for color for m=n+1, len, 1 do rgb2 = block[m] --dist = db.getColorDistanceProx(r1,g1,b1,r2,g2,b2,0.26,0.55,0.19,1.569, 0.1, 0.25) -- pri/bri dist = dt[rgb1[4]+1][rgb2[4]+1] if dist > maxdist then maxdist = dist c1 = rgb1[4]+1 c2 = rgb2[4]+1 end end end -- quantity and quality are normalized to 256 (256 is the total of colors in the set for quantity) -- Note that, regardless of forumla, quality (distance) must always be greater in any block than quantity (colors/pixels) -- Coz a block may contain many of only 1 unique color, thus rendering it impossible to split if selected. kv = 1 qu = 1 if (quant) then kv = math.pow(1 + count*quantnorm*quantpow, 0.5); end if (qual) then qu = maxdist * qualnorm; end diff = qu*(1-quantpow) + qu*kv block.chan = -1 block.diff = diff block.median = -1 block.c1 = c1 block.c2 = c2 return {diff,len} end -- function r_splitBlock(dt,blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow) local n,cmax,median,blockA,blockB,len,cB,block,rgb,res local c1,c2,dist1,dist2,medr,medg,medb,r1,g1,b1,r2,g2,b2,rgb1,rgb2 blockA,blockB = {},{} block = blocklist[maxblock] res = true --chan = block.chan --median = block.median c1 = block.c1 c2 = block.c2 --rgb1 = block[c1] --r1,g1,b1 = rgb1[1],rgb1[2],rgb1[3] --rgb2 = block[c2] --r2,g2,b2 = rgb2[1],rgb2[2],rgb2[3] --medr = (r1+r2)/2 --medg = (g1+g2)/2 --medb = (b1+b2)/2 cB = blocklist[maxblock] -- maxblock starts at 1 when called so it should not hava a +1 len = #cB if len < 2 then return false; end for n=1, len, 1 do rgb = cB[n] dist1 = dt[rgb[4]+1][c1] dist2 = dt[rgb[4]+1][c2] if (dist1 <= dist2) then table.insert(blockA,rgb); end if (dist1 > dist2) then table.insert(blockB,rgb); end end blocklist[maxblock] = blockA -- Can't be empty right? r_analyzeBlock(dt,blocklist[maxblock],qual,quant,rgbw,qualnorm,quantnorm,quantpow) if (#blockB > 0) then table.insert(blocklist,blockB) r_analyzeBlock(dt,blocklist[#blocklist],qual,quant,rgbw,qualnorm,quantnorm,quantpow) -- no -1 on blocklist else res = false end return res -- false = no split end ------------ eof MEDIAN REDUX -------------------------- -- -- ... eof Custom Color / Palette functions ... -- -- ***************************** -- *** Custom Draw functions *** -- ***************************** -- function db.line(x1,y1,x2,y2,c) -- Coords should be integers or broken lines are possible local n,st,m,xd,yd; m = math st = m.max(1,m.abs(x2-x1),m.abs(y2-y1)); xd = (x2-x1) / st yd = (y2-y1) / st for n = 0, st, 1 do putpicturepixel(m.floor(x1 + n*xd), m.floor(y1 + n*yd), c ); end end -- -- function db.lineTransp(x1,y1,x2,y2,c,amt) -- amt: 0-1, 1 = Full color local n,st,m,x,y,r,g,b,r1,g1,b1,c2,org; m = math org = 1 - amt st = m.max(1,m.abs(x2-x1),m.abs(y2-y1)); for n = 0, st, 1 do x = m.floor(x1+n*(x2-x1)/st) y = m.floor(y1+n*(y2-y1)/st) r,g,b = getcolor(getpicturepixel(x,y)) r1,g1,b1 = getcolor(c) c2 = matchcolor(r1*amt+r*org, g1*amt+g*org, b1*amt+b*org) putpicturepixel(x, y, c2 ); end end -- -- function db.drawBrushRectangle(x1,y1,w,h,c) local x,y for y = y1, y1+h-1, 1 do for x = x1, x1+w-1, 1 do putbrushpixel(x,y,c); end end end -- -- function db.drawRectangle(x1,y1,w,h,c) local x,y for y = y1, y1+h-1, 1 do for x = x1, x1+w-1, 1 do putpicturepixel(x,y,c); end end end -- -- function db.drawRectangleNeg(x1,y1,w,h,c) local x,y,xs,ys xs = db.sign(w) ys = db.sign(h) if xs == 0 then xs = 1; end if ys == 0 then ys = 1; end for y = y1, y1+h-1, ys do for x = x1, x1+w-1, xs do putpicturepixel(x,y,c); end end end -- -- function db.drawRectangleLine(x,y,w,h,c) w = w-1 h = h-1 db.line(x,y,x+w,y,c) db.line(x,y,x,y+h,c) db.line(x,y+h,x+w,y+h,c) db.line(x+w,y,x+w,y+h,c) end -- -- function db.drawRectangleMix(x1,y1,w,h,c1,c2) local x,y,c,n c = {c1,c2} n = 0 for y = y1, y1+h-1, 1 do n = n + 1 for x = x1, x1+w-1, 1 do n = n + 1 putpicturepixel(x,y,c[n%2+1]); end end end -- -- function db.drawCircle(x1,y1,r,c) -- ok, lottsa weird adjustments here, can probably be optimized... local x,y,d,r5,r25,r2,xr5,yr5 r5,r25,r2,xr5,yr5 = r+0.5,r-0.25,r*2, x1-r-0.5, y1-r-0.5 for y = 0, r2, 1 do for x = 0, r2, 1 do d = math.sqrt((x-r5)^2 + (y-r5)^2) if d < r25 then putpicturepixel(x + xr5, y + yr5,c); end end end end -- -- function db.drawBrushCircle(x1,y1,r,c) -- ok, lottsa weird adjustments here, can probably be optimized... local x,y,d for y = 0, r*2, 1 do for x = 0, r*2, 1 do d = math.sqrt((x-r-0.5)^2 + (y-r-0.5)^2) if d < r-0.25 then putbrushpixel(x1+x-r-0.5,y1+y-r-0.5,c); end end end end -- -- -- Rotation in degrees -- Step is # of line segments (more is "better") -- a & b are axis-radius function db.ellipse2(x,y,a,b,stp,rot,col) local n,m=math,rad,al,sa,ca,sb,cb,ox,oy,x1,y1,ast m = math; rad = m.pi/180; ast = rad * 360/stp; sb = m.sin(-rot * rad); cb = m.cos(-rot * rad) for n = 0, stp, 1 do ox = x1; oy = y1; sa = m.sin(ast*n) * b; ca = m.cos(ast*n) * a x1 = x + ca * cb - sa * sb y1 = y + ca * sb + sa * cb --if (n > 0) then db.line(ox,oy,x1,y1,col); end if (n > 0) then drawline(ox,oy,x1,y1,col); end end end -- --[[ var ER = 0.3 var DR = 0.15 ellipse(0.5*xx,0.5*yy,DR*xx,6,Math.PI*0) function ellipse(x,y,r,stp,rot){ var n,deg=360,m=Math,rad=Math.PI/180,rn var ox,oy,x1,y1,x2,y2,d1,r1 = ER * xx for (n=0; n<=deg; n+=stp){ ox = x2; oy = y2, rn = rad * n d1 = rn - rot x1 = x + m.sin(d1) * r y1 = y + m.cos(d1) * r x2 = x1 + m.sin(-rn) * r1 y2 = y1 + m.cos(-rn) * r1 if (n > 0){ line_rgb(MX,[0,0,0],0,ox,oy,x2,y2) } } } } ellipse2(0.5*xx,0.5*yy,15,8,200,22,[0,0,0],0.5) function ellipse2(x,y,a,b,stp,rot,rgb,transp){ var n,m=Math,rad=m.PI/180,al,sa,ca,sb,cb,ox,oy,x1,y1 sb = m.sin(-rot * rad); cb = m.cos(-rot * rad) for (n=0; n<=stp; n++){ ox = x1; oy = y1; al = rad * 360/stp * n sa = m.sin(al) * b; ca = m.cos(al) * a x1 = x + ca * cb - sa * sb y1 = y + ca * sb + sa * cb if (n > 0){ line_rgb(MX,rgb,transp,ox,oy,x1,y1) } } } ]] function db.obliqueCube(side,x,y,r,g,b,bri,cols) local n,c,depth,x1,y1,x2,y2,f asscols = false if cols >= 0 and cols<250 then asscols = true c = cols; setcolor(cols,r,g,b); cols = cols + 1 cP50 = cols; q = bri*0.5; setcolor(cols,r+q,g+q,b+q); cols = cols + 1; cP75 = cols; q = bri*0.75; setcolor(cols,r+q,g+q,b+q); cols = cols + 1; cM50 = cols; q = -bri*0.5; setcolor(cols,r+q,g+q,b+q); cols = cols + 1; cM100= cols; q = -bri; setcolor(cols,r+q,g+q,b+q); cols = cols + 1; end f = matchcolor if asscols == false then c = f(r,g,b) cP50 = f(r+bri*0.5,g+bri*0.5,b+bri*0.5) cP75 = f(r+bri*0.75,g+bri*0.75,b+bri*0.75) cM50 = f(r-bri*0.5,g-bri*0.5,b-bri*0.5) cM100 = f(r-bri,g-bri,b-bri) end depth = math.floor(side / 2) for n = 0, depth-1, 1 do db.line(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50) --drawline(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50) end for n = 0, depth-1, 1 do db.line(x+n,y-1-n,x+side+n-1,y-1-n,cP50) --drawline(x+n,y-1-n,x+side+n-1,y-1-n,cP50) end -- / -- --db.line(x+side,y-1,x+side+depth-1,y-depth,c) -- Smoothing & Shade -- -- / --db.line(x+side,y+side-1,x+side+depth-1,y+side-depth,cM100) --db.line(x,y,x+side-2,y,cP75) --db.line(x,y,x,y+side-2,cP75) db.drawRectangle(x,y,side,side,c) return cols end function db.obliqueCubeBRI(side,x,y,r,g,b,bri,pallist,briweight,index_flag) local n,c,depth,x1,y1,x2,y2 --f = db.getBestPalMatchHYBRID c = db.getBestPalMatchHYBRID({r,g,b}, pallist, briweight, index_flag) cP50 = db.getBestPalMatchHYBRID({r+bri*0.5,g+bri*0.5,b+bri*0.5}, pallist, briweight, index_flag) cP75 = db.getBestPalMatchHYBRID({r+bri*0.75,g+bri*0.75,b+bri*0.75}, pallist, briweight, index_flag) cM50 = db.getBestPalMatchHYBRID({r-bri*0.5,g-bri*0.5,b-bri*0.5}, pallist, briweight, index_flag) cM100 = db.getBestPalMatchHYBRID({r-bri,g-bri,b-bri}, pallist, briweight, index_flag) depth = math.floor(side / 2) db.drawRectangle(x,y,side,side,c) for n = 0, depth-1, 1 do db.line(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50) --drawline(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50) end for n = 0, depth-1, 1 do db.line(x+n,y-1-n,x+side+n-1,y-1-n,cP50) --drawline(x+n,y-1-n,x+side+n-1,y-1-n,cP50) end -- / -- db.line(x+side,y-1,x+side+depth-1,y-depth,c) --drawline(x+side,y-1,x+side+depth-1,y-depth,c) -- Smoothing & Shade -- -- / --db.line(x+side,y+side-1,x+side+depth-1,y+side-depth,cM100) --db.line(x,y,x+side-2,y,cP75) --db.line(x,y,x,y+side-2,cP75) end -- function db.fillTriangle(p,fcol,lcol,fill,wire) -- p = list of 3 points local n,x,y,x1,x2,y1,y2,xf,yf,len,mr mr = math.floor -- Convert to screen/matrix-coordinates --if (mode == 'percent') then xf = xx / 100; yf = yy / 100; end --if (mode == 'fraction') then xf = xx; yf = yy; end --if (mode ~= 'absolute') then screenilizeTriangle(p,xf,yf); end if (fill) then local Ax,Ay,Bx,By,Cx,Cy,xd,a,b,yc,ABdy,BCdy,ABix,BCix,ACix xd = {} --sort(p,1) -- Find top and middle y-point db.sorti(p,2) Ay = p[1][2]; Ax = p[1][1] By = p[2][2]; Bx = p[2][1] Cy = p[3][2]; Cx = p[3][1] ABdy = By - Ay BCdy = Cy - By ABix = (Bx - Ax) / ABdy BCix = (Cx - Bx) / BCdy ACix = (Cx - Ax) / (Cy - Ay) a=1; b=2; if (ACix < ABix) then a=2; b=1; end for y = 0, ABdy-1, 1 do -- Upper -1 xd[a] = mr(Ax + ABix * y) xd[b] = mr(Ax + ACix * y) yc = y+Ay; for x=xd[1], xd[2], 1 do putpicturepixel(x,yc,fcol) end end a=1; b=2; if (BCix < ACix) then a=2; b=1; end for y = 0, BCdy, 1 do -- Lower xd[a] = mr(Cx - BCix * y); xd[b] = mr(Cx - ACix * y) yc = Cy-y; for x = xd[1], xd[2], 1 do putpicturepixel(x,yc,fcol) end end end -- eof fill if (wire) then for n = 0, 2, 1 do -- Outline x1 = p[n+1][1]; y1 = p[n+1][2] x2 = p[1 + (n+1) % 3][1]; y2 = p[1 + (n+1) % 3][2] --db.line(x1,y1,x2,y2,lcol) drawline(x1,y1,x2,y2,lcol) end end end -- eof fillTriangle -- -- -- ... eof Custom Draw functions ... -- -- ****************************** -- *** Filters & Convolutions *** -- ****************************** function db.applyConvolution2Pic(convmx,divisor,bias,neg,amt) local r,g,b,mx,my,cx,cy,mxh,myh,mp,rb,gb,bb,xx,yy,x,y,w,h,div,n1,n2,amtr,ro,go,bo n1 = 1 n2 = bias if neg == 1 then n1 = -1 n2 = 255 + bias end amtr = 1 - amt w, h = getpicturesize() cy = #convmx cx = #convmx[1] mxh = math.floor(cx / 2) + 1 myh = math.floor(cy / 2) + 1 for y = 0, h-1, 1 do for x = 0, w-1, 1 do r,g,b = 0,0,0 ro,go,bo = getcolor(getbackuppixel(x,y)) div = divisor for my = 1, cy, 1 do for mx = 1, cx, 1 do xp = mx-mxh yp = my-myh mp = convmx[my][mx] xx = x + xp yy = y + yp if yy>=0 and yy=0 and xx 0 and n 0 and n0) then x = x*px - fx y = y*py - fy nfrac = nfrac + spfrac end n = n+1 end --return 1 - n/i; return 1 - nfrac/i end -- -- function db.mandel(x,y,l,r,o,i) -- pos. as fraction of 1, left coord, right coord, y coord, iterations local w,s,a,p,q,n,v,w s=math.abs(r-l); a = l + s*x; p = a; b = o - s*(y-0.5); q = b; n = 1; v = 0; w = 0; while (v+w<4 and n weakest then weakest = w; weak_i = {z,y,x}; end end end;end;end return weak_i[1],weak_i[2],weak_i[3] end -- -- -- -- Nearest color version: void is selected by the point that has the greatest distance -- to the nearest color. Higher value means greater void. -- function db.addColor2Cube(cube,sha,r,g,b,rw,gw,bw) local star,x,y,z,d,rd,gd,bd,cu1,cu2 star = 0 cube[r+1][g+1][b+1] = {false, star} for z = 0, sha-1, 1 do rd = (rw*(z-r))^2 cu2 = cube[z+1] for y = 0, sha-1, 1 do gd = (gw*(y-g))^2 cu1 = cu2[y+1] for x = 0, sha-1, 1 do d = rd + gd + (bw*(x-b))^2 --cube[z+1][y+1][x+1][2] = math.min(d, cube[z+1][y+1][x+1][2]) -- Don't add, use nearest color cu1[x+1][2] = math.min(d, cu1[x+1][2]) end;end;end end -- -- Should be same as original, but not 100% verified. Using a rgb+1 trick to speed up handling -- function db.addColor2Cube_test(cube,sha,r,g,b,rw,gw,bw) local star,x,y,z,d,rd,gd,bd,cu1,cu2 star = 0 r = r+1; g = g+1; b = b+1 cube[r][g][b] = {false, star} for z = 1, sha, 1 do rd = (rw*(z-r))^2 cu2 = cube[z] for y = 1, sha, 1 do gd = (gw*(y-g))^2 cu1 = cu2[y] for x = 1, sha, 1 do cu1[x][2] = math.min(rd+gd+(bw*(x-b))^2, cu1[x][2]) end;end;end end -- -- Create new allowed colorlines in colorspace (ramps from which colors can be picked) function db.enableRangeColorsInCube(cube,sha,r1,g1,b1,r2,g2,b2) local div,r,g,b,n,rs,gs,bs div = 256 / sha rs = (r2 - r1) / sha / div gs = (g2 - g1) / sha / div bs = (b2 - b1) / sha / div for n = 0, sha-1, 1 do r = math.floor(r1/div + rs * n) g = math.floor(g1/div + gs * n) b = math.floor(b1/div + bs * n) cube[r+1][g+1][b+1][1] = true end end -- function db.colorCigarr(shades,radius,fill_flag) local s,rad,radsq,step,shalf,bas,cols,found,x,y,z,bri,con,d,n radius = radius / 100 step = math.floor(255 / (shades-1)) shalf = math.floor(shades / 2) s = shades - 1 rad = math.floor(shades / 2 * radius) radsq = rad^2 bas = 0 cols = {} found = 0 for z = 0, s, 1 do for y = 0, s, 1 do for x = 0, s, 1 do --0.26,0.55,0.19 bri = (x + y + z ) / 3 --bri = math.sqrt(((x*0.26)^2 + (y*0.55)^2 + (z*0.19)^2)) * 1.5609 con = math.floor((shades - math.abs(bri - shalf)*2) * radius) d = math.floor(math.sqrt((bri-x)^2 + (bri-y)^2 + (bri-z)^2)) --d = math.floor(math.sqrt(((bri-x)*0.26)^2 + ((bri-y)*0.55)^2 + ((bri-z)*0.19)^2)) * 1.5609 -- Filled cigarr: Less or Equal, cigarr shell: Equal if d == con or (d < con and fill_flag) then found = found + 1 r = bas + x * step g = bas + y * step b = bas + z * step cols[found] = {r,g,b} end end; end; end --messagebox("Colors found: "..found.."\n\n".."Run AnalyzePalette to examine") for n = 0, 255, 1 do if n < found then c = cols[n+1] setcolor(n,c[1],c[2],c[3]) else setcolor(n,0,0,0) end end end -- eof colorcigarr -- -- ... eof Color Cube ... -- -- COLORMIX -- -- -- Returns a list of mixcolors palette entries, that are ranked by by quality & usefulness -- -- This whole junk my partly locked on 16 shades (4096 colors/ 12bit palette precision) so don't use anything else... -- -- function db.colormixAnalysis(sha,spare_flag,cust_dist) -- Interface local shades,pallist,ilist,custom_max_distance shades = sha -- 16 is good --messagebox(shades) custom_max_distance = -1 if cust_dist ~= null then custom_max_distance = cust_dist -- in % end if spare_flag == true then -- No shades here for now --pallist = db.makePalListShadeSPARE(256,shades) -- 16 shades so Colorcube processes is possible pallist = db.makeSparePalList(256) pallist = db.fixPalette(pallist,0) -- Remove doubles, No need to sort? ilist = db.makeIndexList(pallist, -1) -- -1, use list order as index else pallist = db.makePalListShade(256,shades) -- 16 shades so Colorcube processes is possible pallist = db.fixPalette(pallist,0) -- Remove doubles, No need to sort? ilist = db.makeIndexList(pallist, -1) -- -1, use list order as index end if shades > 0 then return db.colormixAnalysisEXT(shades,pallist,ilist,custom_max_distance) -- max distance in % end if shades == -1 then return db.colormixAnalysisEXTnoshade(pallist,ilist,custom_max_distance) -- max distance in % end end -- -- function db.colormixAnalysisEXT(SHADES,pallist,ilist,custom_max_distance) -- Shades, most number of mixes returned local n,m,c1,c2,pairs,cube,rm,gm,bm local mix,total,found,dist,void,ideal,mini,maxi,bestmix,bestscore --messagebox("will now make pairs") pairs = db.pairsFromList(ilist,0) -- 0 for unique pairs only, pairs are entries in pallist --messagebox(#pairs.." will now add colors to cube") cube = db.initColorCube(SHADES,{true,9999}) for n = 1, #pallist, 1 do c1 = pallist[n] db.addColor2Cube_test(cube,SHADES,c1[1],c1[2],c1[3],0.26,0.55,0.19) end -- these values are adjusted for a 12bit palette (0-15) and perceptual weight where r+g+b = 1.0 -- Ideal distance = 2.5 Green steps = 1.375 -- Minimum distance = 1 Green step = 0.55 --messagebox("colorcube done") VACT = 1 DACT = 1 total = 9.56 -- Max distance possible with 16 shades ideal = 0.45 -- 1 step = 0.637 mini = 0.35 maxi = ideal + (total - ideal) / math.max(1, #pallist / 16) if custom_max_distance ~= -1 then maxi = total * (custom_max_distance / 100) end mix = {} --mix[1] = {9e99,0,0,9e99,0,0,0} bestmix = -1 bestscore = 9e99 found = 0 for n = 1, #pairs, 1 do c1 = pallist[pairs[n][1]] c2 = pallist[pairs[n][2]] --0.26,0.55,0.19 dist = db.getColorDistance_weight(c1[1],c1[2],c1[3],c2[1],c2[2],c2[3],0.26,0.55,0.19) -- Not normalized rm = math.floor((c1[1]+c2[1])/2) gm = math.floor((c1[2]+c2[2])/2) bm = math.floor((c1[3]+c2[3])/2) -- Mix color adjustment (perhaps less than perfect, but probably good enough) mixbri = db.getBrightness(rm,gm,bm) truebri = math.sqrt((db.getBrightness(c1[1],c1[2],c1[3])^2 + db.getBrightness(c2[1],c2[2],c2[3])^2) / 2) diff = truebri - mixbri rm = math.max(0,math.min(15,math.floor(rm + diff))) gm = math.max(0,math.min(15,math.floor(gm + diff))) bm = math.max(0,math.min(15,math.floor(bm + diff))) newbri = db.getBrightness(rm,gm,bm) delta = math.abs(newbri - truebri) --if delta > 0.9 then -- messagebox(pallist[pairs[n][1]][4]..", "..pallist[pairs[n][2]][4].." delta = "..delta) --end -- --rm = math.floor(math.sqrt((c1[1]^2 + c2[1]^2) / 2)) --gm = math.floor(math.sqrt((c1[2]^2 + c2[2]^2) / 2)) --bm = math.floor(math.sqrt((c1[3]^2 + c2[3]^2) / 2)) void = cube[rm+1][gm+1][bm+1][2] if dist >= mini and dist <= maxi then found = found + 1 score = ((1+DACT*(dist - ideal)^2) / (1+void*VACT)) -- Lowest is best mix[found] = {score,pallist[pairs[n][1]][4],pallist[pairs[n][2]][4],dist,rm*SHADES,gm*SHADES,bm*SHADES,c1[1]*SHADES,c1[2]*SHADES,c1[3]*SHADES,c2[1]*SHADES,c2[2]*SHADES,c2[3]*SHADES} -- mix holds palette entry if score < bestscore then bestscore = score; bestmix = found; end end end if true == false then -- 2nd pass, add bestmix to colorspace. This reduces many similar mixes. m = mix[bestmix] db.addColor2Cube(cube,SHADES,m[5],m[6],m[7],0.26,0.55,0.19) for n = 1, #mix, 1 do if n ~= bestmix then m = mix[n] dist = m[4] void = cube[m[5]+1][m[6]+1][m[7]+1][2] score = ((1+DACT*(dist - ideal)^2) / (1+void*VACT)) m[1] = score end end end c1,c2 = -1,-1 if found > 0 then db.sorti(mix,1) best = mix[1] c1 = best[2] c2 = best[3] end --return found,c1,c2 return mix,found,c1,c2 end -- -- -- Mixcolor without colorcube - no scoring or sorting, 24bit colors, faster... -- function db.colormixAnalysisEXTnoshade(pallist,ilist,custom_max_distance) local n,m,c1,c2,pairs,cube,rm,gm,bm local mix,total,found,dist,void,ideal,mini,maxi,bestmix,bestscore pairs = db.pairsFromList(ilist,0) -- 0 for unique pairs only, pairs are entries in pallist total = 162.53 -- Max distance possible with 24-bit palette ad Dawn3.0 color weights 162.53 ideal = 0 mini = 0 maxi = ideal + (total - ideal) / math.max(1, #pallist / 16) if custom_max_distance ~= -1 then maxi = total * (custom_max_distance / 100) end statusmessage("Mixcol Analysis ("..#pairs.." pairs) "); updatescreen(); if (waitbreak(0)==1) then return; end mix = {} found = 0 for n = 1, #pairs, 1 do c1 = pallist[pairs[n][1]] c2 = pallist[pairs[n][2]] --0.26,0.55,0.19 dist = db.getColorDistance_weight(c1[1],c1[2],c1[3],c2[1],c2[2],c2[3],0.26,0.55,0.19) -- Not normalized rm = math.floor((c1[1]+c2[1])/2) gm = math.floor((c1[2]+c2[2])/2) bm = math.floor((c1[3]+c2[3])/2) -- Mix color adjustment mixbri = db.getBrightness(rm,gm,bm) truebri = math.sqrt((db.getBrightness(c1[1],c1[2],c1[3])^2 + db.getBrightness(c2[1],c2[2],c2[3])^2) / 2) diff = truebri - mixbri rm = math.max(0,math.min(255,math.floor(rm + diff))) gm = math.max(0,math.min(255,math.floor(gm + diff))) bm = math.max(0,math.min(255,math.floor(bm + diff))) newbri = db.getBrightness(rm,gm,bm) delta = math.abs(newbri - truebri) --if delta > 0.9 then -- messagebox(pallist[pairs[n][1]][4]..", "..pallist[pairs[n][2]][4].." delta = "..delta) --end -- if dist >= mini and dist <= maxi then found = found + 1 score = 1 mix[found] = {score,pallist[pairs[n][1]][4],pallist[pairs[n][2]][4],dist,rm,gm,bm,c1[1],c1[2],c1[3],c2[1],c2[2],c2[3]} -- mix holds palette entry end end --messagebox(#mix) return mix,found,-1,-1 end -- -- Fuse a palettelist into an extended mix-anlysis list function db.fusePALandMIX(pal,mix,max_score,max_dist) local n,c,mixlist,tot,score,dist,c1,c2,rm,gm,bm mixlist = {} tot = 0 -- {r,g,b,n} for n = 1, #pal, 1 do tot = tot + 1 c = pal[n] mixlist[tot] = {0,c[4],c[4],0,c[1],c[2],c[3],c[1],c[2],c[3],c[1],c[2],c[3]} end -- {score,col#1,col#2,dist,rm,gm,bm} low score is best for n = 1, #mix, 1 do score = mix[n][1] dist = mix[n][4] if score <= max_score and dist <= max_dist then tot = tot + 1 mixlist[tot] = mix[n] end end return mixlist end -- -- ******************************************** -- *** L-system (fractal curves & "plants") *** -- ******************************************** -- -- function db.Lsys_makeData(a) local n,i; i = {} for n = 1, #a, 1 do i[a[n][2]] = a[n]; end return i end -- -- function db.Lsys_makeSet(seed,iter,data) local s,n,i,nset,set set = seed for n = 1, iter, 1 do nset = '' for i = 1, #set, 1 do s = string.sub(set,i,i) nset = nset..data[s][3] end set = nset end return set end -- function db.Lsys_draw(set,data,cx,cy,size,rot,rgb,rng,transp, speed) local p,M,DEG,l,n,d,i,v,q,c,tx,ty,posx,posy,dval,col,w,h,s,cl,count if speed == nil then speed = 50; end -- speed is drawing operations per update function ang(d) return (d % 360 + 360) * DEG; end w,h = getpicturesize() p = 0 M = math DEG = math.pi/180 l = #set posx={}; posy={}; dval={} if (rgb == null) then rgb = {0,0,0}; end if (transp == null) then transp = 0; end col = db.newArrayMerge(rgb,{}) q = 255 / l count = 0 for n = 1, l, 1 do s = string.sub(set,n,n) d = data[s] i = d[1] v = d[4] --messagebox(i) if (i == 'Left') then rot = rot - v; end if (i == 'Right') then rot = rot + v; end if (i == 'Save') then p=p+1; posx[p] = cx; posy[p] = cy; dval[p] = rot; end if (i == 'Load') then cx = posx[p]; cy = posy[p]; rot = dval[p]; p=p-1; end if (i == 'Draw') then tx = cx + M.sin(ang(rot)) * size ty = cy + -M.cos(ang(rot)) * size for c = 1, 3, 1 do if (rng[c] > 0) then col[c] = rgb[c] + (n * q) * rng[c]; end if (rng[c] < 0) then col[c] = rgb[c] + (n * q) * rng[c]; end end cl = matchcolor(col[1],col[2],col[3]) --putpicturepixel(cx*w,cy*h,cl); --db.line(cx*w,cy*h,tx*w,ty*h,cl) db.lineTransp(cx*w,cy*h,tx*w,ty*h,cl,transp) cx = tx; cy = ty end count = count + 1 if count == speed then count = 0; updatescreen(); if (waitbreak(0)==1) then return end; end end return {cx,cy,rot} end -- draw -- -- eof L-system -- --------------------------------------------------------------------------------------- -- ******************************************** -- *** COMPLEX SPECIAL FUNCTIONS *** -- ******************************************** -- --------------------------------------------------------------------------------------- -- -- Render engine for mathscenes (Full Floyd-Steinberg dither etc) -- function db.fsrender(f,pal,ditherprc,xdith,ydith,percep,xonly, ord_bri,ord_hue,bri_change,hue_change,BRIWEIGHT, wd,ht,ofx,ofy) -- f is function local w,h,i,j,c,x,y,r,g,b,d,fl,v,v1,v2,vt,vt1,vt2,dither,m,mathfunc,dpow,fsdiv,ord,d1a,d1b,briweight local d1,d2,o1,o2,ox,oy -- percep is no longer used, matchcolor2 is always active, but the code is kept if there's ever a need to -- study the effect of perceptual colorspaces versus matchcolor if ord_bri == null then ord_bri = 0; end if ord_hue == null then ord_hue = 0; end if bri_change == null then bri_change = 0; end if hue_change == null then hue_change = 0; end if BRIWEIGHT == null then BRIWEIGHT = 0; end briweight = BRIWEIGHT / 100 ord = {{0,4,1,5}, {6,2,7,3}, {1,5,0,4}, {7,3,6,2}} --i = ((ord[y % 4 + 1][x % 4 + 1])*28.444 - 99.55556)/100 * 16 function hue(r,g,b,deg) local i,brin,diff,brio,r2,g2,b2 r2,g2,b2 = db.shiftHUE(r,g,b,deg) brio = db.getBrightness(r,g,b) for i = 0, 5, 1 do -- 6 iterations, fairly strict brightness preservation brin = db.getBrightness(r2,g2,b2) diff = brin - brio r2,g2,b2 = db.rgbcap(r2-diff, g2-diff, b2-diff, 255,0) end return r2,g2,b2 end fsdiv = 16 if xonly == 1 then fsdiv = 7; end -- Only horizontal dither dither = 0; if ditherprc > 0 then dither = 1; end -- When using standard error-diffusion brightness-matching is not really compatible --matchfunc = matchcolor2 --if dither == 1 then matchfunc = matchcolor; end dpow = ditherprc / 100 if wd == null then w,h = getpicturesize() else w = wd; h = ht end if ofx == null then ox,oy = 0,0 else ox = ofx; oy = ofy end function cap(v) return math.min(255,math.max(0,v)) end -- fl = {} fl[1] = {} fl[2] = {} i = 1 j = 2 -- -- Read the first 2 lines v1 = ydith/2 + 0%2 * -ydith v2 = ydith/2 + 1%2 * -ydith for x = 0, w - 1, 1 do d1a,d1b = 0,0 if ord_bri > 0 then o1 = ord[0 % 4 + 1][x % 4 + 1] d1a = (o1*28.444 - 99.55556)/100 * ord_bri o2 = ord[1 % 4 + 1][(x+2) % 4 + 1] -- +2 To get it in right sequence for some reason d1b = (o2*28.444 - 99.55556)/100 * ord_bri end -- We skip Hue-ordering for now vt1 = v1 + xdith/2 + (x+math.floor(0/2))%2 * -xdith + d1a vt2 = v2 + xdith/2 + (x+math.floor(1))%2 * -xdith + d1b -- Ok, not sure why 1/2 doesn't make for a nice pattern so we just use 1 r,g,b = f(x, 0, w, h) fl[i][x] = {cap(r+vt1),cap(g+vt1),cap(b+vt1)} r,g,b = f(x, 1, w, h) fl[j][x] = {cap(r+vt2),cap(g+vt2),cap(b+vt2)} end for y = 0, h-1, 1 do for x = 0, w-1, 1 do o = fl[i][x] r = o[1] + bri_change g = o[2] + bri_change b = o[3] + bri_change if hue_change ~= 0 then r,g,b = hue(r,g,b,hue_change) end --if percep == 0 then c = matchfunc(r,g,b); end --if percep == 1 then -- --c = db.getBestPalMatchHYBRID({r,g,b},pal,0,true) --c = matchcolor2(r,g,b,briweight) --end c = matchcolor2(r,g,b,briweight) putpicturepixel(x+ox,y+oy,c) if dither == 1 then if x>1 and x 0 then o = ord[y % 4 + 1][x % 4 + 1] d1 = (o*28.444 - 99.55556)/100 * ord_bri end vt = v + xdith/2 + (x+math.floor(y/2))%2 * -xdith + d1 r,g,b = f(x, y+2, w, h) if ord_hue > 0 then o = ord[y % 4 + 1][x % 4 + 1] d2 = (((o + 3.5) % 7) / 7 - 0.5) * ord_hue r,g,b = hue(r,g,b,d2) end fl[j][x] = {cap(r+vt),cap(g+vt),cap(b+vt)} end updatescreen(); if (waitbreak(0)==1) then return; end end end ------------------------------------------------------------------------------- -- -- ROTATE Image or Brush -- -- target: 1 = Brush, 2 = Picture, 3 = Brush-to-Picture -- rot: Rotation in degrees -- mode: 1 = Simple, 2 = Cosine Interpolation, 2 = BiLinear Interpolation -- spritemode: 0 = Off, 1 = On (Only match adjacent colors, use with Bilinear-Ip. for good result) -- resize: 0 = No, 1 = Yes (Resize Image/Brush to fit all gfx, otherwise clip) -- update: 0 = No, 1 = Yes (Update screen while drawing) -- xoffset: For use with Brush-to-Picture operations -- yoffset: For use with Brush-to-Picture operations -- function db.doRotation(target,rot,mode,spritemode,resize,update, xoffset,yoffset) local trg,f,w,h,x,y,r,g,b,c,hub_x,hub_y,x1,y1,x2,y2,x3,y3,x4,y4,dX,dY,dXs,dYs,ox,oy,mx,my,xp,yp,pal,func function donothing(n) end func = { {getsize=getbrushsize, setsize=setbrushsize, clear=donothing, get=getbrushbackuppixel, put=putbrushpixel}, {getsize=getpicturesize, setsize=setpicturesize, clear=clearpicture, get=getbackuppixel, put=putpicturepixel}, {getsize=getbrushsize, setsize=donothing, clear=donothing, get=getbrushbackuppixel, put=putpicturepixel} } trg = func[target] -- function bilinear(ox,oy,w,h,func,mode) local xp1,xp2,yp1,yp2,r1,r2,r3,r4,g1,g2,g3,g4,b1,b2,b3,b4,r,g,b, c1,c2,c3,c4,pal,adjx,adjy xp2 = ox - math.floor(ox) yp2 = oy - math.floor(oy) if mode == 1 then -- Cosinus curve (rather than linear), slightly sharper result (probably same as Photoshop) xp2 = 1 - (math.cos(xp2 * math.pi) + 1)/2 yp2 = 1 - (math.cos(yp2 * math.pi) + 1)/2 end xp1 = 1 - xp2 yp1 = 1 - yp2 c1 = func(math.floor(ox),math.floor(oy)); c2 = func(math.ceil(ox),math.floor(oy)); c3 = func(math.floor(ox),math.ceil(oy)); c4 = func(math.ceil(ox),math.ceil(oy)); r1,g1,b1 = getcolor(c1); r2,g2,b2 = getcolor(c2); r3,g3,b3 = getcolor(c3); r4,g4,b4 = getcolor(c4); pal = {{r1,g1,b1,c1},{r2,g2,b2,c2},{r3,g3,b3,c3},{r4,g4,b4,c4}} -- for SpriteMode ColorMatching r = (r1*xp1 + r2*xp2)*yp1 + (r3*xp1 + r4*xp2)*yp2; g = (g1*xp1 + g2*xp2)*yp1 + (g3*xp1 + g4*xp2)*yp2; b = (b1*xp1 + b2*xp2)*yp1 + (b3*xp1 + b4*xp2)*yp2; return r,g,b, pal end -- f = db.rotationFrac w,h = trg.getsize() hub_x = w / 2 - 0.5 -- Rotates 90,180 perfectly, not 45 hub_y = h / 2 - 0.5 --hub_x = w / 2 --hub_y = h / 2 x1,y1 = f (-rot,hub_x,hub_y,0,0) -- Rot is negative coz we read destination and write to source x2,y2 = f (-rot,hub_x,hub_y,w-1,0) x3,y3 = f (-rot,hub_x,hub_y,0,h-1) x4,y4 = f (-rot,hub_x,hub_y,w-1,h-1) dX = (x2 - x1) / w dY = (y2 - y1) / w dXs = (x4 - x2) / h dYs = (y3 - y1) / h adjx,adjy = 0,0 ox,oy = 0,0 if resize == 1 then mx = math.ceil(math.max(math.abs(x1-hub_x),math.abs(x3-hub_x))) * 2 + 2 my = math.ceil(math.max(math.abs(y1-hub_y),math.abs(y3-hub_y))) * 2 + 2 if target == 3 then -- Center gfx at Brush-to-Picture adjx = -mx/2 adjy = -my/2 end ox = (mx - w) / 2 oy = (my - h) / 2 trg.setsize(mx,my) end trg.clear(0) for y = -oy, h-1+oy, 1 do RE,GE,BE = 0,0,0 for x = -ox, w-1+ox, 1 do xp = x1 + dX * x + dXs * y yp = y1 + dY * x + dYs * y if mode == 2 or mode == 3 then r,g,b,pal = bilinear(xp,yp,w,h,trg.get, mode_co) if spritemode == 1 then c = db.getBestPalMatchHYBRID({r+RE,g+GE,b+BE},pal,0.65,true) -- Brightness do very little in general with 4 set colors else c = matchcolor2(r+RE,g+GE,b+BE) end else c = trg.get(xp,yp) end --rn,gn,bn = getcolor(c) --RE = (r - rn)*0.5 --GE = (g - gn)*0.5 --BE = (b - bn)*0.5 trg.put(x+ox+xoffset+adjx,y+oy+yoffset+adjy, c) end if update == 1 then statusmessage("Working... %"..math.floor(((y+oy) / (h-1+2*oy))*100)) updatescreen(); if (waitbreak(0)==1) then return; end end end end; -- doRotation ------------------------------------------------------------------------------- -- -- PARTICLE v1.0 -- -- Draw Sphere or Disc to any target with gradients that may fade to background -- -- type: "sphere" - volmetric planet-like disc -- "disc" - plain disc -- mode: "blend" - mix graphics with background color -- "add" - add graphics to background color -- wd,ht: - Max Width/Height of drawing area, i.e. screen size (needed if drawing to an array-buffer) -- sx,sy: - drawing coordinates (center) -- xrad,yrad: - x & y radii -- rgba1: - rgb+alpha array of center color: {r,g,b,a}, alpha is 0..1 where 1 is no transparency, Extreme rgb-values allowed -- rgba2: - rgb+alpha array of edge color: {r,g,b,a}, alpha is 0..1 where 1 is no transparency, Extreme rgb-values allowed -- update_flag: - Display rendering option (and add break feature) -- f_get: - Get pixel function: use getpicturepixel if reading from image (set null for image default) -- f_put: - Put pixel function: use putpicturepixel if drawing to image (set null for image default) -- f_recur: - Optional custom control-function for recursion (set null if not used) -- recur_count - Recursion depth counter, for use in combination with a custom function (f_recur), 0 as default -- -- Ex: particle("sphere","add", w,h, w/2,h/2, 40,40, {500,400,255, 0.8},{0,-150,-175, 0.0}, true, null, null, null, 0) -- function db.particle(type,mode,wd,ht,sx,sy,xrad,yrad,rgba1,rgba2, update_flag, f_get, f_put, f_recur, recur_count) local x,y,rev,dix,diy,r3,g3,b3,px,py,alpha,ralpha,add,q,rgb,rgb1,rgb2,rgb3,n,def_get,def_put function def_get(x,y) local r,g,b r,g,b = getcolor(getpicturepixel(x,y)); return {r,g,b} end function def_put(x,y,r,g,b) putpicturepixel(x,y, matchcolor2(r,g,b,0.65)); end if f_get == null then f_get = def_get; end if f_put == null then f_put = def_put; end q = {[true] = 1, [false] = 0} rgb,rgb1,rgb2 = {},{},{} if mode == 'blend' then add = 1 end if mode == 'add' then add = 0 end dix = xrad*2 diy = yrad*2 for y = 0, diy, 1 do py = sy+y-yrad; oy = y / diy; if (py >= 0 and py < ht) then for x = 0, dix, 1 do px = sx+x-xrad; ox = x / dix; if (px >= 0 and px < wd) then if type == 'sphere' then -- Sphere a = math.sqrt(math.max(0,0.25 - ((0.5-ox)^2+(0.5-oy)^2))) * 2 end if type == 'disc' then -- Disc a = 1-math.sqrt((0.5-ox)^2+(0.5-oy)^2)*2 end if a>0 then rev = 1-a rgb3 = f_get(px,py) alpha = rgba1[4] * a + rgba2[4] * rev ralpha = 1 - alpha * add for n = 1, 3, 1 do rgb1[n] = q[rgba1[n]==-1]*rgb3[n] + q[rgba1[n]~=-1]*rgba1[n] -- Fade from background? rgb2[n] = q[rgba2[n]==-1]*rgb3[n] + q[rgba2[n]~=-1]*rgba2[n] -- Fade to background? rgb[n] = (rgb1[n] * a + rgb2[n] * rev) * alpha + rgb3[n] * ralpha end f_put(px, py, rgb[1],rgb[2],rgb[3]); end end -- if x is good end -- x if update_flag then updatescreen(); if (waitbreak(0)==1) then return; end; end end -- if y is good end -- y if f_recur ~= null then -- recursion f_recur(type,mode,wd,ht,sx,sy,xrad,yrad,rgba1,rgba2, update_flag, f_get, f_put, f_recur, recur_count); updatescreen(); if (waitbreak(0)==1) then return; end; end end -- eof PARTICLE -- -- MedianCut a larger palette-list from a MathScene to produce a high-quality BriSorted palette for the final render/colormatching -- function db.makeSamplePal(w,h,colors,frend) local n,x,y,r,g,b,pal n,pal = 0,{} for y = 0, h, math.ceil(h/63) do for x = 0, w, math.ceil(w/63) do r,g,b = frend(x,y,w,h) n = n+1 r,g,b = db.rgbcapInt(r,g,b,255,0) pal[n] = {r,g,b,0} end;end return db.fixPalette(db.medianCut(pal, colors, true, false, {0.26,0.55,0.19}, 8, 0),1) -- pal, cols, qual, quant, weights, bits, quantpower end -- -- -- Backdrop/Gradient Render (May be written to a matrix for rendering with db.fsrender) -- function db.backdrop(p0,p1,p2,p3,fput,ip_mode) -- points:{x,y,r,g,b}, IpMode "linear" is default local x,y,ox,oy,xr,yr,r,g,b,ax,ay,w,h ax,ay = p0[1],p0[2] w = p1[1] - p0[1] h = p2[2] - p0[2] for y = 0, h, 1 do -- +1 to fill screen with FS-render oy = y/h if ip_mode == "cosine" then oy = 1 - (math.cos(oy * math.pi) + 1)/2; end yr = 1 - oy for x = 0, w, 1 do ox = x/w if ip_mode == "cosine" then ox = 1 - (math.cos(ox * math.pi) + 1)/2; end xr = 1 - ox r = (p0[3]*xr + p1[3]*ox)*yr + (p2[3]*xr + p3[3]*ox)*oy; g = (p0[4]*xr + p1[4]*ox)*yr + (p2[4]*xr + p3[4]*ox)*oy; b = (p0[5]*xr + p1[5]*ox)*yr + (p2[5]*xr + p3[5]*ox)*oy; fput(x+ax,y+ay,r,g,b) end;end end -- eof backdrop -- -- SPLINES -- -- function db.splinePoint(x0,y0,x1,y1,x2,y2,points,point) local x,y,sx1,sy1,sx2,sy2,f f = point * 1 / points sx1 = x0*(1-f) + x1*f sy1 = y0*(1-f) + y1*f sx2 = x1*(1-f) + x2*f sy2 = y1*(1-f) + y2*f x = sx1 * (1-f) + sx2 * f y = sy1 * (1-f) + sy2 * f return x,y end -- -- zx = 2*x1 - (x0+x2)/2 -- function db.drawSplineSegment(x0,y0,x1,y1,x2,y2,x3,y3,points,col) -- Does spline segment p1-p2 local n,x,y,sx1,sy1,sx2,sy2,mid,zx1,zy1,zx2,zy2,fx,fy mid = math.floor(points / 2) -- Extended Bezier points zx1 = 2*x1 - (x0+x2)/2 zy1 = 2*y1 - (y0+y2)/2 zx2 = 2*x2 - (x1+x3)/2 zy2 = 2*y2 - (y1+y3)/2 fx,fy = x1,y1 -- Segment to be drawn (0),1 - 2,(3) for n = 0, mid, 1 do f = n * 1 / points * 2 sx1,sy1 = db.splinePoint(x0,y0,zx1,zy1,x2,y2,mid*2, mid + n) sx2,sy2 = db.splinePoint(x1,y1,zx2,zy2,x3,y3,mid*2, n) x = sx1 * (1-f) + sx2 * f y = sy1 * (1-f) + sy2 * f --putpicturepixel(x,y,col) db.line(fx,fy,x,y,col) fx,fy = x,y end end -- -- eof Splines