Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
6581 | leency | 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 |
||
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 |
||
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 |
||
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 |
||
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 |
||
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>>>=>=>=>>=>>>4>>>250>=stp;>=deg;>>>=>>>>>>>>>=>=>=>>>>=>>>>>256)>256>256>=>>>> |