Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. --DawnBringer function library v1.14 (some drawing replaced)
  2. --** THIS IS NOT A RUNNABLE SCRIPT! **
  3. --by Richard Fhager
  4. -- http://hem.fyristorg.com/dawnbringer/
  5. -- Email: dawnbringer@hem.utfors.se
  6. -- MSN:   annassar@hotmail.com
  7. --
  8. -- Many functions in here was adopted from Evalion, a Javascript codecrafting/imageprocessing project
  9. -- http://goto.glocalnet.net/richard_fhager/evalion/evalion.html
  10. --
  11. --
  12. -- You may access these functions in your own scripts by loading this library,
  13. -- just add the follwing line as one of the first instructions:
  14. --
  15. -- run("dawnbringer_lib")
  16. --
  17. -- or
  18. --
  19. -- run("../libs/dawnbringer_lib.lua")
  20. --
  21. --
  22. -- Note that the functions must be called with the full library object-name, "db.function_name..."
  23. --
  24.  
  25. -- Global library object
  26. db = {}
  27.  
  28.  
  29. -- *************************************
  30. -- ***      Text & Conversions       ***
  31. -- *************************************
  32. --
  33. --
  34.  
  35. function db.rgb2HEX(r,g,b,prefix)
  36.   local c,n,s,t,z
  37.   c = {r,g,b}
  38.   z = {"0",""}
  39.   t = ""
  40.   for n = 1, 3, 1 do
  41.   s = string.upper(string.format("%x",c[n]))
  42.   t = t..z[#s]..s
  43.      --s = tonumber(c[n],16)
  44.      --t = t..s
  45.   end
  46.   return prefix..t
  47. end
  48.  
  49.  
  50. --
  51. -- ... eof Text & Conversions ...
  52. --
  53.  
  54.  
  55.  
  56. -- *************************************
  57. -- ***    Custom Math Functions     ***
  58. -- *************************************
  59. --
  60. --
  61. function db.sign(v)
  62.     local s
  63.     s = 0
  64.     if v > 0 then s = 1; end
  65.     if v < 0 then s = -1; end
  66.     return s
  67. end
  68. --
  69.  
  70. function db.distance(ax,ay,bx,by) return math.sqrt((ax-bx)^2 + (ay-by)^2); end
  71.  
  72. --
  73. function db.rotation (rot_ang,hub_x,hub_y,x,y) -- Rotate coordinates x & y relative hub
  74.   local new_ang,dist,m,xd,yd,v; m = math
  75.   xd=hub_x-x;
  76.   yd=hub_y-y;
  77.   if (not(xd==0 and yd==0)) then
  78.    v = -90; if xd < 0 then v = 90; end
  79.    new_ang = m.atan(yd/xd) - (v+rot_ang) * m.pi/180;
  80.    dist = m.sqrt(xd*xd+yd*yd);
  81.    x = hub_x - m.sin(new_ang)*dist;
  82.    y = hub_y + m.cos(new_ang)*dist;
  83.   end
  84.   return math.floor(x),math.floor(y) -- For drawing purposes
  85. end
  86. --
  87.  
  88. --
  89. function db.recursiveSum(div,rlev) -- divisons per recursion,recursion levels
  90.  local s,i,m
  91.  s,m = 1,1
  92.  for i = 1, rlev, 1 do
  93.   m = m*div
  94.   s = s + m
  95.  end
  96.  return s
  97. end
  98. --
  99.  
  100. --
  101. -- ... eof Custom Math Functions ...
  102. --
  103.  
  104. -- *************************************
  105. -- ***     Fractional Scenery        ***
  106. -- *************************************
  107.  
  108. --
  109. function db.setSceneryPalette()
  110.  db.colorCigarr(10,28,true) -- 250 colors
  111.  setcolor(250, 208,48,48)
  112.  setcolor(251, 48,208,48)
  113.  setcolor(252, 48,48,208)
  114.  setcolor(253, 224,224,64)
  115.  setcolor(254, 224,64,224)
  116.  setcolor(255, 64,224,224)
  117. end
  118. --
  119.  
  120. --
  121. function db.star(xf,yf,sx,sy,rgb,haz,out,lum)
  122.  local n,c,dist; c={}
  123.  dist = haz + out * math.sqrt((xf-sx)^2+(yf-sy)^2);
  124.  for n = 1, 3, 1 do c[n] = (rgb[n] * lum) / dist; end
  125.  return c;
  126. end
  127. --
  128.  
  129. --
  130. function db.zoom(xf,yf,zoom,panx,pany) -- Zoom and Pan in a fractional coord-system
  131.   xf = (xf-0.5)/zoom + 0.5 + panx;
  132.   yf = (yf-0.5)/zoom + 0.5 + pany;
  133.   return xf,yf
  134. end
  135. --
  136.  
  137. --
  138. function db.rotationFrac(rot_ang,hub_x,hub_y,x,y) -- Rotate coordinates x & y relative hub
  139.   local new_ang,dist,m,xd,yd,v; m = math
  140.   xd=hub_x-x;
  141.   yd=hub_y-y;
  142.   if (not(xd==0 and yd==0)) then
  143.    v = -90; if xd < 0 then v = 90; end
  144.    new_ang = m.atan(yd/xd) - (v+rot_ang) * m.pi/180;
  145.    dist = m.sqrt(xd*xd+yd*yd);
  146.    x = hub_x - m.sin(new_ang)*dist;
  147.    y = hub_y + m.cos(new_ang)*dist;
  148.   end
  149.   return x,y
  150. end
  151. --
  152.  
  153. --
  154. function db.twirl(x,y,arms,trot,tpow,tang)
  155.  local b,ang,vx,vy,vr,m,deg,tw
  156.  m=math; deg=math.pi/180; tw=.5;
  157.  if (not(x==.5 and y==.5)) then
  158.   ang = m.atan((.5-y)/(.5-x));
  159.   b = 0; if (x>.5) then b = m.pi; end
  160.   vx = .5-x; vy = .5-y; vr = m.pow(m.sqrt(vx*vx+vy*vy),tpow);
  161.   tw = .5+m.sin(-tang*deg+vr*trot+(ang + b)*arms)*.5;
  162.  end
  163.  return tw;
  164. end
  165. --
  166.  
  167. --- Alpha filters
  168. --
  169. function db.alpha1(x,y,amp) -- Coord, Amplify: 0..n
  170.  local p,a,xh,yh,m
  171.  xh=0.5-x; yh=0.5-y; m = math
  172.  p = m.pow(xh*xh+yh*yh,0.7);
  173.  a = m.cos(32*m.pi*p)*m.sin(8*m.pi*(xh+yh));
  174.  return 1 + (a * amp)
  175. end
  176. --
  177.  
  178. --
  179. -- ... eof Fractional Scenery ...
  180. --
  181.  
  182. -- *************************************
  183. -- ***    Custom Array Functions     ***
  184. -- *************************************
  185. --
  186. -- Ok, I don't know Lua that well (still unsure about some scopes & refs etc.)
  187. -- And some features may not be active in Grafx2. So, some of the follwing functions
  188. -- may exist in Lua/Grafx2...but since I'm not sure if and how they work - I'll prefer
  189. -- to add a set of my own of known performance.
  190.  
  191. --
  192. function db.newArrayInit(xs,val)
  193.   local x,ary; ary = {}
  194.    for x = 1, xs, 1 do
  195.      ary[x] = val
  196.    end
  197.   return ary
  198. end
  199. --
  200.  
  201.  
  202. --
  203. function db.newArrayInit2Dim(xs,ys,val)
  204.   local x,y,ary; ary = {}
  205.   for y = 1, ys, 1 do
  206.    ary[y] = {}
  207.    for x = 1, xs, 1 do
  208.      ary[y][x] = val
  209.    end
  210.   end
  211.   return ary
  212. end
  213. --
  214.  
  215. --
  216. -- Merge two arrays into a NEW one: array_c = db.newArrayMerge(array_b,array_b)
  217. --
  218. function db.newArrayMerge(a,b)
  219.   local n,ary; ary = {}
  220.   for n = 1, #a, 1 do
  221.    ary[n] = a[n]
  222.   end
  223.   for n = 1, #b, 1 do
  224.    ary[n+#a] = b[n]
  225.   end
  226.   return ary
  227. end
  228. --
  229.  
  230. --
  231. -- Generate a copy of an array with a new value added Last
  232. --
  233. function db.newArrayInsertLast(a,val)
  234.   local n,ary; ary = {}
  235.   for n = 1, #a, 1 do
  236.    ary[n] = a[n]
  237.   end
  238.   ary[#a+1] = val
  239.   return ary
  240. end
  241. --
  242.  
  243. --
  244. -- Generate a copy of an array with a new value added First
  245. --
  246. function db.newArrayInsertFirst(a,val)
  247.   local n,ary; ary = {}
  248.   ary[1] = val
  249.   for n = 2, #a+1, 1 do
  250.    ary[n] = a[n-1]
  251.   end
  252.   return ary
  253. end
  254. --
  255.  
  256. --
  257. function db.ary2txt(ary) -- One & two dimensions supported [a,b] -> "a,b". [[a,b],[c,d]] -> "a-b, c-d"
  258.  local t,n,m,v
  259.  t = ""
  260.  for n = 1, #ary, 1 do
  261.    if type(ary[n]) == "table" then
  262.      t = t..ary[n][1]
  263.      for m = 2, #ary[n], 1 do
  264.        t = t.."-"..ary[n][m]
  265.      end
  266.    else t = t..ary[n];
  267.    end
  268.    t = t..", "
  269.  end
  270.  return t
  271. end
  272. --
  273.  
  274. --
  275. -- *** Array data manipulation ***
  276. --
  277.  
  278. --
  279. -- InsertionSort Array, this is chaos...I'm confused and stomped...don't understand how Lua works...
  280. -- ...sorting seem be to ok but this code is ugly...
  281. -- Sort LO-HI
  282. --
  283. -- Screwed up or confused thing here I think, perhaps lo-hi/hi-lo. This is working lo-hi but the code
  284. -- looks like hi-lo...edit this some day
  285. --
  286. function db.sorti(d,idx)
  287.    local a,j,tmp,l,e
  288.    l = #d
  289.  
  290.    for a=2, l, 1 do
  291.     tmp = d[a];
  292.     e = a
  293.     for j=a, 2, -1 do  
  294.       e = j
  295.       if d[j-1][idx] > tmp[idx] then d[j] = d[j-1]; e = j-1; else break; end;
  296.     end;
  297.     d[e] = tmp; -- WHY THE F**K CAN'T YOU READ j HERE!?! STUPID ASSUCKING LANGUAGE
  298.      
  299.    end;
  300.    --return d
  301. end
  302. --
  303.  
  304. --
  305. function db.shuffle(list)
  306.  local i,n,t
  307.  for n = #list, 2, -1 do
  308.   i = 1+math.floor(math.random() * n)
  309.   t = list[n]; list[n] = list[i]; list[i] = t
  310.  end
  311. end
  312. --
  313.  
  314. --
  315. -- ... eof Custom Array Functions ...
  316. --
  317.  
  318.  
  319. -- *************************************
  320. -- ***   Misc. Logical Operations    ***
  321. -- *************************************
  322.  
  323. --
  324. -- palList [r,g,b,palindex] is expected only to contain unique colors
  325. -- index = -1 --> index of list
  326. --
  327. function db.makeIndexList(list,index)
  328.   local n,ilist
  329.   ilist = {}
  330.   for n = 1, #list, 1 do
  331.    if (index > 0) then ilist[n] = list[n][index]; end
  332.    if (index == -1) then ilist[n] = n; end
  333.   end
  334.   return ilist
  335. end
  336. --
  337.  
  338. --
  339. -- Return a list of all possible (non-same) pairs from the entries in a list
  340. -- [a,b,c] --> [[a,b],[a,c],[b,c]]
  341. -- (All entries are treated as unique. i.e it's only the INDEX that counts)
  342. -- mode = 0: Only unique pairs (m = (n^2 - n)/2), [a,b] --> [[a,b]]
  343. -- mode = 1: All pairs, i.e mirror versions as well. (m = n^2 - n), [a,b] --> [[a,b], [b,a]]
  344. --
  345. function db.pairsFromList(list,mode)
  346.  local a,b,l,n,pairs
  347.  pairs = {}
  348.  l = #list
  349.  n = 1
  350.  for a = 1, l, 1 do
  351.    for b = a+1, l, 1 do
  352.      pairs[n] = {list[a],list[b]}; n = n + 1
  353.      if mode == 1 then pairs[n] = {list[b],list[a]}; n = n + 1; end
  354.    end
  355.  end
  356.  return pairs
  357. end
  358. --
  359.  
  360. function db.valueInArray(ary,val)
  361.  local n,res
  362.  res = false
  363.  for n = 1, #ary, 1 do
  364.    if ary[n] == val then res = true; break; end
  365.  end
  366.  return res
  367. end
  368.  
  369. -- RAMP specific
  370.  
  371. -- Remove initial pair (palList) colors from pallist
  372. function db.initiateRamp(pair,pallist,pal_index)
  373.   local n,found,plist
  374.   plist = {}
  375.  
  376.   found = 1
  377.   for n = 1, #pallist, 1 do
  378.    if db.valueInArray(pair,pallist[n]) == false then
  379.     plist[found] = pallist[n]; found = found + 1;
  380.    end
  381.   end
  382.  
  383.   pair[pal_index] = plist -- ex: ["pal"]
  384.  
  385.   return pair -- Is now a 2 color RAMP
  386. end
  387. --
  388.  
  389. -- Remove new col entry from ramp's pallist and add it to the ramp, returns an updated ramp
  390. -- RampList = [1,2] ["pal"] = palList = [3,4,5], addindex = 3
  391. -- --> [1,2,3] palList = [4,5]
  392. function db.updateRamp(ramp,addindex,pal_index)
  393.   local n,found,pallist,plist,newramp
  394.   plist = {}
  395.   pallist = ramp[pal_index]
  396.  
  397.   -- New palList without added color to IndexList
  398.   found = 1
  399.   for n = 1, #pallist, 1 do
  400.    if pallist[n] ~= addindex then
  401.     plist[found] = pallist[n]; found = found + 1;
  402.    end
  403.   end
  404.  
  405.   newramp = db.newArrayInsertLast(ramplist,addindex)
  406.   newramp[pal_index] = plist
  407.  
  408.   return rlist
  409. end
  410.  
  411. --
  412. -- Returns a list of all inital ramps from color pairs
  413. --
  414. -- Weeds out bad pairs, attaches remaining palette colors and the first rgb-vector
  415. --
  416. --
  417. function db.initiateRampList(pairs,pallist,pal_index,vec_index,min,maxmult,rw,gw,bw)
  418.  local n,ramplist,newpairs,accept,dist,c1,c2,max,rD,gD,bD
  419.  ramplist = {}
  420.  max = min + (142 / math.sqrt(#pallist)) * maxmult -- min ex: 8-12
  421.  accept = 0
  422.  
  423.  for n = 1, #pairs, 1 do
  424.   c1 = pallist[pairs[n][1]]
  425.   c2 = pallist[pairs[n][2]]
  426.   rD = c2[1] - c1[1]
  427.   gD = c2[2] - c1[2]
  428.   bD = c2[3] - c1[3]
  429.   dist = math.sqrt( (rw*rD)^2 + (gw*gD)^2 + (bw*bD)^2 )
  430.  
  431.   if dist >= min and dist <= max then
  432.     accept = accept + 1; ramplist[accept] = db.initiateRamp(pairs[n],pallist,pal_index);
  433.     ramplist[accept][vec_index] = {rD, gD, bD, dist}; -- Add first color vector, ONLY KEEP DISTANCE?
  434.   end
  435.  end
  436.  
  437.  return ramplist
  438. end
  439.  
  440.  
  441. function db.findRampExpansionColors(ramp)
  442.  local clist
  443.  clist = {}
  444.  -- Calculate vectors here?
  445.  return clist
  446. end
  447.  
  448.  
  449. function db.findRAMPS(min_len, max_len)
  450.  local i,n,c,pallist,ramp,ramplist,pairs,spairs,palindex,vecindex,found,donelist,newlist,dones
  451.  local colorlist
  452.  palindex = "pal"
  453.  vecindex = "vector"
  454.  pallist =  db.fixPalette(db.makePalList(256), 0)
  455.  pairs =    db.pairsFromList(db.makeIndexList(pallist,-1), 0)
  456.  ramplist = db.initiateRampList(pairs,pallist,palindex,vecindex, 8,0.75, 0.26,0.55,0.19)
  457.  
  458.  -- MIN_LEN = 5
  459.  -- MAX_LEN = 10
  460.  
  461.  -- Split Ramp-build into two parts:
  462.  -- 1. Build ramps >= MIN_LEN, NONE added to 'Done'
  463.  -- 2. Run til no more ramps can be expanded or reaches MAX_LEN, ALL ramps added to 'Done'
  464.  
  465.  for i = 1, (min_len - 2), 1 do -- Assuming 2 for inital pairs (2 color ramps)
  466.   newlist = {}
  467.   found = 0
  468.   for n = 1, #ramplist, 1 do
  469.     ramp = ramplist[n]
  470.     colorlist = db.findRampExpansionColors(ramp) -- Colors that can split the current ramp into new expanded ramps
  471.     for c = 1, #colorlist, 1 do
  472.      found = found + 1; newlist[found] = db.updateRamp(ramp,colorlist[c],palindex); -- Ramp is expanded by 1 color
  473.     end
  474.   end
  475.   ramplist = newlist
  476.  end
  477.  
  478.  
  479.  donelist = {}; dones = 0
  480.  
  481.  repeat
  482.   newlist = {}
  483.   found = 0
  484.   for n = 1, #ramplist, 1 do
  485.     ramp = ramplist[n]
  486.     if true == false then
  487.      found = found + 1; newlist[found] = db.updateRamp(ramp,color,palindex);
  488.       else
  489.       dones = dones + 1; donelist[dones] = ramp;
  490.     end
  491.   end
  492.   --ramplist = newlist
  493.  until found == 0
  494.  
  495.  return #pairs.." - "..#ramplist
  496. end
  497.  
  498. --
  499. -- ... eof Misc. Logical Operations ...
  500. --
  501.  
  502.  
  503. -- ***************************************
  504. -- *** General RGB-Color Modifications ***
  505. -- ***************************************
  506.  
  507.  
  508. --
  509. function db.makeComplementaryColor(r,g,b,brikeeplev) -- Lev: 0 = Normal, 1 = Loose, 2 = Strict
  510.  
  511.  local bri_o,bri_n,bdiff
  512.  
  513.  function cap(v) return math.max(0,math.min(v,255)); end
  514.  
  515.  bri_o = db.getBrightness(r,g,b)
  516.  r,g,b = db.shiftHUE(r,g,b,180)
  517.  
  518.  if brikeeplev > 0 then
  519.  
  520.   for n = 0, brikeeplev*3-1, 1 do -- Must iterate to reduce brightness error
  521.     bri_n = db.getBrightness(r,g,b)
  522.     bdiff = (bri_o - bri_n) / 2 * brikeeplev
  523.     r = cap(r + bdiff)
  524.     g = cap(g + bdiff)
  525.     b = cap(b + bdiff)
  526.   end
  527.  
  528.  end
  529.  
  530.  return r,g,b
  531.  
  532. end
  533. --
  534.  
  535.  
  536. -- *** Color balance ***
  537. --
  538. -- bri_flag:   Preserve brightness
  539. -- loose_flag: Loose preservation restrictions for brightness and balance
  540. --
  541. -- Jeez, was this a tricky sucker; color-balance is just adding and capping...
  542. -- but trying color-balance with preserved perceptual brightness is a different monster...
  543. -- ...so bad I could only solve it by iterative error correction.
  544. --
  545. function db.ColorBalance(r,g,b,rd,gd,bd,bri_flag,loose_flag) -- preserve brightness
  546.  local rw,gw,bw,ri,gi,bi,itot,rni,gni,bni,ro,go,bo,ovscale,lev,count,rt,gt,bt,rf,gf,bf,bri
  547.  
  548.  -- Dawn 3.0, [0.26,0.55,0.19], 0-255 bri-colorscale adjust = 1.56905
  549.  rw,gw,bw = 0.26, 0.55, 0.19
  550.  
  551.  function cap(v) return math.min(255,math.max(v,0)); end
  552.  
  553.   bri = db.getBrightness(r,g,b)
  554.  
  555.  
  556.  -- Loose brightness & balance preservation, a good compromise.
  557.  if bri_flag == true and loose_flag == true then
  558.  
  559.    lev = (rd + gd + bd) / 3
  560.    rd = rd - lev
  561.    gd = gd - lev
  562.    bd = bd - lev
  563.  
  564.    brin = db.getBrightness(cap(r+rd),cap(g+gd),cap(b+bd))
  565.    itot = brin - bri
  566.    rd = rd - itot
  567.    gd = gd - itot
  568.    bd = bd - itot
  569.  
  570.  end
  571.  
  572.  
  573.  if bri_flag == true and loose_flag == false then
  574.  
  575.   itot = 255
  576.   count = 0
  577.  
  578.    -- Normalize (Yup, it's right only to normalize once first..cont.norm. will have some counter-effect)
  579.   lev = (rd + gd + bd) / 3
  580.   rd = rd - lev
  581.   gd = gd - lev
  582.   bd = bd - lev
  583.  
  584.  repeat
  585.  
  586.   --messagebox("Norm:"..rd..", "..gd..", "..bd)
  587.  
  588.   -- Calculate total brightness change
  589.   -- Note: Perceptual Brightness is exponential, and can't be delta-adjusted for anything other than greyscales.
  590.   -- Although the formula for the new brightness corrected normalization level can can be derived...
  591.   -- ...it doesn't do much good since the bigger problem is overflow outside the 0-255 boundary.
  592.   -- As for now, I see no other means to solve this issue than with iterative error-correction.
  593.  
  594.   rt = r+rd
  595.   gt = g+gd
  596.   bt = b+bd
  597.  
  598.   itot = 9e99
  599.   rni = rd
  600.   gni = gd
  601.   bni = bd
  602.  
  603.   -- We can get brightness of negative values etc. So bri-correction is put on hold until values are scaled down
  604.   if (rt>=0 and gt>=0 and bt>=0) and (rt<256 and gt<256 and bt<256) then
  605.     brin = db.getBrightness(rt,gt,bt)
  606.     itot = brin - bri
  607.     --messagebox("Bri Diff: "..itot)
  608.     -- Brightness adjusted balance
  609.      rni = rd - itot
  610.      gni = gd - itot
  611.      bni = bd - itot
  612.   end
  613.  
  614.   --messagebox("Bri Adj Bal:"..rni..", "..gni..", "..bni)
  615.  
  616.    -- Apply balance to find overflow (as fraction of the channel change)
  617.    ro = math.max( math.max((r + rni)-255,0), math.abs(math.min((r + rni),0)) ) / math.max(math.abs(rni),1)
  618.    go = math.max( math.max((g + gni)-255,0), math.abs(math.min((g + gni),0)) ) / math.max(math.abs(gni),1)
  619.    bo = math.max( math.max((b + bni)-255,0), math.abs(math.min((b + bni),0)) ) / math.max(math.abs(bni),1)
  620.  
  621.   ovscale = 1 - math.max(ro,go,bo)
  622.  
  623.   -- Scaling balances might be logically incorrect (as they can be seen as constant differences)
  624.   -- But scaling DOWN is quite harmless and I don't see how it could be done otherwise...
  625.   -- ex: +10 red, +5 blue: Scale x2   = +20 red, +10 blue -> More red over blue than ordered, a contrast behaviour.
  626.   --     +10 red, +5 blue: Scale x0.5 = +5 red, +2.5 blue -> Less of everything, but a part of the order. Harmless?
  627.   --
  628.   rd = rni * ovscale
  629.   gd = gni * ovscale  
  630.   bd = bni * ovscale
  631.  
  632.   count = count + 1
  633.  
  634.   --messagebox("Final bal:"..rd..", "..gd..", "..bd)
  635.  
  636.  until math.abs(itot) < 1 or count > 5
  637.  
  638.  end
  639.  
  640.  rf = r + rd
  641.  gf = g + gd
  642.  bf = b + bd
  643.  
  644.  --messagebox("Result color:"..rf..", "..gf..", "..bf)
  645.  
  646.  return rf,gf,bf
  647. end
  648. --
  649.  
  650.  
  651.  
  652. --
  653. -- bri_flag: Preserve brightness
  654. -- cap_flag: Cap new color at 0-255, has a desaturating effect for large values.
  655. --
  656. function db.ColorBalanceXXX(r,g,b,rd,gd,bd,bri_flag,cap_flag) -- preserve brightness
  657.  local rf,gf,bf
  658.  
  659.  if cap_flag == true then
  660.   rd = math.min(255,math.max(0, r+rd)) - r
  661.   gd = math.min(255,math.max(0, g+gd)) - g
  662.   bd = math.min(255,math.max(0, b+bd)) - b
  663.  end
  664.  
  665.  local rw,gw,bw,ri,gi,bi,itot,rni,gni,bni,ro,go,bo,ovscale
  666.  
  667.  
  668.  -- Dawn 3.0, [0.26,0.55,0.19], 0-255 bri-colorscale adjust = 1.56905
  669.  rw,gw,bw = 0.26, 0.55, 0.19
  670.  
  671.  if bri_flag == true then
  672.  
  673.   -- Calculate total brightness change
  674.   --ri = rd * rw
  675.   --gi = gd * gw
  676.   --bi = bd * bw
  677.   --itot = math.sqrt(ri^2 + gi^2 + bi^2)
  678.  
  679.   bri  = db.getBrightness(r,g,b)
  680.   brin = db.getBrightness(r+rd,g+gd,b+bd)
  681.   itot = brin - bri
  682.  
  683.  
  684.   -- Normalized and Brightness adjusted balance
  685.   rni = rd - itot
  686.   gni = gd - itot
  687.   bni = bd - itot
  688.  
  689.   -- Apply balance to find overflow (as fraction of the channel change)
  690.   ro = math.max( math.max((r + rni)-255,0), math.abs(math.min((r + rni),0)) ) / math.max(math.abs(rni),1)
  691.   go = math.max( math.max((g + gni)-255,0), math.abs(math.min((g + gni),0)) ) / math.max(math.abs(gni),1)
  692.   bo = math.max( math.max((b + bni)-255,0), math.abs(math.min((b + bni),0)) ) / math.max(math.abs(bni),1)
  693.  
  694.   ovscale = 1 - math.max(ro,go,bo)
  695.  
  696.   rd = rni * ovscale
  697.   gd = gni * ovscale  
  698.   bd = bni * ovscale
  699.  
  700.  end
  701.  
  702.  rf = r + rd
  703.  gf = g + gd
  704.  bf = b + bd
  705.  
  706.  return rf,gf,bf
  707. end
  708. --
  709.  
  710. --
  711. function db.getContrast(ch) -- Channel, returns fraction -1..0..1, negative for ch < 127.5
  712.  --return math.abs((ch / 127.5) - 1)
  713.  return (ch / 127.5) - 1
  714. end
  715. --
  716.  
  717. --
  718. function db.getAvgContrast(r,g,b)
  719.  return (math.abs(db.getContrast(r)) + math.abs(db.getContrast(g)) + math.abs(db.getContrast(b))) / 3  
  720. end
  721. --
  722.  
  723. --
  724. -- Mode = 0: Proportional - all colors reach max contrast at 100%
  725. --
  726. -- Mode = 1: Linear - percentage simply added
  727. --
  728. function db.changeContrastOLD(r,g,b,prc,mode)
  729.  
  730.  local m,rd,gd,bd,rv,gv,bv,rc,gc,bc,base,sign
  731.  
  732.  base = 1; sign = 1
  733.  if prc < 0 then base = 0; sign = -1; end -- decontrast
  734.  
  735.  m = prc / 100 * sign
  736.  
  737.  -- mode 0
  738.  rc = db.getContrast(r)
  739.  rd = (base - math.abs(rc)) * m  * db.sign(rc)
  740.  rv = (rc+rd+1) * 127.5
  741.  
  742.  gc = db.getContrast(g)
  743.  gd = (base - math.abs(gc)) * m  * db.sign(gc)
  744.  gv = (gc+gd+1) * 127.5
  745.  
  746.  bc = db.getContrast(b)
  747.  bd = (base - math.abs(bc)) * m  * db.sign(bc)
  748.  bv = (bc+bd+1) * 127.5
  749.  
  750.  return rv,gv,bv
  751.  
  752. end
  753. --
  754.  
  755. function db.changeContrast(r,g,b,prc) -- Photoshop style
  756.  
  757.  local m,rd,gd,bd,rv,gv,bv,rc,gc,bc
  758.  
  759.  m = 1 + math.pow((255 / 100 * prc),3) / (255*255)
  760.  
  761.  -- decontrast
  762.  if prc < 0 then
  763.   m = 1 - math.abs(prc)/100
  764.  end
  765.  
  766.  rc = db.getContrast(r)
  767.  rd = rc * m
  768.  rv = (rd+1) * 127.5
  769.  
  770.  gc = db.getContrast(g)
  771.  gd = gc * m
  772.  gv = (gd+1) * 127.5
  773.  
  774.  bc = db.getContrast(b)
  775.  bd = bc * m
  776.  bv = (bd+1) * 127.5
  777.  
  778.  return rv,gv,bv
  779.  
  780. end
  781.  
  782.  
  783.  
  784. --
  785. function db.getBrightness(r,g,b) -- 0-255
  786.  local bri
  787.  --bri = (r+g+b)/3
  788.  --bri = r*0.3 + g*0.59 + b*0.11 -- Luma Y'601
  789.  --bri = math.sqrt((r*0.3)^2 + (g*0.59)^2 + (b*0.11)^2) -- Luma Y'601
  790.  --bri = r*0.245 + g*0.575 + b*0.18 -- Dawn 2.0
  791.  
  792.  bri = math.sqrt((r*0.26)^2 + (g*0.55)^2 + (b*0.19)^2) * 1.56905 -- Dawn 3.0
  793.  return bri  
  794. end
  795. --
  796.  
  797.  
  798. --
  799. -- Note on desaturation: These functions are all junk, the only way to desaturate
  800. --                       is to fade a color into it's corresponding greyscale.
  801. --
  802.  
  803. --
  804. function db.desaturate(percent,r,g,b) -- V1.0 by Richard Fhager
  805.  local a,p
  806.  p = percent / 100
  807.  a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 * p
  808.  r = r + (a-r*p) -- a+r*(1-p)
  809.  g = g + (a-g*p)
  810.  b = b + (a-b*p)
  811.  return r,g,b
  812. end
  813. --
  814.  
  815. --
  816. function db.desaturateA(percent,c) -- array version
  817.  local r,g,b,a
  818.  r = c[1]
  819.  g = c[2]
  820.  b = c[3]
  821.  p = percent / 100
  822.  a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 * p
  823.  r = r + (a-r*p)
  824.  g = g + (a-g*p)
  825.  b = b + (a-b*p)
  826.  return {r,g,b}
  827. end
  828. --
  829.  
  830. --
  831. function db.desatAVG(desat,c) -- Desaturation, simpe average
  832.  r = c[1]
  833.  g = c[2]
  834.  b = c[3]
  835.  p = desat / 100
  836.  a = (r+g+b)/3
  837.  r = r + p*(a-r)
  838.  g = g + p*(a-g)
  839.  b = b + p*(a-b)
  840.  return {r,g,b}
  841. end
  842. --
  843.  
  844.  
  845. --
  846. function db.getSaturation(r,g,b) -- HSL
  847.   local M,m,c,s,l
  848.   M = math.max(r,g,b)
  849.   m = math.min(r,g,b)
  850.   c = (M - m)/255
  851.   s = 0
  852.   if c ~= 0 then
  853.     --l = (0.3*r + 0.59*g + 0.11*b)/255 -- HSLuma: Y'601
  854.     l = (M+m)/510 -- This produces a quite "correct looking" divison of saturation
  855.     if l <= 0.5 then s = c / (2*l); end
  856.     if l  > 0.5 then s = c / (2-2*l); end
  857.   end
  858.   return math.min(255,s * 255)
  859. end
  860. --
  861.  
  862. --
  863. function db.getTrueSaturationX(r,g,b) -- Distance from grayscale axis. Not HSV/HSL
  864.  local sat,bri
  865.  bri = (r+g+b) / 3
  866.  sat = math.min(255, math.sqrt((r-bri)^2 + (g-bri)^2 + (b-bri)^2) * 1.224744875)
  867.  return sat
  868. end
  869. --
  870.  
  871. -- WIP. Trying to find a more natural model for estimating Saturation
  872. -- Current: (HSL + True) / 2
  873. function db.getAppSaturation(r,g,b)
  874.   return  math.min(255, (db.getSaturation(r,g,b) + db.getTrueSaturationX(r,g,b)) / 2)
  875. end
  876. --
  877.  
  878. --
  879. function db.saturate(percent,r,g,b)
  880.   local a,m,p,mc
  881.   a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5
  882.   m = math.min(255-math.max(r,g,b), math.min(r,g,b))
  883.   p = percent * (m / 100)
  884.   mc = math.max((r-a),(g-a),(b-a)) -- Can this be derived elsewhere?
  885.   if mc ~= 0 then
  886.    r = r + (r-a) * p / mc
  887.    g = g + (g-a) * p / mc
  888.    b = b + (b-a) * p / mc
  889.   end
  890.   return r,g,b
  891. end
  892. --
  893.  
  894. --
  895. -- Super Saturate: Better than Photoshop etc.
  896. --
  897. -- Higher than 100% power is ok
  898. --
  899. function db.saturateAdv(percent,r,g,b,brikeeplev,greydamp) -- brikeep = 0 - 2
  900.   local a,m,p,mc,bri_o,bri_n,bdiff,mx,mi,adj,q,n
  901.    function cap(v) return math.max(0,math.min(v,255)); end
  902.   mx = math.max(r,g,b)
  903.   mi = math.min(r,g,b)
  904.   bri_o = db.getBrightness(r,g,b)
  905.   a = (math.min(mx,255) + math.max(mi,0)) * 0.5
  906.   m = math.min(255-mx, mi)
  907.   p = percent * (m / 100)
  908.   mc = math.max((r-a),(g-a),(b-a)) -- Can this be derived elsewhere?
  909.   if mc ~= 0 and m ~= 0 then
  910.    adj = math.min(1,(mx - mi) / m) -- Reduce effect on low saturation
  911.    if greydamp == false then adj = 1; end
  912.    q = p / mc * adj
  913.    r = cap( r + (r-a) * q )
  914.    g = cap( g + (g-a) * q )
  915.    b = cap( b + (b-a) * q )
  916.   end
  917.   for n = 0, brikeeplev*2, 1 do -- Must iterate to reduce brightness error
  918.     bri_n = db.getBrightness(r,g,b)
  919.     bdiff = (bri_o - bri_n) / 2 * brikeeplev
  920.     r = cap(r + bdiff)
  921.     g = cap(g + bdiff)
  922.     b = cap(b + bdiff)
  923.   end
  924.   return r,g,b
  925. end
  926. --
  927.  
  928.  
  929. --
  930. -- Lightness: Darken / Brighten color (Argument and returnvalue is a rgb-list)
  931. --            Rate of change is inversely proportional to the distance of the max/min.
  932. --            i.e. all colors/channels will reach max/min at the same time (at 0 or 100 %)
  933. --            (As opposed to 'Brightness' where all channels are changed by a constant value)
  934. --
  935. function db.lightness(percent,c)
  936.  local v,r,g,b,p
  937.  r = c[1]
  938.  g = c[2]
  939.  b = c[3]
  940.  p = math.abs(percent/100)
  941.  v = 255
  942.  if percent < 0 then v = 0; end
  943.  r = r + (v - r)*p
  944.  g = g + (v - g)*p
  945.  b = b + (v - b)*p
  946.  return {r,g,b}
  947. end
  948. --
  949.  
  950. --
  951. function db.changeLightness(r,g,b,percent)
  952.  local v
  953.  v = db.lightness(percent,{r,g,b})
  954.  return v[1],v[2],v[3]
  955. end
  956. --
  957.  
  958. --
  959. function db.getLightness(r,g,b) -- HSL bi-hexcone
  960.   return (math.max(r,g,b) + math.min(r,g,b)) / 2
  961. end
  962. --
  963.  
  964. --
  965. function db.shiftHUE(r,g,b,deg) -- V1.3 R.Fhager 2007, (Heavily derived code, hehe...)
  966.  local c,h,mi,mx,d,s,p,i,f,q,t
  967.  c = {g,b,r}
  968.  mi = math.min(r,g,b)
  969.  mx = math.max(r,g,b); v = mx;
  970.  d = mx - mi;
  971.  s = 0; if mx ~= 0 then s = d/mx; end
  972.  p = 1; if g ~= mx then p = 2; if b ~= mx then p = 0; end; end
  973.  
  974.  if s~=0 then
  975.   h=(deg/60+(6+p*2+(c[1+p]-c[1+(p+1)%3])/d))%6;
  976.   i=math.floor(h);
  977.   f=h-i;
  978.   p=v*(1-s);
  979.   q=v*(1-s*f);
  980.   t=v*(1-s*(1-f));
  981.   c={v,q,p,p,t,v}
  982.   r = c[1+i]
  983.   g = c[1+(i+4)%6]
  984.   b = c[1+(i+2)%6]
  985.  end
  986.  
  987.  return r,g,b
  988. end
  989. --
  990.  
  991. --
  992. function db.getHUE(r,g,b,greytol) -- 0-6 (6.5 = Greyscale), mult. with 60 for degrees
  993.  -- 1 Color diff is roughly detected by Tolerance = 0.0078125 (Tol. incr. with lightness etc.)
  994.  local c,h,mi,mx,d,s,p,i,f,q,t
  995.  c = {g,b,r}
  996.  mi = math.min(r,g,b)
  997.  mx = math.max(r,g,b); v = mx;
  998.  d = mx - mi;
  999.  s = 0; if mx ~= 0 then s = d/mx; end
  1000.  p = 1; if g ~= mx then p = 2; if b ~= mx then p = 0; end; end
  1001.  
  1002.  h = 6.5 -- for custom graphical purposes
  1003.  if s>greytol then -- can't use >=
  1004.   h=(6+p*2+(c[1+p]-c[1+(p+1)%3])/d)%6;
  1005.  end
  1006.  
  1007.  return h
  1008. end
  1009. --
  1010.  
  1011. --
  1012. -- ... eof RGB color modifications ...
  1013. --
  1014.  
  1015.  
  1016.  
  1017. -- ****************************************
  1018. -- *** Custom Color / Palette functions ***
  1019. -- ****************************************
  1020.  
  1021.  
  1022. --# of Unique colors in palette:
  1023. --#db.fixPalette(db.makePalList(256))
  1024.  
  1025. --# of Colors in Image:
  1026. --#db.makePalListFromHistogram(db.makeHistogram())
  1027.  
  1028. --# of Unique colors in Image:
  1029. --#db.fixPalette(db.makePalListFromHistogram(db.makeHistogram()))
  1030.  
  1031. --
  1032. function db.rgbcap(r,g,b,mx,mi)
  1033.  local m = math
  1034.  return m.max(mi,m.min(r,mx)), m.max(mi,m.min(g,mx)), m.max(mi,m.min(b,mx))
  1035. end
  1036. --
  1037.  
  1038. --
  1039. function db.rgbcapInt(r,g,b,mx,mi)
  1040.  local m = math
  1041.  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)))
  1042. end
  1043. --
  1044.  
  1045.  
  1046. --
  1047. function db.makePalList(cols)
  1048.  local pal,n,r,g,b
  1049.  pal = {}
  1050.  for n = 0, cols-1, 1 do
  1051.    r,g,b = getcolor(n)
  1052.    pal[n+1] = {r,g,b,n}
  1053.  end
  1054.  return pal
  1055. end
  1056. --
  1057.  
  1058. --
  1059. function db.makeSparePalList(cols)
  1060.  local pal,n,r,g,b
  1061.  pal = {}
  1062.  for n = 0, cols-1, 1 do
  1063.    r,g,b = getsparecolor(n)
  1064.    pal[n+1] = {r,g,b,n}
  1065.  end
  1066.  return pal
  1067. end
  1068. --
  1069.  
  1070.  
  1071. --
  1072. -- Use to remove the black colors (marks unused colors) from palette-list
  1073. -- if it's known that no black color exists in the image.
  1074. function db.stripBlackFromPalList(pallist)
  1075.    local i,u,c,dummy; i = 257 -- Do 'nothing' If using a full 256 col palette with no blacks
  1076.    for u = 1, #pallist, 1 do
  1077.      c = pallist[u]
  1078.      if (c[1]+c[2]+c[3]) == 0 then i = u; end
  1079.    end
  1080.    dummy = table.remove(pallist,i)
  1081.    return pallist
  1082. end
  1083. --
  1084.  
  1085. --
  1086. function db.stripIndexFromPalList(pallist,colindex)
  1087.    local i,u,c,dummy
  1088.    for u = 1, #pallist, 1 do
  1089.      c = pallist[u]
  1090.      if c[4] == colindex then i = u; end
  1091.    end
  1092.    dummy = table.remove(pallist,i)
  1093.    return pallist
  1094. end
  1095. --
  1096.  
  1097. --
  1098. function db.addHSBtoPalette(pallist)
  1099.  local n,hue,sat,rgb
  1100.  for n=1, #pallist, 1 do
  1101.    rgb = pallist[n]
  1102.    pallist[n][5] =        db.getHUE(rgb[1],rgb[2],rgb[3],0)
  1103.    pallist[n][6] = db.getSaturation(rgb[1],rgb[2],rgb[3])
  1104.    pallist[n][7] = db.getBrightness(rgb[1],rgb[2],rgb[3])
  1105.  end
  1106.  return pallist -- {r,g,b,n,bri,hue,sat}
  1107. end
  1108. --
  1109.  
  1110. --
  1111. function db.makePalListRange(start,ends)
  1112.  local pal,n,r,g,b,a
  1113.  pal = {}
  1114.  a = 1
  1115.  for n = start, ends, 1 do
  1116.    r,g,b = getcolor(n)
  1117.    pal[a] = {r,g,b,n}; a = a + 1;
  1118.  end
  1119.  return pal
  1120. end
  1121. --
  1122.  
  1123.  
  1124. --
  1125. function db.makePalListShade(cols,sha) -- Convert colors to less bits, colorcube operations etc.
  1126.  local pal,n,r,g,b,mf,div
  1127.  mf = math.floor
  1128.  div = 256 / sha
  1129.  pal = {}
  1130.  for n = 0, cols-1, 1 do
  1131.    r,g,b = getcolor(n)
  1132.    pal[n+1] = {mf(r/div),mf(g/div),mf(b/div),n}
  1133.  end
  1134.  return pal
  1135. end
  1136. --
  1137. --
  1138. function db.makePalListShadeSPARE(cols,sha) -- Convert colors to less bits, colorcube operations etc.
  1139.  local pal,n,r,g,b,mf,div
  1140.  mf = math.floor
  1141.  div = 256 / sha
  1142.  pal = {}
  1143.  for n = 0, cols-1, 1 do
  1144.    r,g,b = getsparecolor(n)
  1145.    pal[n+1] = {mf(r/div),mf(g/div),mf(b/div),n}
  1146.  end
  1147.  return pal
  1148. end
  1149. --
  1150.  
  1151.  
  1152.  
  1153. --
  1154. function db.getColorDistance_weight(r1,g1,b1,r2,g2,b2,rw,gw,bw)
  1155.  return math.sqrt( (rw*(r1-r2))^2 + (gw*(g1-g2))^2 + (bw*(b1-b2))^2 )
  1156. end
  1157. --
  1158.  
  1159. --
  1160. -- Since brightness is exponential, each channel may work as a "star" drowning the color
  1161. -- of a lesser channel. This algorithm is an approximation to adjust distances for this phenomenon.
  1162. -- Ex: Adding 32 red to black is visually obvious, but adding 64 red to full green is almost
  1163. -- impossible to detect by the naked eye.
  1164. --
  1165. -- However this isn't a complete solution so we may weigh in brightness as well...
  1166. --
  1167. -- If cv = 0 (0..1) then prox acts as ordinary perceptual colordistance
  1168. -- if bri = 1 (0..1) then distance is only that of brightness
  1169. function db.getColorDistanceProx(r1,g1,b1,r2,g2,b2,rw,gw,bw,normalizer, cv,briweight)
  1170.  local rp1,gp1,bp1,rp2,gp2,bp2,v,m1,m2,prox,bdiff; v = 2*255*255
  1171.  m1 = math.max(r1,g1,b1)
  1172.  m2 = math.max(r2,g2,b2)
  1173.  rp1 = 1 - math.sqrt((r1-m1)^2 / v) * cv
  1174.  gp1 = 1 - math.sqrt((g1-m1)^2 / v) * cv
  1175.  bp1 = 1 - math.sqrt((b1-m1)^2 / v) * cv
  1176.  
  1177.  rp2 = 1 - math.sqrt((r2-m2)^2 / v) * cv
  1178.  gp2 = 1 - math.sqrt((g2-m2)^2 / v) * cv
  1179.  bp2 = 1 - math.sqrt((b2-m2)^2 / v) * cv
  1180.  
  1181.  bdiff = math.abs(db.getBrightness(r1,g1,b1) - db.getBrightness(r2,g2,b2)) -- weights are hardcoded in function
  1182.  prox = math.sqrt( (rw*(r1*rp1-r2*rp2))^2 + (gw*(g1*gp1-g2*gp2))^2 + (bw*(b1*bp1-b2*bp2))^2 ) * normalizer
  1183.  
  1184.   return prox * (1-briweight) + bdiff * briweight
  1185. end
  1186. --
  1187.  
  1188. --
  1189. function db.getBestPalMatch(r,g,b,pal,index_flag) -- pal = [r,g,b,palindex], index_flag -> return palindex if pal is sorted or reduced
  1190.  local diff,best,bestcol,cols,n,c,p
  1191.  cols = #pal
  1192.  bestcol = -1
  1193.  best = 9e99
  1194.  
  1195.  for n=1, cols, 1 do
  1196.   p = pal[n]
  1197.   diff = db.getColorDistance_weight(r,g,b,p[1],p[2],p[3],0.26,0.55,0.19)  * 1.569
  1198.   if diff < best then bestcol = n; best = diff; end
  1199.  end
  1200.  
  1201.  if index_flag == true then
  1202.   bestcol = pal[bestcol][4] + 1
  1203.  end
  1204.  
  1205.  return bestcol-1 -- palList index start at 1, image-palette at 0
  1206. end
  1207. --
  1208.  
  1209.  
  1210. -- Normally this function will return the (image)palette index of best color
  1211. -- ...but if the palette has been sorted with 'fixPalette' it will return the index
  1212. -- of the custom palList, setting index_flag will convert this value to image-palette index
  1213. --
  1214. -- HYBRID means the colormatch is a combo of color and (perceptual)brightness
  1215. --
  1216. --
  1217. function db.getBestPalMatchHYBRID(rgb,pal,briweight,index_flag) -- Now correctly balanced
  1218.  local diff,diffC,diffB,best,bestcol,cols,n,c,r,g,b,p,obri,pbri
  1219.  cols = #pal
  1220.  bestcol = -1
  1221.  best = 9e99
  1222.  
  1223.  --messagebox(briweight)
  1224.  
  1225.  -- Note: Not secured against negative values (this algorithm is SLOW, we cannot afford it)
  1226.  r = rgb[1]
  1227.  g = rgb[2]
  1228.  b = rgb[3]
  1229.  
  1230.  obri = db.getBrightness(r,g,b) -- 0-255
  1231.  
  1232.  for n=1, cols, 1 do
  1233.   p = pal[n]
  1234.   pbri = db.getBrightness(p[1],p[2],p[3])
  1235.   diffB = math.abs(obri - pbri)
  1236.   -- we need to normalize the distance by the weights
  1237.   diffC = db.getColorDistance_weight(r,g,b,p[1],p[2],p[3],0.26,0.55,0.19)  * 1.569
  1238.  
  1239.   diff = briweight * (diffB - diffC) + diffC
  1240.   if diff < best then bestcol = n; best = diff; end
  1241.  end
  1242.  
  1243.  if index_flag == true then
  1244.   bestcol = pal[bestcol][4] + 1 -- Since we detract 1 on return, God Lua is stupid
  1245.  end
  1246.  
  1247.  return bestcol-1 -- palList index start at 1, image-palette at 0
  1248. end
  1249. --
  1250.  
  1251.  
  1252.  
  1253. --
  1254. -- Special version of Hybrid-remapping for mixPalette list
  1255. --
  1256. -- mixpal: {score,col#1,col#2,dist,rm,gm,bm, c1_r,c1_g,c1_b, c2_r,c2_g,c2_b}
  1257. --
  1258. -- returns: {col#1,col#2} (index of palette)
  1259. --
  1260. function db.getBestPalMatchHybridMIX(rgb,mixpal,briweight,mixreduction)
  1261.  local diff,diffC,diffB,best,bestcol,cols,n,c,r,g,b,p,obri,pbri, distmult
  1262.  cols = #mixpal
  1263.  bestcol = -1
  1264.  best = 9e99
  1265.  
  1266.  -- We will simply add the the distance to the mix with the distance between the mixcolors and
  1267.  -- employ a user tolerance to much the latter will matter.
  1268.   --distmult = 255 / 9.56 / 100 * mixreduction -- 16 shades
  1269.  distmult = 1.56902 / 100 * mixreduction  -- 24-bit, Dawn3.0 colormodel
  1270.  
  1271.  -- Note: Not secured against negative values (this algorithm is SLOW, we cannot afford it)
  1272.  r = rgb[1]
  1273.  g = rgb[2]
  1274.  b = rgb[3]
  1275.  
  1276.  obri = db.getBrightness(r,g,b) -- 0-255
  1277.  
  1278.  for n=1, cols, 1 do
  1279.   p = mixpal[n]
  1280.   --pbri = db.getBrightness(p[5],p[6],p[7])
  1281.  
  1282.  -- *** DawnBringer's exponetial color brightness dither resolution phenomena theorem ***
  1283.  -- Bri = color value ^ 2
  1284.  -- Two adjacent pixels displayed with "normal high resolution" will NOT have the perceptual
  1285.  -- brightness of the resulting mixcolor. The brightness lies closer to that of the brightest pixel.
  1286.  -- Bri[(C1+C2)/2] = SQRT( (C1bri^2 + C2bri^2) / 2 )
  1287.  -- (Brightness according to Dawn-model: bri = SQRT( (r*.26)^2 + (g*.55)^2 + (b*.19)^2 ) )
  1288.  
  1289.   pbri = math.sqrt((db.getBrightness(p[8],p[9],p[10])^2 + db.getBrightness(p[11],p[12],p[13])^2) / 2)
  1290.  
  1291.   diffB = math.abs(obri - pbri)
  1292.   -- we need to normalize the distance by the weights
  1293.   diffC = db.getColorDistance_weight(r,g,b,p[5],p[6],p[7],0.26,0.55,0.19)  * 1.569 + p[4]*distmult
  1294.  
  1295.   diff = briweight * (diffB - diffC) + diffC
  1296.   if diff <= best then bestcol = n; best = diff; end
  1297.  end
  1298.  
  1299.  return {mixpal[bestcol][2], mixpal[bestcol][3]}
  1300. --return {mixpal[bestcol][2], 0}
  1301.  
  1302.  
  1303.  
  1304. end
  1305. --
  1306.  
  1307.  
  1308.  
  1309. --
  1310. function db.matchcolorHSB(h,s,b,pallist,index_flag)
  1311.  --
  1312.  -- why don't we just convert HSB-diagram to RGB and do normal colormatching?
  1313.  -- Not the same...
  1314.  --
  1315.  local n,c,best,bestcol,pb,ph,ps,diff,huediff,huecorr,hue_adj,sat_adj,bri_adj
  1316.  bestcol = -1
  1317.  best = 9e99
  1318.  
  1319.  -- higher adjust means more impact (higher hue gives more interpolation )
  1320.  hue_adj = 4
  1321.  sat_adj = 0.075
  1322.  bri_adj = 2
  1323.  
  1324.  huecorr = 255 / 6 -- Our Hue goes from 0.0 - 5.999
  1325.  
  1326.  for n=1, #pallist, 1 do
  1327.   c = pallist[n]
  1328.   ph = c[5]
  1329.   ps = c[6]
  1330.   pb = c[7]
  1331.  
  1332.   huediff = math.abs(h-ph*huecorr)
  1333.   if huediff > 127 then huediff = huediff - (huediff % 127) * 2; end
  1334.  
  1335.   --if ph == 6.5 then huediff = 0; end
  1336.  
  1337.   -- With less saturation, exact hue becomes less important and brightness more usefull
  1338.   -- This allows for greyscales and low saturation colors to work smoothly.
  1339.   huediff = huediff * (ps /255)
  1340.  
  1341.   diff = hue_adj*huediff^2 + (s-ps)^2 * sat_adj + (b-pb)^2 * bri_adj
  1342.  
  1343.   if diff <= best then bestcol = n; best = diff; end
  1344.  end
  1345.  
  1346.  if index_flag == true then
  1347.   bestcol = pallist[bestcol][4] + 1 -- Since we detract 1 on return, God Lua is stupid
  1348.  end
  1349.  
  1350.  return bestcol-1
  1351.  
  1352. end
  1353. --
  1354.  
  1355. --
  1356. -- Used by PaletteAnalysis.lua, FindRamps(), MixColors() etc.
  1357. -- Assigns is used by ApplySpare script
  1358. --
  1359. function db.fixPalette(pal,sortflag) -- Arrange palette & only keep unique colors
  1360.  
  1361.  local n,l,rgb,i,unique,bri,hue,sat,ulist,indexpal,newpal,dtot
  1362.  ulist = {}
  1363.  indexpal = {}
  1364.  newpal = {}
  1365.   local doubles,assign
  1366.   doubles = {}; assign = {}
  1367.  
  1368.  l = #pal
  1369.  
  1370.  unique = 1 -- ok, see how stupid lua is
  1371.  dtot = 0
  1372.  for n=1, l, 1 do
  1373.    rgb = pal[n]; -- actually rgbn
  1374.    i = 1 + rgb[1] * 65536 + rgb[2] * 256 + rgb[3];
  1375.    bri = db.getBrightness(rgb[1],rgb[2],rgb[3])
  1376.    if indexpal[i] == nil then
  1377.       indexpal[i] = rgb; ulist[unique] = {i,bri}; unique = unique+1;
  1378.       assign[rgb[4]+1] = rgb[4] -- really n, but can we be sure?
  1379.       else
  1380.         doubles[rgb[4]] = true; -- Mark as double (This is wrong; starts at 0...but col 0 cannot be a double so...)
  1381.         dtot = dtot + 1
  1382.         assign[rgb[4]+1] = indexpal[i][4] -- Reassign removed color
  1383.    end
  1384.  end
  1385.  
  1386.  -- sort ulist
  1387.  if sortflag == 1 then db.sorti(ulist,2); end -- sort by brightness
  1388.  
  1389.  l = #ulist
  1390.  for n=1, l, 1 do
  1391.   newpal[n] = indexpal[ulist[n][1]]
  1392.  end
  1393.  
  1394.  newpal["assigns"] = assign -- Complete list of image color assigns (removed cols will point to 1st occurence)
  1395.  newpal["doubles"] = doubles
  1396.  newpal.double_total = dtot
  1397.  
  1398.   --messagebox("unique colors", unique-1)
  1399.  
  1400.  return newpal
  1401.  
  1402. end
  1403. --
  1404.  
  1405. --
  1406. function db.drawColorspace12bit(x,y,cols,size)
  1407.  local r,g,b,c,rows,row,col,s16,rx,ry,xx,yy
  1408.  s16 = size*16
  1409.  rows = math.floor(16/cols)
  1410.  
  1411.  for g = 0, 15, 1 do
  1412.   col = g % cols
  1413.   row = math.floor(g / cols)
  1414.   for r = 0, 15, 1 do
  1415.    for b = 0, 15, 1 do
  1416.     c  = matchcolor(r*17,g*17,b*17)
  1417.     xx = x+col*s16+r*size
  1418.     yy = y+row*s16+b*size
  1419.      for ry = 0, size-1, 1 do
  1420.       for rx = 0, size-1, 1 do
  1421.        putpicturepixel(xx+rx,yy+ry,c)
  1422.      end;end
  1423.    end
  1424.   end
  1425.  end
  1426. end
  1427. --
  1428.  
  1429. --
  1430. function db.drawHSBdiagram(pallist,posx,posy,width,height,size,sat)
  1431.  --db.addHSBtoPalette(palList)
  1432.  local x,y,c
  1433.  for y = 0, height-1, 1 do
  1434.   for x = 0, width-1, 1 do
  1435.    hue = 255/width  * x
  1436.    bri = 255/height * y
  1437.    c = db.matchcolorHSB(hue,sat,bri,pallist,true)
  1438.    db.drawRectangle(posx + x*size, posy + y*size,size,size, c)
  1439.   end
  1440.  end
  1441. end
  1442. --
  1443.  
  1444. --
  1445. function db.polarHSBdiagram(ox,oy,radius,pol,brilev,huelev,saturation,dark2bright_flag)
  1446.  
  1447. local pal,bstep,bstep2,hstep,hstep2,bri,hue,sx,sy,cx,cy,x,y,p1,p2,c
  1448.  
  1449. pal = db.addHSBtoPalette(db.fixPalette(db.makePalList(256)))
  1450.  
  1451. bstep = radius / (brilev + pol)
  1452. bstep2 = bstep / 2
  1453. hstep = -360 / huelev
  1454. hstep2 = hstep / 2
  1455.  
  1456. c = 255; if dark2bright_flag then c = 0; end
  1457. drawdisk(ox,oy,math.ceil(pol*bstep),matchcolor(c,c,c))
  1458.  
  1459. for y=pol, brilev+pol-1,1 do
  1460.  
  1461.  bri = (brilev - y + pol) * (256 / brilev)
  1462.  
  1463.  if dark2bright_flag then
  1464.   bri = (brilev - (brilev - y + pol)) * (256 / brilev)
  1465.  end
  1466.  
  1467.  for x=0, huelev-1,1 do
  1468.  
  1469.   hue = x * (360 / huelev) * 255/360
  1470.  
  1471.   c = db.matchcolorHSB(hue,saturation,bri,pal,true)
  1472.  
  1473.   sx = ox
  1474.   sy = oy - y*bstep
  1475.  
  1476.   cx,cy = db.rotationFrac(x*hstep,ox,oy,sx,sy)
  1477.  
  1478.   x1,y1 = db.rotation(x*hstep-hstep2,ox,oy,ox, sy-bstep2)
  1479.   x2,y2 = db.rotation(x*hstep+hstep2,ox,oy,ox, sy-bstep2)
  1480.   x3,y3 = db.rotation(x*hstep-hstep2,ox,oy,ox, sy+bstep2)
  1481.   x4,y4 = db.rotation(x*hstep+hstep2,ox,oy,ox, sy+bstep2)
  1482.  
  1483.   p1 = {{x1,y1},{x2,y2},{x3,y3}}
  1484.   p2 = {{x3,y3},{x4,y4},{x2,y2}}
  1485.  
  1486.   db.fillTriangle(p1,c,0,true,false) -- triangle, fillcol, linecol, fill, wire
  1487.   db.fillTriangle(p2,c,0,true,false)
  1488.  
  1489.  end
  1490.  updatescreen(); if (waitbreak(0)==1) then return; end
  1491. end
  1492.  
  1493. end -- polarHSB
  1494. --
  1495.  
  1496.  
  1497. --
  1498. -- Histograms, remapping etc.
  1499. --
  1500.  
  1501. --
  1502. function db.makeHistogram()
  1503.   local n,y,x,c,w,h,list; list = {}
  1504.   w, h = getpicturesize()
  1505.   for n = 1, 256, 1 do list[n] = 0; end
  1506.   for y = 0, h - 1, 1 do
  1507.     for x = 0, w - 1, 1 do
  1508.       c = getpicturepixel(x,y)
  1509.       list[c+1] = list[c+1] + 1
  1510.     end
  1511.   end
  1512.   return list
  1513. end
  1514. --
  1515.  
  1516. --
  1517. function db.makeHistogramIndexed() -- With color index so it can be sorted etc.
  1518.   local n,y,x,c,w,h,r,g,b,list; list = {}
  1519.   w, h = getpicturesize()
  1520.   for n = 1, 256, 1 do
  1521.    r,g,b = getcolor(n-1)
  1522.    list[n] = {0,n-1,r,g,b};
  1523.   end
  1524.   for y = 0, h - 1, 1 do
  1525.     for x = 0, w - 1, 1 do
  1526.       c = getpicturepixel(x,y)
  1527.       list[c+1][1] = list[c+1][1] + 1
  1528.     end
  1529.   end
  1530.   return list
  1531. end
  1532. --
  1533.  
  1534. --
  1535. function db.makeSpareHistogram()
  1536.   local n,y,x,c,w,h,list; list = {}
  1537.   w, h = getsparepicturesize()
  1538.   --w,h = 512,360
  1539.   for n = 1, 256, 1 do list[n] = 0; end
  1540.   for y = 0, h - 1, 1 do
  1541.     for x = 0, w - 1, 1 do
  1542.       c = getsparepicturepixel(x,y)
  1543.       list[c+1] = list[c+1] + 1
  1544.     end
  1545.   end
  1546.   return list
  1547. end
  1548. --
  1549.  
  1550.  
  1551. --
  1552. -- Makes a palette-list from only the colors (histogram) that occurs in the image
  1553. -- Assumes image/palette has not changed since histogram was created
  1554. function db.makePalListFromHistogram(hist)
  1555.   local n,r,g,b,list,count
  1556.   list = {}
  1557.   count = 1
  1558.   for n = 1, #hist, 1 do
  1559.     if hist[n] > 0 then
  1560.       r,g,b = getcolor(n-1)
  1561.       list[count] = {r,g,b,n-1}
  1562.       count = count + 1
  1563.     end
  1564.   end
  1565.   return list
  1566. end
  1567. --
  1568.  
  1569. function db.makePalListFromSpareHistogram(hist)
  1570.   local n,r,g,b,list,count
  1571.   list = {}
  1572.   count = 1
  1573.   for n = 1, #hist, 1 do
  1574.     if hist[n] > 0 then
  1575.       r,g,b = getsparecolor(n-1)
  1576.       list[count] = {r,g,b,n-1}
  1577.       count = count + 1
  1578.     end
  1579.   end
  1580.   return list
  1581. end
  1582. --
  1583.  
  1584.  
  1585. --
  1586. function db.remap(org) -- Working with a remap-list there's no need of reading backuppixel
  1587.   --messagebox("Remapping")
  1588.   local x,y,c,i,w,h,s,f,col
  1589.   f = getpicturepixel
  1590.   s = false
  1591.   w, h = getpicturesize()
  1592.   for y = 0, h - 1, 1 do
  1593.    for x = 0, w - 1, 1 do
  1594.     c = f(x,y)
  1595.     i = org[c+1]
  1596.     if i == null then i = matchcolor(getbackupcolor(getbackuppixel(x,y))); s = true; col = c; end -- Find color for a removed double
  1597.     putpicturepixel(x,y,i)
  1598.    end
  1599.   end
  1600.   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);
  1601.   end
  1602. end
  1603. --
  1604.  
  1605. --
  1606. -- Same as db.remap but no comments
  1607. --
  1608. function db.remapImage(colassignlist) -- assignment list is optional
  1609.  local x,y,c,w,i,h,assign
  1610.  assign = false
  1611.  if colassignlist ~= null then assign = true; end
  1612.  w,h = getpicturesize()
  1613.  for y = 0, h-1, 1 do
  1614.    for x = 0, w-1, 1 do
  1615.     c = getbackuppixel(x,y)
  1616.     i = null; if assign then i = colassignlist[c+1]; end
  1617.     if not assign or i == null then
  1618.      i = matchcolor(getbackupcolor(c))
  1619.     end
  1620.     putpicturepixel(x,y,i)
  1621.  end
  1622.  end
  1623. end
  1624. --
  1625.  
  1626. --
  1627. -- Palette DeCluster: Color-reduction by fusing similar colors into new ones, using a desired tolerance.
  1628. --                    This is a method similar to Median-Cut, but more surgical.
  1629. --
  1630. -- pallist:   Palette list {r,g,b,palette_index}
  1631. -- hist:      Histogram {color 0 pixels, color 1 pixels...etc} always a full 256 color list
  1632. -- crad:      Cluster radius treshold in % of distance between black & white
  1633. --            A value of 0 will only remove identical colors
  1634. --            A value of 3-4 will usally fuse redundant colors without causing notice
  1635. -- prot_pow:  (0..10) Protect common colors in histogram. Distances are increased by ocurrence.
  1636. --            Also gives protection to fused colors even if not using histogram (combined nominal weights)
  1637. -- pixels:    Pixels in image (so protection can be calculated)
  1638. -- rw,gw,bw:  Color weights (rw+gw+bw = 1, 0.33,0.33,0.33 is nominal)
  1639. --
  1640. -- Returns:
  1641. -- a new (c)palette list {r,g,b,{original palette_indices},fused flag, histogram_weight}
  1642. -- a remap list (org) [image color + 1] = remap color (in the new palette)
  1643. function db.deCluster(pallist, hist, crad, prot_pow, pixels, rw,gw,bw)
  1644.  
  1645.  --messagebox(pixels)
  1646.  
  1647.  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
  1648.  local worst,wtot,maxdist,maxDist,distfrac,clusterExists,clustVal,count,crad1
  1649.  local cList,cPalList,clusterList,fuseCol,orgcols,newPalList,org
  1650.  
  1651.  maxdist = math.sqrt(rw*rw*65025 + gw*gw*65025 + bw*bw*65025)
  1652.  distfrac = 100 / maxdist
  1653.  
  1654.  -- Let's just make a slightly more suitable format of the pallist (List for original color(s))
  1655.  cPalList = {}
  1656.  for u = 1, #pallist, 1 do
  1657.   c = pallist[u]
  1658.   cPalList[u] = {c[1],c[2],c[3],{c[4]},false,hist[c[4]+1]} -- r,g,b,{original colors},fuse_marker,histogram_weight
  1659.  end
  1660.  
  1661.  --table.insert(cPalList,{255,255,0,{257},false,1})
  1662.  
  1663.  clusterExists = true
  1664.  while clusterExists do
  1665.   clusterExists = false
  1666.   clusterList = {}
  1667.  
  1668.   crad1 = crad + 1 -- avoid divison by zero
  1669.   worst = 9999
  1670.   for a = 1, #cPalList, 1 do
  1671.     c1 = cPalList[a]
  1672.     r1,g1,b1 = c1[1],c1[2],c1[3]
  1673.     wtot = c1[6]
  1674.     cList = {a}
  1675.     maxDist = 0
  1676.     for b = 1, #cPalList, 1 do
  1677.       if (b ~= a) then
  1678.         c2 = cPalList[b]
  1679.         r2,g2,b2 = c2[1],c2[2],c2[3]
  1680.         wt = c2[6]
  1681.         pd = math.pow((1 + wt / pixels),  prot_pow) -- Protection, increase distance
  1682.         dist = db.getColorDistance_weight(r1,g1,b1,r2,g2,b2,rw,gw,bw) * distfrac * pd
  1683.         if dist <= crad then
  1684.            wtot = wtot + wt
  1685.           table.insert(cList,b)
  1686.           maxDist = math.max(dist,maxDist)
  1687.         end
  1688.       end
  1689.     end -- b
  1690.     if #cList > 1 then
  1691.       clustVal = maxDist / (crad1 * #cList) * (wtot / #cList)  
  1692.       if clustVal < worst then
  1693.         worst = clustVal
  1694.         clusterList = cList
  1695.       end
  1696.     end
  1697.   end -- a
  1698.  
  1699.   --t = db.ary2txt(clusterList)
  1700.   --messagebox("Worst cluster is "..t)
  1701.  
  1702.    -- Fuse
  1703.   if #clusterList > 1 then
  1704.     clusterExists = true -- Run another iteration and look for more clusters
  1705.     fuseCol = {0,0,0,{}}
  1706.     rt,gt,bt,tot = 0,0,0,0
  1707.     for n = 1, #clusterList, 1 do
  1708.      i = clusterList[n]
  1709.      c = cPalList[i]
  1710.        --o = c[4][1] -- Original color (always #1 in list since fused colors can't re-fuse)
  1711.      o = c[4] -- Original color list
  1712.      --if c[5] == true then messagebox("Re-Fusing..."); end
  1713.      r,g,b = c[1],c[2],c[3]
  1714.        --wt = hist[o+1] -- Org. colors are 0-255
  1715.      wt = c[6]
  1716.      rt = rt + r * wt
  1717.      gt = gt + g * wt
  1718.      bt = bt + b * wt
  1719.      tot = tot + wt
  1720.      cPalList[i] = -1 -- Erase color
  1721.        --table.insert(fuseCol[4],o)
  1722.      orgcols = fuseCol[4]
  1723.      for j = 1, #o, 1 do
  1724.       table.insert(orgcols,o[j])
  1725.      end
  1726.      fuseCol[4] = orgcols
  1727.     end
  1728.  
  1729.     rt = rt / tot
  1730.     gt = gt / tot
  1731.     bt = bt / tot
  1732.     fuseCol[1] = rt
  1733.     fuseCol[2] = gt
  1734.     fuseCol[3] = bt
  1735.     fuseCol[5] = true -- fusecol marker
  1736.     fuseCol[6] = tot
  1737.     table.insert(cPalList,fuseCol)
  1738.     --messagebox(#clusterList.." Colors was fused, resulting in "..rt..", "..gt..", "..bt)
  1739.     newPalList = {}
  1740.     for n = 1, #cPalList, 1 do
  1741.      if cPalList[n] ~= -1 then
  1742.        table.insert(newPalList,cPalList[n])
  1743.       --newPalList = db.newArrayInsertLast(newPalList,cPalList[n])  
  1744.      end
  1745.     end
  1746.     cPalList = newPalList
  1747.     --messagebox("Pal length: "..#cPalList)
  1748.    statusmessage("DC - Image colors: "..#cPalList.."                       "); waitbreak(0)
  1749.   end -- fuse
  1750.  
  1751.  end -- while
  1752.  
  1753.     -- Create remap-list
  1754.     org = {}
  1755.     count = 0
  1756.     for u = 1, #cPalList, 1 do
  1757.        c = cPalList[u]
  1758.        for n = 1, #c[4], 1 do
  1759.          i = c[4][n]
  1760.          org[i+1] = count -- quick way to remap without matchcolor
  1761.        end
  1762.        count = count + 1
  1763.     end
  1764.  
  1765.  return org,cPalList
  1766.  
  1767. end; -- decluster
  1768.  
  1769.  
  1770.  
  1771. -- ------------- MEDIAN CUT V1.0 ------------
  1772. --
  1773. --   256 color Palette Lua-version (converted from Evalion JS-script)
  1774. --
  1775. --   by Richard 'DawnBringer' Fhager
  1776. --
  1777. --
  1778. -- pal:    [[r,g,b,i]]  Pallist
  1779. -- cnum:                Target number of colors in reduced palette
  1780. -- (step:)   1..          Pixel picks for processing, 1 = all pixels in image, best and slowest. 2 = 25% of pixels
  1781. -- qual:   Flag         Qualitative color selection (Normal mode)
  1782. -- quant:  Flag         Quantative color/pixel selection (Histogram) 100% mean that it count as much as quality
  1783. --  (One of or both qual/quant must be selected)
  1784. -- rgbw:   [3]          RGB-weights []. Weigh the color channels. ex: [1,1.333,0.75]
  1785. -- bits:   1-8          Bits used for each color channel in palette
  1786. -- quantpow:            0..1
  1787. --
  1788. -- return: A palette! A list of [r,g,b] values
  1789. --
  1790. -- NOTE: Quantity will act as a counterforce to altered colorspace (weights)...
  1791. -- Ex: if Green is considered bigger, it will be split into more blocks
  1792. --     but each blocks will have less colors and thus less quantity.
  1793. --
  1794. -- Perceptual colorspace (rgb-weights) will generally produce the best quality of palettes, but if
  1795. -- It's desirable to preserve stronger colors, esp. in small palettes,
  1796. -- it can be good to just use nominal space: 1,1,1
  1797. --
  1798. -- Histogram may be useful to assign more colors to an object that already covers most of the image,
  1799. -- however; if the object of interest is small in relation to a large (unimportant) background, it's
  1800. -- usually best not to have any histogram at all. Histogram will dampen strong infrequent colors.
  1801.  
  1802.  
  1803. function db.medianCut(pal,cnum,qual,quant,rgbw,bits,quantpow)
  1804.  local n,x,y,xs,ys,rgb,blocklist,blocks
  1805.  local len,res,chan,diff,maxdiff,maxblock,split
  1806.  local qualnorm, quantnorm
  1807.  
  1808.  -- Normalize 256 for quality/quantity relationship
  1809.  qualnorm =  1 / math.sqrt(rgbw[1]^2 + rgbw[2]^2 + rgbw[3]^2)
  1810.  quantnorm = 256 / #pal
  1811.  
  1812.  blocklist = {}
  1813.  blocklist[1] = {}; blocks = 1
  1814.  
  1815.  for n=1, #pal, 1 do
  1816.   blocklist[1][n] = pal[n];
  1817.  end
  1818.  
  1819.  analyzeBlock(blocklist[1],qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  1820.  
  1821.  failsafe = 0
  1822.  while (blocks < cnum and failsafe < 256) do
  1823.    failsafe = failsafe + 1
  1824.   maxdiff  = -1
  1825.   maxblock = -1
  1826.   for n=1, blocks, 1 do
  1827.     diff = blocklist[n].diff
  1828.     if (diff > maxdiff) then maxdiff = diff; maxblock = n; end -- maxchan is stored as .chan in block
  1829.   end
  1830.   split = splitBlock(blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  1831.   --if (split == false){ alert("Only found " + blocks + " (24-bit) colors!"); break;  }
  1832.   blocks = #blocklist
  1833.   --status.value = "MC: " +blocks
  1834.  end -- while
  1835.  
  1836.  return blocks2Palette(blocklist,bits)
  1837.  
  1838. end
  1839. --
  1840.  
  1841. --
  1842. function blocks2Palette(blocklist,bits)
  1843.  local n,r,g,b,c,pal,block,rgb,blen,M,dB,cB,rf,gf,bf
  1844.  
  1845.  M = math
  1846.  pal = {}
  1847.  
  1848.  --bits = 1
  1849.  dB = M.pow(2,8-bits)
  1850.  cB = M.ceil(255 / (M.pow(2,bits) - 1))
  1851.  
  1852.  for n=1, #blocklist, 1 do
  1853.   block = blocklist[n]
  1854.   r,g,b = 0,0,0
  1855.   blen = #block
  1856.   for c=1, blen, 1 do
  1857.    rgb = block[c]
  1858.    r = r + rgb[1]
  1859.    g = g + rgb[2]
  1860.    b = b + rgb[3]
  1861.   end
  1862.  
  1863.   rf = M.floor(M.min(255,M.max(0,M.floor(r/blen))) / dB) * cB
  1864.   gf = M.floor(M.min(255,M.max(0,M.floor(g/blen))) / dB) * cB
  1865.   bf = M.floor(M.min(255,M.max(0,M.floor(b/blen))) / dB) * cB
  1866.  
  1867.   pal[n] = {rf, gf, bf, 0} -- col is avg. of all colors in block (index is set (to 0) for compatibility)
  1868.  end -- blocklist
  1869.  
  1870.  return pal
  1871. end
  1872. --
  1873.  
  1874. --
  1875. function analyzeBlock(block,qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  1876.  local r,g,b,n,rmin,gmin,bmin,rmax,gmax,bmax,rdif,gdif,bdif,chan,d,median,diff
  1877.  local len,Mm,Mx,rgb,kv,qu
  1878.  
  1879.  Mx,Mm = math.max, math.min
  1880.  len = #block
  1881.  
  1882.  rmin,gmin,bmin = 255,255,255
  1883.  rmax,gmax,bmax = 0,0,0
  1884.  
  1885.  for n=1, len, 1 do
  1886.   rgb = block[n]
  1887.   r = rgb[1] * rgbw[1]
  1888.   g = rgb[2] * rgbw[2]
  1889.   b = rgb[3] * rgbw[3]
  1890.   --if (!isNaN(r) and !isNaN(g) and !isNaN(b)) then -- Ignore any erroneous data
  1891.    rmin = Mm(rmin,r)
  1892.    gmin = Mm(gmin,g)
  1893.    bmin = Mm(bmin,b)
  1894.    rmax = Mx(rmax,r)
  1895.    gmax = Mx(gmax,g)
  1896.    bmax = Mx(bmax,b)
  1897.   --end
  1898.  end
  1899.  
  1900.  rdif = (rmax - rmin) -- * rgbw[1]
  1901.  gdif = (gmax - gmin) -- * rgbw[2]
  1902.  bdif = (bmax - bmin) -- * rgbw[3]
  1903.  
  1904.  d = {{rmin,rdif,rmax},{gmin,gdif,gmax},{bmin,bdif,bmax}}
  1905.  
  1906.  chan = 1 -- Widest channel
  1907.  if (gdif > rdif) then chan = 2; end
  1908.  if (bdif > rdif and bdif > gdif) then chan = 3; end
  1909.  
  1910.  -- Ok, this is the average of the max/min value rather than an actual median
  1911.  -- I guess this will fill the colorspace more uniformly and perhaps select extremes to a greater extent?
  1912.  -- Which is better?
  1913.  --median = d[chan][1] + d[chan][2] / 2 -- OLD same as median with nominal weights
  1914.  
  1915.  median = (d[chan][1] + d[chan][3]) / 2
  1916.  
  1917.  -- quantity and quality are normalized to 256 (256 is the total of colors in the set for quantity)
  1918.  -- Note that, regardless of forumla, quality (distance) must always be greater in any block than quantity (colors/pixels)
  1919.  -- Coz a block may contain many of only 1 unique color, thus rendering it impossible to split if selected.
  1920.  kv = 1
  1921.  qu = 1
  1922.  if (quant) then kv = 1 + len*quantnorm*quantpow; end
  1923.  if (qual)  then qu = d[chan][2] * qualnorm; end
  1924.  diff = qu + qu*kv^2.5
  1925.  
  1926.  block.chan   = chan
  1927.  block.diff   = diff
  1928.  block.median = median
  1929.  
  1930.  return {chan,diff,median,len}
  1931.  
  1932. end
  1933. --
  1934.  
  1935. function splitBlock(blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  1936.   local n,cmax,median,blockA,blockB,len,cB,block,rgb,res
  1937.  
  1938.   blockA,blockB = {},{}
  1939.   block = blocklist[maxblock]
  1940.  
  1941.   res = true
  1942.  
  1943.   chan   = block.chan
  1944.   median = block.median  
  1945.  
  1946.   cB = blocklist[maxblock] -- maxblock starts at 1 when called so it should not hava a +1
  1947.   len = #cB
  1948.  
  1949.   for n=1, len, 1 do
  1950.     rgb = cB[n]
  1951.     --if (rgb[chan] >= median) then blockA.push(rgb); end
  1952.     --if (rgb[chan] <  median) then blockB.push(rgb); end
  1953.     if (rgb[chan]*rgbw[chan] >= median) then table.insert(blockA,rgb); end
  1954.     if (rgb[chan]*rgbw[chan] <  median) then table.insert(blockB,rgb); end
  1955.   end
  1956.  
  1957.   blocklist[maxblock] = blockA  -- Can't be empty right?
  1958.   analyzeBlock(blocklist[maxblock],qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  1959.  
  1960.   if (#blockB > 0) then
  1961.    table.insert(blocklist,blockB)
  1962.    analyzeBlock(blocklist[#blocklist],qual,quant,rgbw,qualnorm,quantnorm,quantpow) -- no -1 on blocklist
  1963.    else
  1964.     res = false
  1965.   end
  1966.  
  1967.   return res -- false = no split
  1968. end
  1969.  
  1970. ------------ eof MEDIAN CUT --------------------------
  1971.  
  1972.  
  1973. -- ------------- MEDIAN REDUX V1.0 ------------
  1974. --
  1975. --  Divide space by greatest distance of any two colors (rather than MC-method of any given channel)
  1976. --  Basically it allows colorspace to be sliced at any angles rather than the "boxing" of MC.
  1977. --
  1978. --
  1979. --   by Richard 'DawnBringer' Fhager
  1980. --
  1981. --
  1982. -- pal:    [[r,g,b,i,h]]  Pallist (h = histogram/pixelcount)
  1983. -- cnum:                Target number of colors in reduced palette
  1984. -- (step:)   1..          Pixel picks for processing, 1 = all pixels in image, best and slowest. 2 = 25% of pixels
  1985. -- qual:   Flag         Qualitative color selection (Normal mode)
  1986. -- quant:  Flag         Quantative color/pixel selection (Histogram) 100% mean that it count as much as quality
  1987. --  (One of or both qual/quant must be selected)
  1988. -- rgbw:   [3]          RGB-weights []. Weigh the color channels. ex: [0.26, 0.55, 0.19]
  1989. -- bits:   1-8          Bits used for each color channel in palette
  1990. -- quantpow: 0..1       Quantity vs Quality (put weight into histogram/pixelcount)
  1991. -- briweight: 0..1      Brightness distance weight in colordistance
  1992. -- proxweight: 0..1     Primary Proximity distance weight in colordistance (ColorTheory-WIP: compensate for brightness of individual channels, the "extra power" of primary colors)    
  1993. --
  1994. -- return: A palette! A list of [r,g,b] values
  1995. --
  1996. -- NOTE: Quantity will act as a counterforce to altered colorspace (weights)...
  1997. -- Ex: if Green is considered bigger, it will be split into more blocks
  1998. --     but each blocks will have less colors and thus less quantity.
  1999. --
  2000. -- Perceptual colorspace (rgb-weights) will generally produce the best quality of palettes, but if
  2001. -- It's desirable to preserve stronger colors, esp. in small palettes,
  2002. -- it can be good to just use nominal space: 0.33, 0.33, 0.33
  2003. --
  2004. -- Histogram may be useful to assign more colors to an object that already covers most of the image,
  2005. -- however; if the object of interest is small in relation to a large (unimportant) background, it's
  2006. -- usually best not to have any histogram at all. Histogram will dampen strong infrequent colors.
  2007.  
  2008.  
  2009. function db.medianRedux(pal,cnum,qual,quant,rgbw,bits,quantpow, briweight, proxweight) -- pal[r,g,b,i,pixelcount]
  2010.  local n,x,y,xs,ys,rgb,blocklist,blocks
  2011.  local len,res,chan,diff,maxdiff,maxblock,split
  2012.  local qualnorm, quantnorm,count
  2013.  
  2014.  blocklist = {}
  2015.  blocklist[1] = {}; blocks = 1
  2016.  
  2017.  count = 0
  2018.  for n=1, #pal, 1 do
  2019.   blocklist[1][n] = pal[n];
  2020.   count = count + pal[n][5]
  2021.  end
  2022.  
  2023.  -- Normalize 256 for quality/quantity relationship
  2024.  qualnorm =  1 / math.sqrt(rgbw[1]^2 + rgbw[2]^2 + rgbw[3]^2)
  2025.  quantnorm = 256 / count
  2026.  
  2027.  
  2028.  -- Dist table
  2029.  statusmessage("MR: Making Distance Table..."); updatescreen(); if (waitbreak(0)==1) then return; end
  2030.  local dy,c,r1,g1,b1,i1,i2
  2031.  dt = {}
  2032.  for n=1, #pal, 1 do
  2033.   c = pal[n]
  2034.   r1,g1,b1,i1 = c[1],c[2],c[3],c[4]
  2035.   dt[i1+1] = {}
  2036.   for m=1, #pal, 1 do
  2037.    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
  2038.   end
  2039.  end
  2040.  --
  2041.  
  2042.  statusmessage("MR: Analyzing Block 1..."); updatescreen(); if (waitbreak(0)==1) then return; end
  2043.  r_analyzeBlock(dt,blocklist[1],qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  2044.  
  2045.  statusmessage("MR: Analyzing Blocks..."); updatescreen(); if (waitbreak(0)==1) then return; end
  2046.  failsafe = 0
  2047.  while (blocks < cnum and failsafe < 256) do
  2048.    failsafe = failsafe + 1
  2049.   maxdiff  = -1
  2050.   maxblock = -1
  2051.   for n=1, blocks, 1 do
  2052.     diff = blocklist[n].diff
  2053.     if (diff > maxdiff) then maxdiff = diff; maxblock = n; end -- maxchan is stored as .chan in block
  2054.   end
  2055.   split = r_splitBlock(dt,blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  2056.   if (split == false) then messagebox("Only found "..blocks.." (24-bit) colors!"); break; end
  2057.   blocks = #blocklist
  2058.   statusmessage("MR: "..blocks); updatescreen(); if (waitbreak(0)==1) then return; end
  2059.  end -- while
  2060.  
  2061.  return r_blocks2Palette(blocklist,bits)
  2062.  
  2063. end
  2064. --
  2065.  
  2066. --
  2067. function r_blocks2Palette(blocklist,bits)
  2068.  local n,r,g,b,c,pal,block,rgb,blen,M,dB,cB,rf,gf,bf
  2069.  
  2070.  M = math
  2071.  pal = {}
  2072.  
  2073.  --bits = 1
  2074.  dB = M.pow(2,8-bits)
  2075.  cB = M.ceil(255 / (M.pow(2,bits) - 1))
  2076.  
  2077.  for n=1, #blocklist, 1 do
  2078.   block = blocklist[n]
  2079.   r,g,b = 0,0,0
  2080.   blen = #block
  2081.   for c=1, blen, 1 do
  2082.    rgb = block[c]
  2083.    r = r + rgb[1]
  2084.    g = g + rgb[2]
  2085.    b = b + rgb[3]
  2086.   end
  2087.  
  2088.   rf = M.floor(M.min(255,M.max(0,M.floor(r/blen))) / dB) * cB
  2089.   gf = M.floor(M.min(255,M.max(0,M.floor(g/blen))) / dB) * cB
  2090.   bf = M.floor(M.min(255,M.max(0,M.floor(b/blen))) / dB) * cB
  2091.  
  2092.   pal[n] = {rf, gf, bf} -- col is avg. of all colors in block
  2093.  end -- blocklist
  2094.  
  2095.  return pal
  2096. end
  2097. --
  2098.  
  2099. --
  2100. function r_analyzeBlock(dt,block,qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  2101.  local r,g,b,n,m,rmin,gmin,bmin,rmax,gmax,bmax,rdif,gdif,bdif,chan,d,median,diff
  2102.  local len,Mm,Mx,rgb,kv,qu
  2103.  local maxdist,dist,r1,g1,b1,r2,g2,b2,c1,c2,count
  2104.  
  2105.  Mx,Mm = math.max, math.min
  2106.  len = #block
  2107.  
  2108.  rmin,gmin,bmin = 255,255,255
  2109.  rmax,gmax,bmax = 0,0,0
  2110.  
  2111.  maxdist,c1,c2,count = 0,-1,-1,0
  2112.  
  2113.  for n=1, len, 1 do
  2114.    rgb1 = block[n]
  2115.    count = count + rgb1[5] -- pixelcount for color
  2116.   for m=n+1, len, 1 do
  2117.    rgb2 = block[m]
  2118.    --dist = db.getColorDistanceProx(r1,g1,b1,r2,g2,b2,0.26,0.55,0.19,1.569, 0.1, 0.25) -- pri/bri
  2119.    dist = dt[rgb1[4]+1][rgb2[4]+1]
  2120.  
  2121.    if dist > maxdist then
  2122.     maxdist = dist
  2123.     c1 = rgb1[4]+1
  2124.     c2 = rgb2[4]+1
  2125.    end
  2126.  
  2127.   end
  2128.  end
  2129.  
  2130.  -- quantity and quality are normalized to 256 (256 is the total of colors in the set for quantity)
  2131.  -- Note that, regardless of forumla, quality (distance) must always be greater in any block than quantity (colors/pixels)
  2132.  -- Coz a block may contain many of only 1 unique color, thus rendering it impossible to split if selected.
  2133.  kv = 1
  2134.  qu = 1
  2135.  if (quant) then kv = math.pow(1 + count*quantnorm*quantpow, 0.5); end
  2136.  if (qual)  then qu = maxdist * qualnorm; end
  2137.  diff = qu*(1-quantpow) + qu*kv
  2138.  
  2139.  block.chan   = -1
  2140.  block.diff   = diff
  2141.  block.median = -1
  2142.  block.c1     = c1
  2143.  block.c2     = c2
  2144.  
  2145.  return {diff,len}
  2146.  
  2147. end
  2148. --
  2149.  
  2150. function r_splitBlock(dt,blocklist,maxblock,qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  2151.   local n,cmax,median,blockA,blockB,len,cB,block,rgb,res
  2152.   local c1,c2,dist1,dist2,medr,medg,medb,r1,g1,b1,r2,g2,b2,rgb1,rgb2  
  2153.  
  2154.   blockA,blockB = {},{}
  2155.   block = blocklist[maxblock]
  2156.  
  2157.   res = true
  2158.  
  2159.   --chan   = block.chan
  2160.   --median = block.median  
  2161.   c1 = block.c1
  2162.   c2 = block.c2
  2163.  
  2164.   --rgb1 = block[c1]
  2165.   --r1,g1,b1 = rgb1[1],rgb1[2],rgb1[3]
  2166.   --rgb2 = block[c2]
  2167.   --r2,g2,b2 = rgb2[1],rgb2[2],rgb2[3]
  2168.   --medr = (r1+r2)/2
  2169.   --medg = (g1+g2)/2
  2170.   --medb = (b1+b2)/2
  2171.  
  2172.   cB = blocklist[maxblock] -- maxblock starts at 1 when called so it should not hava a +1
  2173.   len = #cB
  2174.  
  2175.   if len < 2 then return false; end
  2176.  
  2177.   for n=1, len, 1 do
  2178.     rgb = cB[n]
  2179.  
  2180.      dist1 = dt[rgb[4]+1][c1]
  2181.      dist2 = dt[rgb[4]+1][c2]
  2182.  
  2183.     if (dist1 <= dist2)
  2184.      then table.insert(blockA,rgb);
  2185.     end
  2186.  
  2187.     if (dist1 > dist2) then
  2188.      table.insert(blockB,rgb);
  2189.     end
  2190.   end
  2191.  
  2192.   blocklist[maxblock] = blockA  -- Can't be empty right?
  2193.   r_analyzeBlock(dt,blocklist[maxblock],qual,quant,rgbw,qualnorm,quantnorm,quantpow)
  2194.  
  2195.   if (#blockB > 0) then
  2196.    table.insert(blocklist,blockB)
  2197.    r_analyzeBlock(dt,blocklist[#blocklist],qual,quant,rgbw,qualnorm,quantnorm,quantpow) -- no -1 on blocklist
  2198.    else
  2199.     res = false
  2200.   end
  2201.  
  2202.   return res -- false = no split
  2203. end
  2204.  
  2205. ------------ eof MEDIAN REDUX --------------------------
  2206.  
  2207.  
  2208.  
  2209.  
  2210. --
  2211. -- ... eof Custom Color / Palette functions ...
  2212. --
  2213.  
  2214.  
  2215. -- *****************************
  2216. -- *** Custom Draw functions ***
  2217. -- *****************************
  2218.  
  2219. --
  2220. function db.line(x1,y1,x2,y2,c) -- Coords should be integers or broken lines are possible
  2221.  local n,st,m,xd,yd; m = math
  2222.  st = m.max(1,m.abs(x2-x1),m.abs(y2-y1));
  2223.  xd = (x2-x1) / st
  2224.  yd = (y2-y1) / st
  2225.  for n = 0, st, 1 do
  2226.    putpicturepixel(m.floor(x1 + n*xd), m.floor(y1 + n*yd), c );
  2227.  end
  2228. end
  2229. --
  2230.  
  2231. --
  2232. function db.lineTransp(x1,y1,x2,y2,c,amt) -- amt: 0-1, 1 = Full color
  2233.  local n,st,m,x,y,r,g,b,r1,g1,b1,c2,org; m = math
  2234.  org = 1 - amt
  2235.  st = m.max(1,m.abs(x2-x1),m.abs(y2-y1));
  2236.  for n = 0, st, 1 do
  2237.    x = m.floor(x1+n*(x2-x1)/st)
  2238.    y = m.floor(y1+n*(y2-y1)/st)
  2239.    r,g,b = getcolor(getpicturepixel(x,y))
  2240.    r1,g1,b1 = getcolor(c)
  2241.    c2 = matchcolor(r1*amt+r*org, g1*amt+g*org, b1*amt+b*org)
  2242.    putpicturepixel(x, y, c2 );
  2243.  end
  2244. end
  2245. --
  2246.  
  2247. --
  2248. function db.drawBrushRectangle(x1,y1,w,h,c)
  2249.    local x,y
  2250.    for y = y1, y1+h-1, 1 do
  2251.     for x = x1, x1+w-1, 1 do
  2252.        putbrushpixel(x,y,c);
  2253.     end
  2254.    end
  2255. end
  2256. --
  2257.  
  2258. --
  2259. function db.drawRectangle(x1,y1,w,h,c)
  2260.    local x,y
  2261.    for y = y1, y1+h-1, 1 do
  2262.     for x = x1, x1+w-1, 1 do
  2263.        putpicturepixel(x,y,c);
  2264.     end
  2265.    end
  2266. end
  2267. --
  2268.  
  2269. --
  2270. function db.drawRectangleNeg(x1,y1,w,h,c)
  2271.    local x,y,xs,ys
  2272.    xs = db.sign(w)
  2273.    ys = db.sign(h)
  2274.    if xs == 0 then xs = 1; end
  2275.    if ys == 0 then ys = 1; end
  2276.    for y = y1, y1+h-1, ys do
  2277.     for x = x1, x1+w-1, xs do
  2278.        putpicturepixel(x,y,c);
  2279.     end
  2280.    end
  2281. end
  2282. --
  2283.  
  2284. --
  2285. function db.drawRectangleLine(x,y,w,h,c)
  2286.  w = w-1
  2287.  h = h-1
  2288.  db.line(x,y,x+w,y,c)
  2289.  db.line(x,y,x,y+h,c)
  2290.  db.line(x,y+h,x+w,y+h,c)
  2291.  db.line(x+w,y,x+w,y+h,c)
  2292. end
  2293. --
  2294.  
  2295.  
  2296. --
  2297. function db.drawRectangleMix(x1,y1,w,h,c1,c2)
  2298.    local x,y,c,n
  2299.    c = {c1,c2}
  2300.    n = 0
  2301.    for y = y1, y1+h-1, 1 do
  2302.      n = n + 1
  2303.     for x = x1, x1+w-1, 1 do
  2304.        n = n + 1
  2305.        putpicturepixel(x,y,c[n%2+1]);
  2306.     end
  2307.    end
  2308. end
  2309. --
  2310.  
  2311. --
  2312. function db.drawCircle(x1,y1,r,c) -- ok, lottsa weird adjustments here, can probably be optimized...
  2313.    local x,y,d,r5,r25,r2,xr5,yr5
  2314.    r5,r25,r2,xr5,yr5 = r+0.5,r-0.25,r*2, x1-r-0.5, y1-r-0.5
  2315.    for y = 0, r2, 1 do
  2316.     for x = 0, r2, 1 do
  2317.        d = math.sqrt((x-r5)^2 + (y-r5)^2)
  2318.        if d < r25 then putpicturepixel(x + xr5, y + yr5,c); end
  2319.     end
  2320.    end
  2321. end
  2322. --
  2323.  
  2324. --
  2325. function db.drawBrushCircle(x1,y1,r,c) -- ok, lottsa weird adjustments here, can probably be optimized...
  2326.    local x,y,d
  2327.    for y = 0, r*2, 1 do
  2328.     for x = 0, r*2, 1 do
  2329.        d = math.sqrt((x-r-0.5)^2 + (y-r-0.5)^2)
  2330.        if d < r-0.25 then putbrushpixel(x1+x-r-0.5,y1+y-r-0.5,c); end
  2331.     end
  2332.    end
  2333. end
  2334. --
  2335.  
  2336. --
  2337. -- Rotation in degrees
  2338. -- Step is # of line segments (more is "better")
  2339. -- a & b are axis-radius
  2340. function db.ellipse2(x,y,a,b,stp,rot,col)
  2341.  local n,m=math,rad,al,sa,ca,sb,cb,ox,oy,x1,y1,ast
  2342.  m = math; rad = m.pi/180; ast = rad * 360/stp;
  2343.  sb = m.sin(-rot * rad); cb = m.cos(-rot * rad)
  2344.  for n = 0, stp, 1 do
  2345.   ox = x1; oy = y1;
  2346.   sa = m.sin(ast*n) * b; ca = m.cos(ast*n) * a
  2347.   x1 = x + ca * cb - sa * sb
  2348.   y1 = y + ca * sb + sa * cb
  2349.   --if (n > 0) then db.line(ox,oy,x1,y1,col); end
  2350.   if (n > 0) then drawline(ox,oy,x1,y1,col); end
  2351.  end
  2352. end
  2353. --
  2354.  
  2355.  
  2356.  
  2357. --[[
  2358. var ER = 0.3
  2359. var DR = 0.15
  2360.  
  2361. ellipse(0.5*xx,0.5*yy,DR*xx,6,Math.PI*0)
  2362.  
  2363. function ellipse(x,y,r,stp,rot){
  2364.  var n,deg=360,m=Math,rad=Math.PI/180,rn
  2365.  var ox,oy,x1,y1,x2,y2,d1,r1 = ER * xx
  2366.  
  2367.  for (n=0; n<=deg; n+=stp){
  2368.  
  2369.   ox = x2; oy = y2, rn = rad * n
  2370.   d1 = rn - rot
  2371.   x1 = x + m.sin(d1) * r
  2372.   y1 = y + m.cos(d1) * r
  2373.  
  2374.   x2 = x1 + m.sin(-rn) * r1
  2375.   y2 = y1 + m.cos(-rn) * r1
  2376.   if (n > 0){ line_rgb(MX,[0,0,0],0,ox,oy,x2,y2) }
  2377.  }
  2378. }
  2379.  
  2380. }
  2381.  
  2382. ellipse2(0.5*xx,0.5*yy,15,8,200,22,[0,0,0],0.5)
  2383.  
  2384. function ellipse2(x,y,a,b,stp,rot,rgb,transp){
  2385.  var n,m=Math,rad=m.PI/180,al,sa,ca,sb,cb,ox,oy,x1,y1
  2386.  sb = m.sin(-rot * rad); cb = m.cos(-rot * rad)
  2387.  for (n=0; n<=stp; n++){
  2388.   ox = x1; oy = y1; al = rad * 360/stp * n
  2389.   sa = m.sin(al) * b; ca = m.cos(al) * a
  2390.   x1 = x + ca * cb - sa * sb
  2391.   y1 = y + ca * sb + sa * cb
  2392.   if (n > 0){ line_rgb(MX,rgb,transp,ox,oy,x1,y1) }
  2393.  }
  2394. }
  2395.  
  2396.  
  2397. ]]
  2398.  
  2399.  
  2400.  
  2401. function db.obliqueCube(side,x,y,r,g,b,bri,cols)
  2402.  local n,c,depth,x1,y1,x2,y2,f
  2403.  
  2404.  asscols = false
  2405.  if cols >= 0 and cols<250 then
  2406.   asscols = true
  2407.   c = cols;                   setcolor(cols,r,g,b);       cols = cols + 1
  2408.   cP50 = cols; q =  bri*0.5;  setcolor(cols,r+q,g+q,b+q); cols = cols + 1;
  2409.   cP75 = cols; q =  bri*0.75; setcolor(cols,r+q,g+q,b+q); cols = cols + 1;
  2410.   cM50 = cols; q = -bri*0.5;  setcolor(cols,r+q,g+q,b+q); cols = cols + 1;
  2411.   cM100= cols; q = -bri;      setcolor(cols,r+q,g+q,b+q); cols = cols + 1;
  2412.  end
  2413.  
  2414.   f = matchcolor
  2415.   if asscols == false then
  2416.    c = f(r,g,b)
  2417.    cP50 =  f(r+bri*0.5,g+bri*0.5,b+bri*0.5)
  2418.    cP75 =  f(r+bri*0.75,g+bri*0.75,b+bri*0.75)
  2419.    cM50 =  f(r-bri*0.5,g-bri*0.5,b-bri*0.5)
  2420.    cM100 = f(r-bri,g-bri,b-bri)
  2421.   end
  2422.  
  2423.  
  2424.  depth = math.floor(side / 2)
  2425.  
  2426.  for n = 0, depth-1, 1 do
  2427.   db.line(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50)
  2428.   --drawline(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50)
  2429.  end
  2430.  
  2431.  for n = 0, depth-1, 1 do
  2432.   db.line(x+n,y-1-n,x+side+n-1,y-1-n,cP50)
  2433.   --drawline(x+n,y-1-n,x+side+n-1,y-1-n,cP50)
  2434.  end
  2435.  
  2436.  --  /
  2437.  --  
  2438.  --db.line(x+side,y-1,x+side+depth-1,y-depth,c)
  2439.  
  2440.  -- Smoothing & Shade
  2441.  
  2442.  --
  2443.  --  /
  2444.  --db.line(x+side,y+side-1,x+side+depth-1,y+side-depth,cM100)
  2445.  
  2446.  --db.line(x,y,x+side-2,y,cP75)
  2447.  --db.line(x,y,x,y+side-2,cP75)
  2448.  
  2449.  db.drawRectangle(x,y,side,side,c)
  2450.  
  2451.  return cols
  2452.  
  2453. end
  2454.  
  2455.  
  2456. function db.obliqueCubeBRI(side,x,y,r,g,b,bri,pallist,briweight,index_flag)
  2457.  local n,c,depth,x1,y1,x2,y2
  2458.  
  2459.   --f = db.getBestPalMatchHYBRID
  2460.   c =  db.getBestPalMatchHYBRID({r,g,b},    pallist, briweight, index_flag)
  2461.   cP50 =  db.getBestPalMatchHYBRID({r+bri*0.5,g+bri*0.5,b+bri*0.5},    pallist, briweight, index_flag)
  2462.   cP75 =  db.getBestPalMatchHYBRID({r+bri*0.75,g+bri*0.75,b+bri*0.75}, pallist, briweight, index_flag)
  2463.   cM50 =  db.getBestPalMatchHYBRID({r-bri*0.5,g-bri*0.5,b-bri*0.5},    pallist, briweight, index_flag)
  2464.   cM100 = db.getBestPalMatchHYBRID({r-bri,g-bri,b-bri},                pallist, briweight, index_flag)
  2465.  
  2466.  depth = math.floor(side / 2)
  2467.  
  2468.  db.drawRectangle(x,y,side,side,c)
  2469.  
  2470.  for n = 0, depth-1, 1 do
  2471.   db.line(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50)
  2472.   --drawline(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50)
  2473.  end
  2474.  
  2475.  for n = 0, depth-1, 1 do
  2476.   db.line(x+n,y-1-n,x+side+n-1,y-1-n,cP50)
  2477.   --drawline(x+n,y-1-n,x+side+n-1,y-1-n,cP50)
  2478.  end
  2479.  
  2480.  --  /
  2481.  --  
  2482.  db.line(x+side,y-1,x+side+depth-1,y-depth,c)
  2483.  --drawline(x+side,y-1,x+side+depth-1,y-depth,c)
  2484.  
  2485.  
  2486.  -- Smoothing & Shade
  2487.  
  2488.  --
  2489.  --  /
  2490.  --db.line(x+side,y+side-1,x+side+depth-1,y+side-depth,cM100)
  2491.  
  2492.  --db.line(x,y,x+side-2,y,cP75)
  2493.  --db.line(x,y,x,y+side-2,cP75)
  2494.  
  2495.  
  2496. end
  2497.  
  2498.  
  2499. --
  2500. function db.fillTriangle(p,fcol,lcol,fill,wire) -- p = list of 3 points
  2501.  
  2502.  local n,x,y,x1,x2,y1,y2,xf,yf,len,mr
  2503.  
  2504.  mr = math.floor
  2505.  
  2506.  -- Convert to screen/matrix-coordinates
  2507.  --if (mode == 'percent')  then xf = xx / 100; yf = yy / 100; end
  2508.  --if (mode == 'fraction') then xf = xx; yf = yy; end
  2509.  --if (mode ~= 'absolute') then screenilizeTriangle(p,xf,yf); end
  2510.  
  2511.  if (fill) then
  2512.   local Ax,Ay,Bx,By,Cx,Cy,xd,a,b,yc,ABdy,BCdy,ABix,BCix,ACix
  2513.  
  2514.   xd = {}
  2515.  
  2516.   --sort(p,1)                    -- Find top and middle y-point
  2517.   db.sorti(p,2)  
  2518.  
  2519.   Ay = p[1][2]; Ax = p[1][1]
  2520.   By = p[2][2]; Bx = p[2][1]
  2521.   Cy = p[3][2]; Cx = p[3][1]
  2522.  
  2523.   ABdy = By - Ay
  2524.   BCdy = Cy - By
  2525.   ABix = (Bx - Ax) / ABdy
  2526.   BCix = (Cx - Bx) / BCdy
  2527.   ACix = (Cx - Ax) / (Cy - Ay)
  2528.  
  2529.   a=1; b=2;
  2530.   if (ACix < ABix) then a=2; b=1; end
  2531.   for y = 0, ABdy-1, 1 do -- Upper -1
  2532.    xd[a] = mr(Ax + ABix * y)
  2533.    xd[b] = mr(Ax + ACix * y)
  2534.    yc = y+Ay;
  2535.    for x=xd[1], xd[2], 1 do
  2536.     putpicturepixel(x,yc,fcol)
  2537.    end
  2538.   end
  2539.  
  2540.   a=1; b=2;
  2541.   if (BCix < ACix) then a=2; b=1; end
  2542.   for y = 0, BCdy, 1 do -- Lower
  2543.    xd[a] = mr(Cx - BCix * y);
  2544.    xd[b] = mr(Cx - ACix * y)
  2545.    yc = Cy-y;
  2546.    for x = xd[1], xd[2], 1 do
  2547.     putpicturepixel(x,yc,fcol)
  2548.    end
  2549.   end
  2550.  
  2551.  end -- eof fill
  2552.  
  2553.  if (wire) then
  2554.   for n = 0, 2, 1 do -- Outline
  2555.    x1 = p[n+1][1]; y1 = p[n+1][2]
  2556.    x2 = p[1 + (n+1) % 3][1]; y2 = p[1 + (n+1) % 3][2]
  2557.    --db.line(x1,y1,x2,y2,lcol)
  2558.    drawline(x1,y1,x2,y2,lcol)
  2559.   end
  2560.  end
  2561.  
  2562. end -- eof fillTriangle
  2563. --
  2564.  
  2565. --
  2566. -- ... eof Custom Draw functions ...
  2567. --
  2568.  
  2569.  
  2570. -- ******************************
  2571. -- *** Filters & Convolutions ***
  2572. -- ******************************
  2573.  
  2574.  
  2575. function db.applyConvolution2Pic(convmx,divisor,bias,neg,amt)
  2576.  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
  2577.  
  2578.  n1 = 1
  2579.  n2 = bias
  2580.  if neg == 1 then
  2581.   n1 = -1
  2582.   n2 = 255 + bias
  2583.  end
  2584.  
  2585.  amtr = 1 - amt
  2586.  w, h = getpicturesize()
  2587.  cy = #convmx
  2588.  cx = #convmx[1]
  2589.  mxh = math.floor(cx / 2) + 1
  2590.  myh = math.floor(cy / 2) + 1
  2591.  
  2592.   for y = 0, h-1, 1 do
  2593.   for x = 0, w-1, 1 do
  2594.    r,g,b = 0,0,0
  2595.    ro,go,bo = getcolor(getbackuppixel(x,y))
  2596.    div = divisor
  2597.    for my = 1, cy, 1 do
  2598.     for mx = 1, cx, 1 do
  2599.      xp = mx-mxh
  2600.      yp = my-myh
  2601.      mp = convmx[my][mx]
  2602.      xx = x + xp
  2603.      yy = y + yp
  2604.       if yy>=0 and yy<h and xx>=0 and xx<w then
  2605.        rb,gb,bb = getcolor(getbackuppixel(xx,yy))
  2606.        r = r + rb * mp
  2607.        g = g + gb * mp
  2608.        b = b + bb * mp
  2609.         else div = div - mp -- Assumes divisor is the sum of the convolution matrix
  2610.       end
  2611.     end
  2612.    end
  2613.    r = ro*amtr + (n2 + (n1 * r) / div)*amt -- +bias
  2614.    g = go*amtr + (n2 + (n1 * g) / div)*amt
  2615.    b = bo*amtr + (n2 + (n1 * b) / div)*amt
  2616.    putpicturepixel(x,y,matchcolor(r,g,b))
  2617.  end;
  2618.   updatescreen(); if (waitbreak(0)==1) then return; end
  2619.  end;
  2620.  
  2621. end
  2622.  
  2623. --
  2624. -- ... eof Filters & Convolutions ...
  2625. --
  2626.  
  2627.  
  2628.  
  2629. -- *****************************
  2630. -- *** Fractals etc.         ***
  2631. -- *****************************
  2632.  
  2633. -- Fractal Pattern V1.0 by Richard Fhager (mod allows for wrapping)
  2634. --
  2635. -- Pattern matrix example: {{1,1,1},{1,0,1},{1,1,1}}
  2636. --
  2637. function db.pattern(x,y,p,n,i) -- coord as fraction of 1, pattern, offset(0), iterations (1-15)
  2638.  local px,py
  2639.  py = #p
  2640.  px = #p[1]
  2641.  while ((p[1+math.abs(math.floor(y*py))%py][1+math.abs(math.floor(x*px))%px]) > 0 and n<i) do
  2642.   x=x*px-math.floor(x*px);
  2643.   y=y*py-math.floor(y*py);
  2644.   n = n+1
  2645.  end
  2646.  return 1 - n/i;
  2647. end
  2648. --
  2649.  
  2650. --
  2651. function db.patternDec(x,y,p,n,i) -- coord as fraction of 1, pattern, offset(0), iterations (1-15)
  2652.  local px,py,spfrac,nfrac,fx,fy
  2653. spfrac = 1
  2654. nfrac = 0
  2655.  py = #p
  2656.  px = #p[1]
  2657.  while (spfrac > 0 and n<i) do
  2658.    fy = math.floor(math.abs(y*py))%py
  2659.    fx = math.floor(math.abs(x*px))%px
  2660.    spfrac = p[fy+1][fx+1]
  2661.    if (spfrac>0) then
  2662.     x = x*px - fx
  2663.     y = y*py - fy
  2664.     nfrac = nfrac + spfrac
  2665.    end
  2666.   n = n+1
  2667.  end
  2668.  --return 1 - n/i;
  2669.  return 1 - nfrac/i
  2670. end
  2671. --
  2672.  
  2673.  
  2674. --
  2675. function db.mandel(x,y,l,r,o,i) -- pos. as fraction of 1, left coord, right coord, y coord, iterations
  2676.  
  2677.   local w,s,a,p,q,n,v,w
  2678.  
  2679.   s=math.abs(r-l);
  2680.  
  2681.   a = l + s*x;
  2682.   p = a;
  2683.   b = o - s*(y-0.5);
  2684.   q = b;
  2685.   n = 1;
  2686.   v = 0;
  2687.   w = 0;
  2688.  
  2689.   while (v+w<4 and n<i) do n=n+1; v=p*p; w=q*q; q=2*p*q+b; p=v-w+a; end;
  2690.  
  2691.   return n
  2692. end
  2693. --
  2694.  
  2695. --
  2696. -- ... eof Fractals etc. ...
  2697. --
  2698.  
  2699.  
  2700. -- ********************************************
  2701. -- *** Color Cube / Space, Custom Functions ***
  2702. -- ********************************************
  2703. --
  2704. -- SHADES (sha): 24bit colors is too complex so we operate with less shades/bits/colors.
  2705. -- 16 shades = 12bit = 16*16*16 = 4096 colors (or cubic-elements)
  2706. -- 32 shades = 15bit = 32*32*32 = 32768 colors
  2707.  
  2708. --
  2709. -- data: {color available flag, 0 (gravity/distance, calculated, init as zero)}
  2710. --
  2711. function db.initColorCube(sha,data)
  2712.   local ary,z,y,x,n
  2713.   ary = {}
  2714.   for z = 0, sha-1, 1 do
  2715.    ary[z+1] = {}
  2716.    for y = 0, sha-1, 1 do
  2717.     ary[z+1][y+1] = {}
  2718.       if data ~= nil then
  2719.         for x = 0, sha-1, 1 do
  2720.            -- This is silly stupid, if you know how to assign one array to another (no ref), plz help!
  2721.            -- i.e. how to: data = {false,0}; myArray[z][y][x] = data.
  2722.            ary[z+1][y+1][x+1] = {}  
  2723.            for n = 1, #data, 1 do
  2724.              ary[z+1][y+1][x+1][n] = data[n]
  2725.            end
  2726.         end
  2727.       end
  2728.    end
  2729.   end
  2730.   return ary
  2731. end
  2732. --
  2733.  
  2734. --
  2735. function db.findVoid(cube,sha) -- void is point with longest distance to closest color
  2736.   local weakest,weak_i,x,y,z,c,w
  2737.   weakest = -1
  2738.    weak_i = {-1,-1,-1}
  2739.   for z = 0, sha-1, 1 do
  2740.    for y = 0, sha-1, 1 do
  2741.     for x = 0, sha-1, 1 do
  2742.  
  2743.       c = cube[z+1][y+1][x+1]
  2744.       if c[1] == true then
  2745.         w = c[2]
  2746.         if w > weakest then weakest = w; weak_i = {z,y,x}; end
  2747.       end
  2748.    
  2749.   end;end;end
  2750.   return weak_i[1],weak_i[2],weak_i[3]
  2751. end
  2752. --
  2753.  
  2754. --
  2755. --
  2756. -- Nearest color version: void is selected by the point that has the greatest distance
  2757. -- to the nearest color. Higher value means greater void.
  2758. --
  2759. function db.addColor2Cube(cube,sha,r,g,b,rw,gw,bw)
  2760.   local star,x,y,z,d,rd,gd,bd,cu1,cu2
  2761.   star = 0
  2762.   cube[r+1][g+1][b+1] = {false, star}
  2763.   for z = 0, sha-1, 1 do
  2764.    rd = (rw*(z-r))^2
  2765.    cu2 = cube[z+1]
  2766.    for y = 0, sha-1, 1 do
  2767.     gd = (gw*(y-g))^2
  2768.     cu1 = cu2[y+1]
  2769.     for x = 0, sha-1, 1 do
  2770.  
  2771.       d = rd + gd + (bw*(x-b))^2
  2772.  
  2773.       --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
  2774.  
  2775.       cu1[x+1][2] = math.min(d, cu1[x+1][2])
  2776.  
  2777.   end;end;end
  2778. end
  2779. --
  2780.  
  2781. -- Should be same as original, but not 100% verified. Using a rgb+1 trick to speed up handling
  2782. --
  2783. function db.addColor2Cube_test(cube,sha,r,g,b,rw,gw,bw)
  2784.   local star,x,y,z,d,rd,gd,bd,cu1,cu2
  2785.   star = 0
  2786.   r = r+1; g = g+1; b = b+1
  2787.   cube[r][g][b] = {false, star}
  2788.   for z = 1, sha, 1 do
  2789.    rd = (rw*(z-r))^2
  2790.    cu2 = cube[z]
  2791.    for y = 1, sha, 1 do
  2792.     gd = (gw*(y-g))^2
  2793.     cu1 = cu2[y]
  2794.     for x = 1, sha, 1 do
  2795.       cu1[x][2] = math.min(rd+gd+(bw*(x-b))^2, cu1[x][2])
  2796.   end;end;end
  2797. end
  2798. --
  2799.  
  2800.  
  2801.  
  2802. -- Create new allowed colorlines in colorspace (ramps from which colors can be picked)
  2803. function db.enableRangeColorsInCube(cube,sha,r1,g1,b1,r2,g2,b2)
  2804.  
  2805.     local div,r,g,b,n,rs,gs,bs
  2806.     div = 256 / sha
  2807.     rs = (r2 - r1) / sha / div
  2808.     gs = (g2 - g1) / sha / div
  2809.     bs = (b2 - b1) / sha / div
  2810.  
  2811.     for n = 0, sha-1, 1 do
  2812.  
  2813.      r = math.floor(r1/div + rs * n)
  2814.      g = math.floor(g1/div + gs * n)
  2815.      b = math.floor(b1/div + bs * n)
  2816.  
  2817.      cube[r+1][g+1][b+1][1] = true
  2818.  
  2819.     end
  2820. end
  2821. --
  2822.  
  2823.  
  2824. function db.colorCigarr(shades,radius,fill_flag)
  2825.  local s,rad,radsq,step,shalf,bas,cols,found,x,y,z,bri,con,d,n
  2826.  radius = radius / 100
  2827.  step = math.floor(255 / (shades-1))
  2828.  shalf = math.floor(shades / 2)
  2829.  s = shades - 1
  2830.  rad = math.floor(shades / 2 * radius)
  2831.  radsq = rad^2
  2832.  
  2833.  bas = 0
  2834.  cols = {}
  2835.  found = 0
  2836.  
  2837.  for z = 0, s, 1 do
  2838.   for y = 0, s, 1 do
  2839.    for x = 0, s, 1 do
  2840.  
  2841.   --0.26,0.55,0.19
  2842.   bri = (x + y + z ) / 3
  2843.   --bri = math.sqrt(((x*0.26)^2 + (y*0.55)^2 + (z*0.19)^2)) * 1.5609
  2844.   con = math.floor((shades - math.abs(bri - shalf)*2) * radius)
  2845.  
  2846.   d = math.floor(math.sqrt((bri-x)^2 + (bri-y)^2 + (bri-z)^2))
  2847.   --d = math.floor(math.sqrt(((bri-x)*0.26)^2 + ((bri-y)*0.55)^2 + ((bri-z)*0.19)^2)) * 1.5609
  2848.  
  2849.   -- Filled cigarr: Less or Equal, cigarr shell: Equal
  2850.    if d == con or (d < con and fill_flag) then
  2851.       found = found + 1
  2852.       r = bas + x * step
  2853.       g = bas + y * step
  2854.       b = bas + z * step
  2855.       cols[found] = {r,g,b}
  2856.    end
  2857.  
  2858.   end; end; end
  2859.  
  2860.   --messagebox("Colors found: "..found.."\n\n".."Run AnalyzePalette to examine")
  2861.  
  2862.   for n = 0, 255, 1 do
  2863.    if n < found then
  2864.     c = cols[n+1]
  2865.     setcolor(n,c[1],c[2],c[3])
  2866.      else
  2867.      setcolor(n,0,0,0)
  2868.     end
  2869.   end
  2870. end -- eof colorcigarr
  2871.  
  2872.  
  2873. --
  2874. -- ... eof Color Cube ...
  2875. --
  2876.  
  2877.  
  2878.  
  2879. -- COLORMIX --
  2880. --
  2881. -- Returns a list of mixcolors palette entries, that are ranked by by quality & usefulness
  2882. --
  2883. -- This whole junk my partly locked on 16 shades (4096 colors/ 12bit palette precision) so don't use anything else...
  2884. --
  2885. --
  2886. function db.colormixAnalysis(sha,spare_flag,cust_dist) -- Interface
  2887.   local shades,pallist,ilist,custom_max_distance
  2888.  
  2889.   shades = sha -- 16 is good
  2890.   --messagebox(shades)
  2891.  
  2892.   custom_max_distance = -1
  2893.   if cust_dist ~= null then
  2894.     custom_max_distance = cust_dist -- in %
  2895.   end
  2896.  
  2897.   if spare_flag == true then -- No shades here for now
  2898.     --pallist = db.makePalListShadeSPARE(256,shades)     -- 16 shades so Colorcube processes is possible
  2899.     pallist = db.makeSparePalList(256)
  2900.     pallist = db.fixPalette(pallist,0)            -- Remove doubles, No need to sort?
  2901.     ilist = db.makeIndexList(pallist, -1)         -- -1, use list order as index
  2902.    else
  2903.      pallist = db.makePalListShade(256,shades)       -- 16 shades so Colorcube processes is possible
  2904.      pallist = db.fixPalette(pallist,0)              -- Remove doubles, No need to sort?
  2905.      ilist = db.makeIndexList(pallist, -1)           -- -1, use list order as index
  2906.   end
  2907.  
  2908.    if shades > 0 then
  2909.     return db.colormixAnalysisEXT(shades,pallist,ilist,custom_max_distance) -- max distance in %
  2910.    end
  2911.    if shades == -1 then
  2912.      return db.colormixAnalysisEXTnoshade(pallist,ilist,custom_max_distance) -- max distance in %
  2913.    end
  2914. end
  2915. --
  2916. --
  2917. function db.colormixAnalysisEXT(SHADES,pallist,ilist,custom_max_distance) -- Shades, most number of mixes returned
  2918.  local n,m,c1,c2,pairs,cube,rm,gm,bm
  2919.  local mix,total,found,dist,void,ideal,mini,maxi,bestmix,bestscore
  2920.  
  2921.  --messagebox("will now make pairs")
  2922.  
  2923.   pairs = db.pairsFromList(ilist,0)            -- 0 for unique pairs only, pairs are entries in pallist
  2924.  
  2925.  --messagebox(#pairs.." will now add colors to cube")
  2926.  
  2927.  cube = db.initColorCube(SHADES,{true,9999})
  2928.  for n = 1, #pallist, 1 do
  2929.    c1 = pallist[n]
  2930.    db.addColor2Cube_test(cube,SHADES,c1[1],c1[2],c1[3],0.26,0.55,0.19)
  2931.  end
  2932.  
  2933.  -- these values are adjusted for a 12bit palette (0-15) and perceptual weight where r+g+b = 1.0
  2934.  -- Ideal distance = 2.5 Green steps = 1.375
  2935.  -- Minimum distance = 1 Green step  = 0.55
  2936.  
  2937.  --messagebox("colorcube done")
  2938.  
  2939.  VACT = 1
  2940.  DACT = 1
  2941.  
  2942.  total = 9.56 -- Max distance possible with 16 shades
  2943.  ideal = 0.45  -- 1 step = 0.637
  2944.  mini = 0.35
  2945.  maxi = ideal + (total - ideal) / math.max(1, #pallist / 16)
  2946.   if custom_max_distance ~= -1 then
  2947.    maxi = total * (custom_max_distance / 100)
  2948.   end
  2949.  mix = {}
  2950.  --mix[1] =  {9e99,0,0,9e99,0,0,0}
  2951.  bestmix = -1
  2952.  bestscore = 9e99
  2953.  found = 0
  2954.  for n = 1, #pairs, 1 do
  2955.  
  2956.    c1 = pallist[pairs[n][1]]
  2957.    c2 = pallist[pairs[n][2]]
  2958.    --0.26,0.55,0.19
  2959.    dist = db.getColorDistance_weight(c1[1],c1[2],c1[3],c2[1],c2[2],c2[3],0.26,0.55,0.19) -- Not normalized
  2960.  
  2961.    rm = math.floor((c1[1]+c2[1])/2)
  2962.    gm = math.floor((c1[2]+c2[2])/2)
  2963.    bm = math.floor((c1[3]+c2[3])/2)
  2964.  
  2965.   -- Mix color adjustment (perhaps less than perfect, but probably good enough)
  2966.    mixbri = db.getBrightness(rm,gm,bm)
  2967.    truebri = math.sqrt((db.getBrightness(c1[1],c1[2],c1[3])^2 + db.getBrightness(c2[1],c2[2],c2[3])^2) / 2)
  2968.    diff = truebri - mixbri
  2969.    rm = math.max(0,math.min(15,math.floor(rm + diff)))
  2970.    gm = math.max(0,math.min(15,math.floor(gm + diff)))
  2971.    bm = math.max(0,math.min(15,math.floor(bm + diff)))
  2972.     newbri = db.getBrightness(rm,gm,bm)
  2973.     delta =  math.abs(newbri - truebri)
  2974.     --if delta > 0.9 then
  2975.     -- messagebox(pallist[pairs[n][1]][4]..", "..pallist[pairs[n][2]][4].." delta = "..delta)
  2976.     --end
  2977.   --
  2978.  
  2979.    --rm = math.floor(math.sqrt((c1[1]^2 + c2[1]^2) / 2))
  2980.    --gm = math.floor(math.sqrt((c1[2]^2 + c2[2]^2) / 2))
  2981.    --bm = math.floor(math.sqrt((c1[3]^2 + c2[3]^2) / 2))
  2982.  
  2983.    void = cube[rm+1][gm+1][bm+1][2]
  2984.  
  2985.    if dist >= mini and dist <= maxi then
  2986.      found = found + 1
  2987.      score = ((1+DACT*(dist - ideal)^2) / (1+void*VACT)) -- Lowest is best
  2988.      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
  2989.      if score < bestscore then bestscore = score; bestmix = found; end
  2990.    end
  2991.  
  2992.  end
  2993.  
  2994.  
  2995.  if true == false then
  2996.  -- 2nd pass, add bestmix to colorspace. This reduces many similar mixes.
  2997.  m = mix[bestmix]
  2998.  db.addColor2Cube(cube,SHADES,m[5],m[6],m[7],0.26,0.55,0.19)
  2999.  for n = 1, #mix, 1 do
  3000.   if n ~= bestmix then
  3001.    m = mix[n]
  3002.    dist = m[4]
  3003.    void = cube[m[5]+1][m[6]+1][m[7]+1][2]
  3004.    score = ((1+DACT*(dist - ideal)^2) / (1+void*VACT))
  3005.    m[1] = score
  3006.   end
  3007.  end
  3008.  end
  3009.  
  3010.  c1,c2 = -1,-1
  3011.  if found > 0 then
  3012.   db.sorti(mix,1)
  3013.   best = mix[1]
  3014.   c1 = best[2]
  3015.   c2 = best[3]
  3016.  end
  3017.  
  3018.  --return found,c1,c2
  3019.  return mix,found,c1,c2
  3020. end
  3021. --
  3022.  
  3023.  
  3024. --
  3025. -- Mixcolor without colorcube - no scoring or sorting, 24bit colors, faster...
  3026. --
  3027. function db.colormixAnalysisEXTnoshade(pallist,ilist,custom_max_distance)
  3028.  local n,m,c1,c2,pairs,cube,rm,gm,bm
  3029.  local mix,total,found,dist,void,ideal,mini,maxi,bestmix,bestscore
  3030.  
  3031.   pairs = db.pairsFromList(ilist,0)            -- 0 for unique pairs only, pairs are entries in pallist
  3032.  
  3033.  total = 162.53 -- Max distance possible with 24-bit palette ad Dawn3.0 color weights  162.53
  3034.  ideal = 0
  3035.  mini  = 0
  3036.  maxi  = ideal + (total - ideal) / math.max(1, #pallist / 16)
  3037.  
  3038.   if custom_max_distance ~= -1 then
  3039.    maxi = total * (custom_max_distance / 100)
  3040.   end
  3041.  
  3042.  statusmessage("Mixcol Analysis ("..#pairs.." pairs) ");
  3043.  updatescreen(); if (waitbreak(0)==1) then return; end
  3044.  
  3045.  mix = {}
  3046.  found = 0
  3047.  for n = 1, #pairs, 1 do
  3048.    c1 = pallist[pairs[n][1]]
  3049.    c2 = pallist[pairs[n][2]]
  3050.    --0.26,0.55,0.19
  3051.    dist = db.getColorDistance_weight(c1[1],c1[2],c1[3],c2[1],c2[2],c2[3],0.26,0.55,0.19) -- Not normalized
  3052.  
  3053.    rm = math.floor((c1[1]+c2[1])/2)
  3054.    gm = math.floor((c1[2]+c2[2])/2)
  3055.    bm = math.floor((c1[3]+c2[3])/2)
  3056.  
  3057.   -- Mix color adjustment
  3058.    mixbri = db.getBrightness(rm,gm,bm)
  3059.    truebri = math.sqrt((db.getBrightness(c1[1],c1[2],c1[3])^2 + db.getBrightness(c2[1],c2[2],c2[3])^2) / 2)
  3060.    diff = truebri - mixbri
  3061.    rm = math.max(0,math.min(255,math.floor(rm + diff)))
  3062.    gm = math.max(0,math.min(255,math.floor(gm + diff)))
  3063.    bm = math.max(0,math.min(255,math.floor(bm + diff)))
  3064.     newbri = db.getBrightness(rm,gm,bm)
  3065.     delta =  math.abs(newbri - truebri)
  3066.     --if delta > 0.9 then
  3067.     --  messagebox(pallist[pairs[n][1]][4]..", "..pallist[pairs[n][2]][4].." delta = "..delta)
  3068.     --end
  3069.   --
  3070.  
  3071.    if dist >= mini and dist <= maxi then
  3072.      found = found + 1
  3073.      score = 1
  3074.      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
  3075.    end
  3076.  
  3077.  end
  3078.  
  3079.  --messagebox(#mix)
  3080.  
  3081.  return mix,found,-1,-1
  3082. end
  3083. --
  3084.  
  3085.  
  3086.  
  3087. -- Fuse a palettelist into an extended mix-anlysis list
  3088. function db.fusePALandMIX(pal,mix,max_score,max_dist)
  3089.  local n,c,mixlist,tot,score,dist,c1,c2,rm,gm,bm
  3090.  
  3091.   mixlist = {}
  3092.   tot = 0
  3093.  
  3094.   -- {r,g,b,n}
  3095.   for n = 1, #pal, 1 do
  3096.    tot = tot + 1
  3097.    c = pal[n]
  3098.    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]}
  3099.   end
  3100.  
  3101.   -- {score,col#1,col#2,dist,rm,gm,bm} low score is best
  3102.   for n = 1, #mix, 1 do
  3103.    score = mix[n][1]
  3104.    dist  = mix[n][4]
  3105.    if score <= max_score and dist <= max_dist then
  3106.      tot = tot + 1
  3107.       mixlist[tot] = mix[n]
  3108.    end
  3109.   end
  3110.  
  3111.  return mixlist
  3112. end
  3113. --
  3114.  
  3115.  
  3116. -- ********************************************
  3117. -- *** L-system (fractal curves & "plants") ***
  3118. -- ********************************************
  3119. --
  3120.  
  3121. --
  3122. function db.Lsys_makeData(a)
  3123.   local n,i; i = {}
  3124.   for n = 1, #a, 1 do i[a[n][2]] = a[n]; end
  3125.   return i
  3126. end
  3127. --
  3128.  
  3129. --
  3130. function db.Lsys_makeSet(seed,iter,data)
  3131.   local s,n,i,nset,set
  3132.   set = seed
  3133.   for n = 1, iter, 1 do
  3134.    nset = ''
  3135.    for i = 1, #set, 1 do
  3136.     s = string.sub(set,i,i)
  3137.     nset = nset..data[s][3]
  3138.    end
  3139.    set = nset
  3140.   end
  3141.   return set
  3142. end
  3143. --
  3144.  
  3145. function db.Lsys_draw(set,data,cx,cy,size,rot,rgb,rng,transp, speed)
  3146.   local p,M,DEG,l,n,d,i,v,q,c,tx,ty,posx,posy,dval,col,w,h,s,cl,count
  3147.  
  3148.   if speed == nil then speed = 50; end -- speed is drawing operations per update
  3149.  
  3150.   function ang(d) return (d % 360 + 360) * DEG; end
  3151.  
  3152.   w,h = getpicturesize()
  3153.  
  3154.   p   = 0
  3155.   M   = math
  3156.   DEG = math.pi/180
  3157.   l   = #set
  3158.  
  3159.   posx={}; posy={}; dval={}
  3160.  
  3161.   if (rgb == null) then rgb = {0,0,0}; end
  3162.   if (transp == null) then transp = 0; end
  3163.   col = db.newArrayMerge(rgb,{})
  3164.   q = 255 / l
  3165.  
  3166.   count = 0
  3167.   for n = 1, l, 1 do
  3168.     s = string.sub(set,n,n)
  3169.     d = data[s]
  3170.     i = d[1]
  3171.     v = d[4]
  3172.  
  3173.     --messagebox(i)
  3174.  
  3175.     if (i == 'Left')  then rot = rot - v; end
  3176.     if (i == 'Right') then rot = rot + v; end
  3177.  
  3178.     if (i == 'Save') then p=p+1; posx[p] = cx; posy[p] = cy; dval[p] = rot; end
  3179.     if (i == 'Load') then cx = posx[p]; cy = posy[p]; rot = dval[p]; p=p-1; end
  3180.  
  3181.     if (i == 'Draw') then
  3182.       tx = cx +  M.sin(ang(rot)) * size
  3183.       ty = cy + -M.cos(ang(rot)) * size
  3184.          for c = 1, 3, 1 do
  3185.           if (rng[c] > 0) then col[c] = rgb[c] + (n * q) * rng[c]; end
  3186.           if (rng[c] < 0) then col[c] = rgb[c] + (n * q) * rng[c]; end
  3187.          end
  3188.  
  3189.       cl = matchcolor(col[1],col[2],col[3])
  3190.       --putpicturepixel(cx*w,cy*h,cl);
  3191.       --db.line(cx*w,cy*h,tx*w,ty*h,cl)
  3192.       db.lineTransp(cx*w,cy*h,tx*w,ty*h,cl,transp)
  3193.  
  3194.       cx = tx; cy = ty
  3195.     end
  3196.    count = count + 1
  3197.    if count == speed then count = 0; updatescreen(); if (waitbreak(0)==1) then return end; end
  3198.   end
  3199.  
  3200.   return {cx,cy,rot}
  3201. end -- draw
  3202.  
  3203.  
  3204. --
  3205. -- eof L-system
  3206. --
  3207. ---------------------------------------------------------------------------------------
  3208.  
  3209.  
  3210.  
  3211. -- ********************************************
  3212. -- ***      COMPLEX SPECIAL FUNCTIONS       ***
  3213. -- ********************************************
  3214. --
  3215.  
  3216. ---------------------------------------------------------------------------------------
  3217. --
  3218. -- Render engine for mathscenes (Full Floyd-Steinberg dither etc)
  3219. --
  3220. 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
  3221.  
  3222. 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
  3223. local d1,d2,o1,o2,ox,oy
  3224.  
  3225.   -- percep is no longer used, matchcolor2 is always active, but the code is kept if there's ever a need to
  3226.   -- study the effect of perceptual colorspaces versus matchcolor
  3227.  
  3228.   if ord_bri == null then ord_bri = 0; end
  3229.   if ord_hue == null then ord_hue = 0; end
  3230.   if bri_change == null then bri_change = 0; end
  3231.   if hue_change == null then hue_change = 0; end
  3232.   if BRIWEIGHT == null then BRIWEIGHT = 0; end  
  3233.  
  3234.   briweight = BRIWEIGHT / 100
  3235.  
  3236.   ord = {{0,4,1,5},
  3237.          {6,2,7,3},
  3238.          {1,5,0,4},
  3239.          {7,3,6,2}}
  3240.  
  3241.  
  3242.    --i = ((ord[y % 4 + 1][x % 4 + 1])*28.444 - 99.55556)/100 * 16
  3243.    
  3244.       function hue(r,g,b,deg)
  3245.          local i,brin,diff,brio,r2,g2,b2
  3246.          r2,g2,b2 = db.shiftHUE(r,g,b,deg)
  3247.          brio = db.getBrightness(r,g,b)
  3248.          for i = 0, 5, 1 do -- 6 iterations, fairly strict brightness preservation
  3249.           brin = db.getBrightness(r2,g2,b2)
  3250.           diff = brin - brio
  3251.           r2,g2,b2 = db.rgbcap(r2-diff, g2-diff, b2-diff, 255,0)
  3252.          end
  3253.         return r2,g2,b2
  3254.        end
  3255.  
  3256.  
  3257.  
  3258. fsdiv = 16
  3259. if xonly == 1 then fsdiv = 7; end -- Only horizontal dither
  3260.  
  3261. dither = 0; if ditherprc > 0 then dither = 1; end
  3262.  
  3263. -- When using standard error-diffusion brightness-matching is not really compatible
  3264. --matchfunc = matchcolor2
  3265. --if dither == 1 then matchfunc = matchcolor; end
  3266.  
  3267. dpow = ditherprc / 100
  3268.  
  3269.  
  3270. if wd == null then
  3271.  w,h = getpicturesize()
  3272.   else w = wd; h = ht
  3273. end
  3274.  
  3275. if ofx == null then
  3276.  ox,oy = 0,0
  3277.   else ox = ofx; oy = ofy
  3278. end
  3279.  
  3280.  
  3281. function cap(v)
  3282.   return math.min(255,math.max(0,v))
  3283.  end
  3284.  
  3285.  
  3286.  
  3287.  --
  3288.  fl = {}
  3289.  fl[1] = {}
  3290.  fl[2] = {}
  3291.  i = 1
  3292.  j = 2
  3293.  --
  3294.  
  3295.   -- Read the first 2 lines
  3296.   v1 = ydith/2 + 0%2 * -ydith
  3297.   v2 = ydith/2 + 1%2 * -ydith
  3298.   for x = 0, w - 1, 1 do
  3299.     d1a,d1b = 0,0
  3300.     if ord_bri > 0 then
  3301.      o1 = ord[0 % 4 + 1][x % 4 + 1]
  3302.      d1a = (o1*28.444 - 99.55556)/100 * ord_bri
  3303.      o2 = ord[1 % 4 + 1][(x+2) % 4 + 1] -- +2 To get it in right sequence for some reason
  3304.      d1b = (o2*28.444 - 99.55556)/100 * ord_bri
  3305.     end
  3306.    -- We skip Hue-ordering for now
  3307.    vt1 = v1 + xdith/2 + (x+math.floor(0/2))%2 * -xdith + d1a
  3308.    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
  3309.    r,g,b = f(x, 0, w, h)
  3310.    fl[i][x] = {cap(r+vt1),cap(g+vt1),cap(b+vt1)}
  3311.    r,g,b = f(x, 1, w, h)
  3312.    fl[j][x] = {cap(r+vt2),cap(g+vt2),cap(b+vt2)}
  3313.   end
  3314.  
  3315. for y = 0, h-1, 1 do
  3316.  for x = 0, w-1, 1 do
  3317.  
  3318.   o = fl[i][x]
  3319.   r = o[1] + bri_change
  3320.   g = o[2] + bri_change
  3321.   b = o[3] + bri_change
  3322.  
  3323.   if hue_change ~= 0 then
  3324.    r,g,b = hue(r,g,b,hue_change)
  3325.   end
  3326.  
  3327.  
  3328.   --if percep == 0 then c = matchfunc(r,g,b); end
  3329.   --if percep == 1 then
  3330.    -- --c = db.getBestPalMatchHYBRID({r,g,b},pal,0,true)
  3331.    --c = matchcolor2(r,g,b,briweight)
  3332.   --end
  3333.  
  3334.   c = matchcolor2(r,g,b,briweight)
  3335.  
  3336.   putpicturepixel(x+ox,y+oy,c)
  3337.  
  3338.   if dither == 1 then
  3339.    if x>1 and x<w-1 and y<h-1 then
  3340.      rn,gn,bn = getcolor(c)
  3341.      re = ((r - rn) / fsdiv) * dpow
  3342.      ge = ((g - gn) / fsdiv) * dpow
  3343.      be = ((b - bn) / fsdiv) * dpow
  3344.      o = fl[i][x+1]; r,g,b = o[1],o[2],o[3]  
  3345.      fl[i][x+1] = {cap(r+re*7), cap(g+ge*7), cap(b+be*7)}
  3346.      if xonly ~= 1 then
  3347.        o = fl[j][x]; r,g,b = o[1],o[2],o[3]    
  3348.        fl[j][x] =   {cap(r+re*5), cap(g+ge*5), cap(b+be*5)}
  3349.        o = fl[j][x-1]; r,g,b = o[1],o[2],o[3]    
  3350.        fl[j][x-1] = {cap(r+re*3), cap(g+ge*3), cap(b+be*3)}
  3351.        o = fl[j][x+1]; r,g,b = o[1],o[2],o[3]    
  3352.        fl[j][x+1] = {cap(r+re), cap(g+ge), cap(b+be)}
  3353.      end
  3354.     end
  3355.    end
  3356.  
  3357.  end
  3358.  
  3359.   vt = 0
  3360.   v = ydith/2 + y%2 * -ydith
  3361.   -- Flip ED lines and read the nextline
  3362.   i,j = j,i
  3363.   for x = 0, w - 1, 1 do
  3364.  
  3365.     d1,d2 = 0,0
  3366.     if ord_bri > 0 then
  3367.      o = ord[y % 4 + 1][x % 4 + 1]
  3368.      d1 = (o*28.444 - 99.55556)/100 * ord_bri
  3369.     end
  3370.    
  3371.    vt = v + xdith/2 + (x+math.floor(y/2))%2 * -xdith + d1
  3372.    r,g,b = f(x, y+2, w, h)
  3373.  
  3374.    if ord_hue > 0 then
  3375.     o = ord[y % 4 + 1][x % 4 + 1]
  3376.     d2 = (((o + 3.5) % 7) / 7 - 0.5) * ord_hue
  3377.     r,g,b = hue(r,g,b,d2)
  3378.    end
  3379.  
  3380.    fl[j][x] = {cap(r+vt),cap(g+vt),cap(b+vt)}
  3381.   end
  3382.  updatescreen(); if (waitbreak(0)==1) then return; end
  3383. end
  3384.  
  3385.  
  3386. end
  3387. -------------------------------------------------------------------------------
  3388.  
  3389. --
  3390. -- ROTATE Image or Brush
  3391. --
  3392. -- target:     1 = Brush, 2 = Picture, 3 = Brush-to-Picture
  3393. -- rot:        Rotation in degrees
  3394. -- mode:       1 = Simple, 2 = Cosine Interpolation, 2 = BiLinear Interpolation
  3395. -- spritemode: 0 = Off, 1 = On (Only match adjacent colors, use with Bilinear-Ip. for good result)
  3396. -- resize:     0 = No, 1 = Yes (Resize Image/Brush to fit all gfx, otherwise clip)
  3397. -- update:     0 = No, 1 = Yes (Update screen while drawing)
  3398. -- xoffset:    For use with Brush-to-Picture operations
  3399. -- yoffset:    For use with Brush-to-Picture operations
  3400. --
  3401. function db.doRotation(target,rot,mode,spritemode,resize,update, xoffset,yoffset)
  3402.  
  3403.  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
  3404.  
  3405.  function donothing(n)
  3406.  end
  3407.  
  3408. func = {
  3409.  {getsize=getbrushsize,   setsize=setbrushsize,   clear=donothing,    get=getbrushbackuppixel, put=putbrushpixel},
  3410.  {getsize=getpicturesize, setsize=setpicturesize, clear=clearpicture, get=getbackuppixel,      put=putpicturepixel},
  3411.  {getsize=getbrushsize,   setsize=donothing,      clear=donothing,    get=getbrushbackuppixel, put=putpicturepixel}
  3412. }
  3413. trg = func[target]
  3414.  
  3415. --
  3416. function bilinear(ox,oy,w,h,func,mode)
  3417.  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
  3418.            xp2 = ox - math.floor(ox)
  3419.            yp2 = oy - math.floor(oy)
  3420.  
  3421.            if mode == 1 then -- Cosinus curve (rather than linear), slightly sharper result (probably same as Photoshop)
  3422.             xp2 = 1 - (math.cos(xp2 * math.pi) + 1)/2
  3423.             yp2 = 1 - (math.cos(yp2 * math.pi) + 1)/2
  3424.            end
  3425.  
  3426.            xp1 = 1 - xp2
  3427.            yp1 = 1 - yp2
  3428.  
  3429.            c1 = func(math.floor(ox),math.floor(oy));
  3430.            c2 = func(math.ceil(ox),math.floor(oy));
  3431.            c3 = func(math.floor(ox),math.ceil(oy));
  3432.            c4 = func(math.ceil(ox),math.ceil(oy));
  3433.  
  3434.            r1,g1,b1 = getcolor(c1);
  3435.            r2,g2,b2 = getcolor(c2);
  3436.            r3,g3,b3 = getcolor(c3);
  3437.            r4,g4,b4 = getcolor(c4);
  3438.  
  3439.            pal = {{r1,g1,b1,c1},{r2,g2,b2,c2},{r3,g3,b3,c3},{r4,g4,b4,c4}} -- for SpriteMode ColorMatching
  3440.  
  3441.            r = (r1*xp1 + r2*xp2)*yp1 + (r3*xp1 + r4*xp2)*yp2;
  3442.            g = (g1*xp1 + g2*xp2)*yp1 + (g3*xp1 + g4*xp2)*yp2;
  3443.            b = (b1*xp1 + b2*xp2)*yp1 + (b3*xp1 + b4*xp2)*yp2;
  3444.  
  3445.            return r,g,b, pal
  3446. end
  3447. --
  3448.  
  3449.  f = db.rotationFrac
  3450.  w,h = trg.getsize()
  3451.  hub_x = w / 2 - 0.5 -- Rotates 90,180 perfectly, not 45
  3452.  hub_y = h / 2 - 0.5
  3453.  --hub_x = w / 2
  3454.  --hub_y = h / 2
  3455.  x1,y1 = f (-rot,hub_x,hub_y,0,0) -- Rot is negative coz we read destination and write to source
  3456.  x2,y2 = f (-rot,hub_x,hub_y,w-1,0)
  3457.  x3,y3 = f (-rot,hub_x,hub_y,0,h-1)
  3458.  x4,y4 = f (-rot,hub_x,hub_y,w-1,h-1)
  3459.  dX  = (x2 - x1) / w
  3460.  dY  = (y2 - y1) / w
  3461.  dXs = (x4 - x2) / h
  3462.  dYs = (y3 - y1) / h
  3463.  
  3464.  adjx,adjy = 0,0
  3465.  ox,oy = 0,0
  3466.  if resize == 1 then
  3467.   mx = math.ceil(math.max(math.abs(x1-hub_x),math.abs(x3-hub_x))) * 2 + 2
  3468.   my = math.ceil(math.max(math.abs(y1-hub_y),math.abs(y3-hub_y))) * 2 + 2
  3469.    if target == 3 then -- Center gfx at Brush-to-Picture
  3470.     adjx = -mx/2
  3471.     adjy = -my/2
  3472.    end
  3473.   ox = (mx - w) / 2
  3474.   oy = (my - h) / 2
  3475.   trg.setsize(mx,my)
  3476.  end
  3477.  
  3478.  trg.clear(0)
  3479.  
  3480.  for y = -oy, h-1+oy, 1 do
  3481.    RE,GE,BE = 0,0,0
  3482.    for x = -ox, w-1+ox, 1 do
  3483.    
  3484.     xp = x1 + dX * x + dXs * y
  3485.     yp = y1 + dY * x + dYs * y
  3486.  
  3487.     if mode == 2 or mode == 3 then
  3488.       r,g,b,pal = bilinear(xp,yp,w,h,trg.get, mode_co)
  3489.       if spritemode == 1 then
  3490.        c = db.getBestPalMatchHYBRID({r+RE,g+GE,b+BE},pal,0.65,true) -- Brightness do very little in general with 4 set colors
  3491.         else c = matchcolor2(r+RE,g+GE,b+BE)
  3492.       end
  3493.        else c = trg.get(xp,yp)
  3494.     end
  3495.  
  3496.      --rn,gn,bn = getcolor(c)
  3497.      --RE = (r - rn)*0.5
  3498.      --GE = (g - gn)*0.5
  3499.      --BE = (b - bn)*0.5
  3500.  
  3501.      trg.put(x+ox+xoffset+adjx,y+oy+yoffset+adjy, c)
  3502.    
  3503.   end
  3504.    if update == 1 then
  3505.     statusmessage("Working... %"..math.floor(((y+oy) / (h-1+2*oy))*100))
  3506.     updatescreen(); if (waitbreak(0)==1) then return; end
  3507.    end
  3508.  end
  3509.  
  3510. end; -- doRotation
  3511.  
  3512. -------------------------------------------------------------------------------
  3513.  
  3514. --
  3515. -- PARTICLE v1.0
  3516. --
  3517. -- Draw Sphere or Disc to any target with gradients that may fade to background
  3518. --
  3519. -- type: "sphere" - volmetric planet-like disc
  3520. --       "disc"   - plain disc
  3521. -- mode: "blend"  - mix graphics with background color
  3522. --       "add"    - add graphics to background color
  3523. -- wd,ht:         - Max Width/Height of drawing area, i.e. screen size (needed if drawing to an array-buffer)
  3524. -- sx,sy:         - drawing coordinates (center)
  3525. -- xrad,yrad:     - x & y radii
  3526. -- rgba1:         - rgb+alpha array of center color: {r,g,b,a}, alpha is 0..1 where 1 is no transparency, Extreme rgb-values allowed
  3527. -- rgba2:         - rgb+alpha array of edge color: {r,g,b,a}, alpha is 0..1 where 1 is no transparency, Extreme rgb-values allowed
  3528. -- update_flag:   - Display rendering option (and add break feature)
  3529. -- f_get:         - Get pixel function: use getpicturepixel if reading from image (set null for image default)
  3530. -- f_put:         - Put pixel function: use putpicturepixel if drawing to image (set null for image default)
  3531. -- f_recur:       - Optional custom control-function for recursion (set null if not used)
  3532. -- recur_count    - Recursion depth counter, for use in combination with a custom function (f_recur), 0 as default
  3533. --
  3534. -- 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)
  3535. --
  3536. function db.particle(type,mode,wd,ht,sx,sy,xrad,yrad,rgba1,rgba2, update_flag, f_get, f_put, f_recur, recur_count)
  3537.  
  3538.  local x,y,rev,dix,diy,r3,g3,b3,px,py,alpha,ralpha,add,q,rgb,rgb1,rgb2,rgb3,n,def_get,def_put
  3539.  
  3540.   function def_get(x,y)
  3541.    local r,g,b  
  3542.     r,g,b = getcolor(getpicturepixel(x,y));
  3543.     return {r,g,b}
  3544.   end
  3545.   function def_put(x,y,r,g,b)     putpicturepixel(x,y, matchcolor2(r,g,b,0.65)); end
  3546.   if f_get == null then f_get = def_get; end
  3547.   if f_put == null then f_put = def_put; end
  3548.  
  3549.  q = {[true] = 1, [false] = 0}
  3550.  
  3551.  rgb,rgb1,rgb2 = {},{},{}
  3552.  
  3553.  if mode == 'blend' then
  3554.    add = 1
  3555.  end
  3556.  
  3557.  if mode == 'add' then
  3558.    add = 0
  3559.  end
  3560.  
  3561.  dix = xrad*2
  3562.  diy = yrad*2
  3563.  
  3564.  for y = 0, diy, 1 do
  3565.   py = sy+y-yrad; oy = y / diy;
  3566.   if (py >= 0 and py < ht) then
  3567.  
  3568.   for x = 0, dix, 1 do
  3569.    px = sx+x-xrad; ox = x / dix;
  3570.    if (px >= 0 and px < wd) then
  3571.  
  3572.    if type == 'sphere' then  -- Sphere
  3573.     a = math.sqrt(math.max(0,0.25 - ((0.5-ox)^2+(0.5-oy)^2))) * 2
  3574.    end
  3575.  
  3576.    if type == 'disc' then  -- Disc
  3577.     a = 1-math.sqrt((0.5-ox)^2+(0.5-oy)^2)*2
  3578.    end
  3579.  
  3580.    if a>0 then
  3581.  
  3582.     rev    = 1-a
  3583.     rgb3   = f_get(px,py)
  3584.     alpha  = rgba1[4] * a + rgba2[4] * rev
  3585.     ralpha = 1 - alpha * add
  3586.  
  3587.     for n = 1, 3, 1 do
  3588.      rgb1[n] = q[rgba1[n]==-1]*rgb3[n] + q[rgba1[n]~=-1]*rgba1[n] -- Fade from background?
  3589.      rgb2[n] = q[rgba2[n]==-1]*rgb3[n] + q[rgba2[n]~=-1]*rgba2[n] -- Fade to background?
  3590.      rgb[n] = (rgb1[n] * a + rgb2[n] * rev) * alpha + rgb3[n] * ralpha
  3591.     end
  3592.  
  3593.     f_put(px, py, rgb[1],rgb[2],rgb[3]);
  3594.    
  3595.    end
  3596.  
  3597.   end -- if x is good
  3598.   end -- x
  3599.   if update_flag then updatescreen(); if (waitbreak(0)==1) then return; end; end
  3600.   end -- if y is good
  3601.  end -- y
  3602.  
  3603.  if f_recur ~= null then  -- recursion
  3604.   f_recur(type,mode,wd,ht,sx,sy,xrad,yrad,rgba1,rgba2, update_flag, f_get, f_put, f_recur, recur_count);
  3605.   updatescreen(); if (waitbreak(0)==1) then return; end;
  3606.  end
  3607.  
  3608. end
  3609. -- eof PARTICLE
  3610.  
  3611.  
  3612.   --
  3613.   -- MedianCut a larger palette-list from a MathScene to produce a high-quality BriSorted palette for the final render/colormatching
  3614.   --
  3615.   function db.makeSamplePal(w,h,colors,frend)
  3616.    local n,x,y,r,g,b,pal
  3617.    n,pal = 0,{}
  3618.    for y = 0, h, math.ceil(h/63) do
  3619.    for x = 0, w, math.ceil(w/63) do
  3620.      r,g,b = frend(x,y,w,h)
  3621.      n = n+1
  3622.      r,g,b = db.rgbcapInt(r,g,b,255,0)
  3623.      pal[n] = {r,g,b,0}
  3624.     end;end
  3625.    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
  3626.   end
  3627.   --
  3628.  
  3629. --
  3630. -- Backdrop/Gradient Render (May be written to a matrix for rendering with db.fsrender)
  3631. --
  3632.  function db.backdrop(p0,p1,p2,p3,fput,ip_mode) -- points:{x,y,r,g,b}, IpMode "linear" is default
  3633.  
  3634.    local x,y,ox,oy,xr,yr,r,g,b,ax,ay,w,h
  3635.  
  3636.    ax,ay = p0[1],p0[2]
  3637.  
  3638.    w = p1[1] - p0[1]
  3639.    h = p2[2] - p0[2]
  3640.  
  3641.   for y = 0, h, 1 do -- +1 to fill screen with FS-render
  3642.  
  3643.     oy = y/h
  3644.     if ip_mode == "cosine" then oy = 1 - (math.cos(oy * math.pi) + 1)/2; end
  3645.     yr = 1 - oy
  3646.  
  3647.    for x = 0, w, 1 do
  3648.  
  3649.     ox = x/w
  3650.     if ip_mode == "cosine" then ox = 1 - (math.cos(ox * math.pi) + 1)/2; end
  3651.     xr = 1 - ox
  3652.  
  3653.     r = (p0[3]*xr + p1[3]*ox)*yr + (p2[3]*xr + p3[3]*ox)*oy;
  3654.     g = (p0[4]*xr + p1[4]*ox)*yr + (p2[4]*xr + p3[4]*ox)*oy;
  3655.     b = (p0[5]*xr + p1[5]*ox)*yr + (p2[5]*xr + p3[5]*ox)*oy;
  3656.  
  3657.     fput(x+ax,y+ay,r,g,b)
  3658.  
  3659.    end;end
  3660.  
  3661.   end
  3662. -- eof backdrop
  3663.  
  3664.  
  3665.  
  3666. --
  3667. -- SPLINES --
  3668. --
  3669. function db.splinePoint(x0,y0,x1,y1,x2,y2,points,point)
  3670.  local x,y,sx1,sy1,sx2,sy2,f
  3671.  
  3672.   f = point * 1 / points
  3673.  
  3674.   sx1 = x0*(1-f) + x1*f  
  3675.   sy1 = y0*(1-f) + y1*f
  3676.  
  3677.   sx2 = x1*(1-f) + x2*f  
  3678.   sy2 = y1*(1-f) + y2*f
  3679.  
  3680.   x = sx1 * (1-f) + sx2 * f
  3681.   y = sy1 * (1-f) + sy2 * f
  3682.  
  3683.   return x,y
  3684. end
  3685. --
  3686.  
  3687.  
  3688. -- zx = 2*x1 - (x0+x2)/2
  3689. --
  3690. function db.drawSplineSegment(x0,y0,x1,y1,x2,y2,x3,y3,points,col) -- Does spline segment p1-p2
  3691.  local n,x,y,sx1,sy1,sx2,sy2,mid,zx1,zy1,zx2,zy2,fx,fy
  3692.  
  3693.  mid = math.floor(points / 2)
  3694.  -- Extended Bezier points
  3695.  zx1 = 2*x1 - (x0+x2)/2  
  3696.  zy1 = 2*y1 - (y0+y2)/2
  3697.  zx2 = 2*x2 - (x1+x3)/2
  3698.  zy2 = 2*y2 - (y1+y3)/2
  3699.  
  3700.  fx,fy = x1,y1 -- Segment to be drawn (0),1 - 2,(3)
  3701.  for n = 0, mid, 1 do
  3702.  
  3703.   f = n * 1 / points * 2
  3704.  
  3705.   sx1,sy1 = db.splinePoint(x0,y0,zx1,zy1,x2,y2,mid*2, mid + n)
  3706.   sx2,sy2 = db.splinePoint(x1,y1,zx2,zy2,x3,y3,mid*2, n)
  3707.  
  3708.   x = sx1 * (1-f) + sx2 * f
  3709.   y = sy1 * (1-f) + sy2 * f
  3710.  
  3711.   --putpicturepixel(x,y,col)
  3712.   db.line(fx,fy,x,y,col)
  3713.   fx,fy = x,y
  3714.  
  3715.  end
  3716.  
  3717. end
  3718. --
  3719.  
  3720. -- eof Splines
  3721.