Subversion Repositories Kolibri OS

Rev

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=0 and xx
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