diff --git a/share/grafx2/scripts/ani_db_3DPalette.lua b/share/grafx2/scripts/ani_db_3DPalette.lua
new file mode 100644
index 00000000..bf119e65
--- /dev/null
+++ b/share/grafx2/scripts/ani_db_3DPalette.lua
@@ -0,0 +1,431 @@
+--3D-Palette viwer V0.7 (HSL-models added, 3D-World added, Pen-color only cycles thru unique colors, InputBox)
+--by Richard 'Dawnbringer' Fhager
+
+-- Mouse:	Rotate Cube (Stops animation)
+-- Arrow-keys:	Move Cube (in 3D world)
+-- F1:		Start/Stop animation 
+-- F2:		Reset
+-- F3:		Increase Color-Size
+-- F4:		Decrease Color-Size
+-- F5:		(Wip) Cycle thru selected PenColor (Note that only unique colors are displayed)
+-- F9:		RGB-space model
+--F10:		HSL-space model
+--F11:		HSLcubic-space model
+-- "+" (Num):	Zoom In
+-- "-" (Num):   Zoom Out
+-- Esc:		Exit script
+
+require("dawnbringer_lib")
+
+
+BRIDIAG_SHOW = 1     -- Show brightness/Grayscale diagonal (1 = on, 0 = off)
+ANIM         = 1     -- Animation (1 = on, 0 = off)
+BOX_DRK      = 8     -- Darkest   color used for box (0-255)
+BOX_BRI      = 112   -- Brightest color used for box (0-255)
+COLSIZE_BASE = 26    -- Colors base size (value to adjusted by palette-size, with 2 cols maxsize is v / 1.23)
+
+--
+OK,RGB,HSL,HSLC,BOX_BRI,COLSIZE_BASE,SET800x600 = inputbox("3D-Palette Viever Settings",
+                        
+                           "1. RGB space [F9]",       1,  0,1,-1,
+                           "2. HSL space [F10]",      0,  0,1,-1,
+                           "3. HSL-cubic space [F11]",0,  0,1,-1,
+                           "Box Brightness (16-255)", BOX_BRI,   16,255,0,  
+                           "Col Size (1-100) [F3/F4]", COLSIZE_BASE,   1,100,0,                     
+                           "Set Screen to 800x600",      1,0,1,0
+                                                                        
+);
+--
+
+if OK then
+
+ if SET800x600 == 1 then setpicturesize(800,600); end
+
+ SPACE = "rgb"
+ FORM  = "cube"
+ if HSL == 1 then
+  SPACE = "hsl"
+  FORM  = "cylinder"
+ end
+ if HSLC == 1 then
+  SPACE = "hsl_cubic"
+  FORM  = "cube"
+ end
+
+
+pal = db.fixPalette(db.makePalList(256))
+
+FG = getforecolor() 
+BG = getbackcolor()
+
+palcol = FG
+
+--
+function initColors(space)
+  for n = 1, #pal, 1 do 
+   c = pal[n]; 
+   if space == "rgb" then
+   cols[n] = {c[1]/128-1,c[2]/128-1,c[3]/128-1,c[4]};
+  end
+  if space == "hsl_cubic" then
+   cols[n] = {}
+   cols[n][1] = (db.getHUE(c[1],c[2],c[3],0) / 6.0 * 255) / 128 - 1
+   cols[n][2] = (db.getSaturation(c[1],c[2],c[3])) / 128 - 1
+   cols[n][3] = (db.getLightness(c[1],c[2],c[3])) / 128 - 1
+   cols[n][4] = c[4]
+  end
+  if space == "hsl" then
+   cols[n] = {}
+   hue = db.getHUE(c[1],c[2],c[3],0) / 6.0 * math.pi*2
+   rad = db.getSaturation(c[1],c[2],c[3]) / 256
+   cols[n][1] = math.cos(hue) * rad
+   cols[n][2] = math.sin(hue) * rad
+   cols[n][3] = (db.getLightness(c[1],c[2],c[3])) / 128 - 1
+   cols[n][4] = c[4]
+  end
+ end
+end
+--
+
+cols = {} -- Make points of palette colors
+colz = {} -- To hold calculated points
+initColors(SPACE)
+
+
+function initPointsAndLines(form,bridiag)
+ if form == "cube" then 
+  pts = {{-1,1,-1},{1,1,-1},{1,-1,-1},{-1,-1,-1}, -- The box
+        {-1,1, 1},{1,1, 1},{1,-1, 1},{-1,-1, 1}}
+  lin = {{1,2},{2,3},{3,4},{4,1},{5,6},{6,7},{7,8},{8,5},{1,5},{2,6},{3,7},{4,8}} -- Box Lines
+  if bridiag == 1 then lin[13] = {4,6}; end
+ end
+ if form == "cylinder" then
+  p = 28
+  pts = {}
+  lin = {}
+  for n = 1, p, 1 do
+   x = math.cos(math.pi*2 / p * (n-1))
+   y = math.sin(math.pi*2 / p * (n-1))
+   pts[n] = {x,y,-1}
+   lin[n] = {n,1 + (n%p)}
+   pts[n + p] = {x,y,1}
+   lin[n + p] = {n+p,p + 1 + (n%p)}
+  end
+  lin[p*2+1] = {1,p+1} -- Red (0 degrees)
+  lin[p*2+2] = {p+1,p+1+math.ceil(p/2)} -- Lightness end (needs an even # of points to work)
+ end
+end
+
+boxp = {} -- To hold the calculated points
+initPointsAndLines(FORM,BRIDIAG_SHOW)
+
+w,h = getpicturesize()
+CX,CY = w/2, h/2
+
+
+
+function initAndReset()
+ XANG, YANG, ZANG, ZOOM, COLSIZE_ADJ, XD, YD, WORLD_X, WORLD_Y = 0,0,0,0,0,0,0,0,0
+end
+
+initAndReset()
+
+SIZE = math.min(w,h)/4
+DIST = 5 -- Distance perspective modifier, ~5 is nominal, more means "less 3D" 
+
+CMAXSIZE = math.floor(COLSIZE_BASE / ((#pal)^0.3))
+--CMAXSIZE = 8
+CMINSIZE = 1 -- Negative values are ok. Color are never smaller than 1 pix
+
+BOX_LINE_DIV = 20 -- Number of colors/segments that a box-line can be divided into (depth)
+BOX_DIV_MULT = BOX_LINE_DIV / (math.sqrt(3)*2)
+
+-- Box depth colors
+box_div = {}
+for n = 0, BOX_LINE_DIV-1, 1 do
+ c = BOX_DRK + (BOX_BRI / (BOX_LINE_DIV - 1)) * n 
+ --box_div[BOX_LINE_DIV - n] = matchcolor(c,c,c)
+ box_div[BOX_LINE_DIV - n] = db.getBestPalMatchHYBRID({c,c,c},pal,0.5,true)
+end
+
+--BOX_COL = matchcolor(80,80,80)
+BKG_COL = matchcolor(0,0,0)
+--CUR_COL = matchcolor(112,112,112)
+
+
+ function rotate3D(x,y,z,Xsin,Ysin,Zsin,Xcos,Ycos,Zcos) -- PrecCalced cos&sin for speed
+
+   local x1,x2,x3,y1,y2,y3,f,xp,yp
+
+    x1 = x
+    y1 = y * Xcos + z * Xsin
+    z1 = z * Xcos - y * Xsin
+
+    x2 = x1 * Ycos - z1 * Ysin
+    y2 = y1
+    z2 = x1 * Ysin + z1 * Ycos
+
+    x3 = x2 * Zcos - y2 * Zsin
+    y3 = x2 * Zsin + y2 * Zcos
+    z3 = z2
+
+    return x3,y3,z3
+  end
+
+ function do3D(x,y,z,zoom,dist,Xsin,Ysin,Zsin,Xcos,Ycos,Zcos) -- PrecCalced cos&sin for speed
+
+   local x1,x2,x3,y1,y2,y3,f,xp,yp
+
+    x1 = x
+    y1 = y * Xcos + z * Xsin
+    z1 = z * Xcos - y * Xsin
+
+    x2 = x1 * Ycos - z1 * Ysin
+    y2 = y1
+    z2 = x1 * Ysin + z1 * Ycos
+
+    x3 = x2 * Zcos - y2 * Zsin
+    y3 = x2 * Zsin + y2 * Zcos
+    z3 = z2
+
+    f = dist/(z3 + dist + zoom) 
+    xp = x3 * f
+    yp = y3 * f
+
+    return xp,yp,z3
+  end
+
+
+function draw3Dline(x1,y1,z1,x2,y2,z2,div,mult,depthlist)
+   local s,xt,yt,xd,yd,zd,xf,yf
+   xd = (x2 - x1) / div
+   yd = (y2 - y1) / div
+   zd = (z2 - z1) / div
+   xf,yf = x1,y1
+
+  for s = 1, div, 1 do
+    -- Depth assumes a 1-Box (z ranges from -sq(3) to sq(3))
+    depth = math.floor(1 + (z1+zd*s + 1.732) * mult) 
+    xt = x1 + xd*s -- + math.random()*8
+    yt = y1 + yd*s -- + math.random()*8
+    c = depthlist[depth]
+    if c == null then c = 1; end -- Something isn't perfect, error is super rare but this controls it
+    drawline(xf,yf,xt,yt,c)
+    xf = xt
+    yf = yt
+  end
+end
+
+function killinertia()
+ XD = 0
+ YD = 0
+end
+
+ -- If using 1-box, z is -sq(3) to sq(3)
+ minz = math.sqrt(3)
+ totz = minz * 2
+ maxrad = CMAXSIZE - CMINSIZE
+
+q = 0
+delay = 4
+move = 0.03
+
+while 1 < 2 do
+
+ -- Time-for-space-wiggle...or somekindof attempt
+ --WORLD_X = -move
+ --q = (q + 1) % delay
+ --if q < delay/2 then WORLD_X = move; end
+
+ clearpicture(BKG_COL)
+
+ Xsin = math.sin(XANG); Xcos = math.cos(XANG)
+ Ysin = math.sin(YANG); Ycos = math.cos(YANG)
+ Zsin = math.sin(ZANG); Zcos = math.cos(ZANG) 
+
+ -- Rotate Box points
+ for n = 1, #pts, 1 do
+  p = pts[n]
+  x,y,z = p[1],p[2],p[3]
+  XP,YP,zp = rotate3D(x,y,z,Xsin,Ysin,Zsin,Xcos,Ycos,Zcos)
+  boxp[n] = {XP,YP,zp}
+ end
+
+ -- Rotate Colors in palette
+ for n = 1, #cols, 1 do
+  p = cols[n]
+  x,y,z,c = p[1],p[2],p[3],p[4]
+  XP,YP,zp = rotate3D(x,y,z,Xsin,Ysin,Zsin,Xcos,Ycos,Zcos)
+  colz[n] = {XP,YP,zp,c} 
+ end
+
+ ------------------------------------
+ -- Control world
+ ------------------------------------
+
+  -- Calculate points anew
+
+ -- Worldize Box points
+ for n = 1, #boxp, 1 do
+  s = SIZE
+  v = boxp[n]
+  x = v[1] + WORLD_X
+  y = v[2] + WORLD_Y
+  z = v[3]
+  f = DIST/(z + DIST + ZOOM)
+  XP = CX + x * f * s 
+  YP = CY + y * f * s
+  boxp[n] = {XP,YP,z}
+ end
+
+ -- Worldize Colors in palette
+ for n = 1, #colz, 1 do
+  s = SIZE
+  v = colz[n]
+  x = v[1] + WORLD_X
+  y = v[2] + WORLD_Y
+  z = v[3]
+  c = v[4]
+  f = DIST/(z + DIST + ZOOM)
+  XP = CX + x * f * s 
+  YP = CY + y * f * s
+  colz[n] = {XP,YP,z,c} 
+ end
+
+
+-------------------------------------
+-------------------------------------
+
+ -- Brightness Diagonal
+ --if BRIDIAG_SHOW == 1 then
+ -- p1 = boxp[4]
+ -- p2 = boxp[6]
+ -- x1,y1,z1 = p1[1],p1[2],p1[3]
+ -- x2,y2,z2 = p2[1],p2[2],p2[3]
+ -- draw3Dline(x1,y1,z1,x2,y2,z2,BOX_LINE_DIV,BOX_DIV_MULT,box_div)
+ --end
+
+ -- sort on z
+ db.sorti(colz,3) 
+
+ -- Draw colors
+ for n = #colz, 1, -1 do
+  p = colz[n]
+  XP,YP,zp,c = p[1],p[2],p[3],p[4]
+
+  radius = CMINSIZE + maxrad - (zp+minz) / totz * maxrad
+  dorad = math.floor(radius - ZOOM*2 +  COLSIZE_ADJ) 
+
+  if dorad >= 1 then
+   drawdisk(XP,YP,dorad,c) 
+   --db.drawRectangle(XP,YP,dorad,dorad,c)
+   else putpicturepixel(XP,YP,c)
+  end
+
+  if c == FG or c == BG then
+   sz = math.max(3,dorad + 3)
+   if c == BKG_COL then v = (c+128) % 255; c = matchcolor(v,v,v); end
+   db.drawRectangleLine(XP-sz,YP-sz,sz*2,sz*2,c)
+  end
+
+ end -- colz
+
+
+
+ -- Draw box
+ for n = 1, #lin, 1 do
+  
+  l = lin[n]
+  p1 = boxp[l[1]]
+  p2 = boxp[l[2]]
+  x1,y1,z1 = p1[1],p1[2],p1[3]
+  x2,y2,z2 = p2[1],p2[2],p2[3]
+  draw3Dline(x1,y1,z1,x2,y2,z2,BOX_LINE_DIV,BOX_DIV_MULT,box_div)
+
+ end -- eof box
+
+ --updatescreen(); if (waitbreak(0.00)==1) then return; end
+
+ repeat
+
+    old_key = key;
+    old_mouse_x = mouse_x;
+    old_mouse_y = mouse_y;
+    old_mouse_b = mouse_b;
+    
+    updatescreen()
+    moved, key, mouse_x, mouse_y, mouse_b = waitinput(0)
+    
+    if mouse_b == 1 then ANIM = 0; end
+
+    if (key==27) then
+       return;
+    end
+
+    if (key==282) then  ANIM = (ANIM+1) % 2;  end -- F1: Stop/Start Animation
+    if (key==283) then  initAndReset();       end -- F2: Reset all values
+    if (key==284) then  COLSIZE_ADJ = COLSIZE_ADJ + 0.5;       end -- F3
+    if (key==285) then  COLSIZE_ADJ = COLSIZE_ADJ - 0.5;       end -- F4
+
+    if (key==286) then  
+      --FG = (FG + 1) % 255; 
+      palcol = (palcol + 1) % #pal 
+      FG = pal[palcol+1][4]
+      setforecolor(FG);
+      setcolor(0,getcolor(0))  -- Force update of palette until setforecolor() is fixed
+     end -- F5
+
+    if (key==290) then -- F9
+     initColors("rgb")
+     initPointsAndLines("cube",BRIDIAG_SHOW)
+    end
+    if (key==291) then -- F10
+     initColors("hsl")
+     initPointsAndLines("cylinder", 0) -- Bridiag won't show even if turned on, it's only for cube
+    end
+    if (key==292) then -- F11
+     initColors("hsl_cubic")
+     initPointsAndLines("cube",BRIDIAG_SHOW)
+    end
+
+    if (key==269) then  ZOOM = ZOOM + 0.1;  end
+    if (key==270) then  ZOOM = ZOOM - 0.1;  end
+    SPEED = math.pi / 100
+ 
+
+    if (key==273) then WORLD_Y = WORLD_Y - 0.05; killinertia();  end
+    if (key==274) then WORLD_Y = WORLD_Y + 0.05; killinertia();  end
+
+    if (key==276) then WORLD_X = WORLD_X - 0.05; killinertia();  end
+    if (key==275) then WORLD_X = WORLD_X + 0.05; killinertia();  end
+   
+  until ((mouse_b == 1 and (old_mouse_x~=mouse_x or old_mouse_y~=mouse_y)) or key~=0 or ANIM==1 or math.abs(XD)>0.01 or math.abs(YD)>0.01);
+
+ if ANIM == 0 then
+  if (mouse_b==1 and (old_mouse_x~=mouse_x or old_mouse_y~=mouse_y)) then -- Inertia
+   XD = (mouse_y - old_mouse_y)*0.005
+   YD = (mouse_x - old_mouse_x)*0.005
+    else
+     XD = XD*0.92
+     YD = YD*0.92
+  end
+  XANG = ((XANG - XD) % (math.pi*2));
+  YANG = ((YANG + YD) % (math.pi*2));
+  ZANG = 0
+ end 
+
+ if ANIM == 1 then
+    XANG = (XANG + math.pi/300) % (math.pi*2)
+    YANG = (YANG + math.pi/500) % (math.pi*2)
+    ZANG = (ZANG + math.pi/1000) % (math.pi*2)
+ end
+
+ --XANG = ((CY-mouse_y) / 200  % (math.pi*2));
+  --YANG = ((mouse_x - CX) / 200  % (math.pi*2));
+  --ZANG = 0
+
+
+  statusmessage("X: "..math.floor(XANG*57.3).."°, Y: "..math.floor(YANG*57.3).."°, Z: "..math.floor(ZANG*57.3).."°, Zoom: "..math.floor(-ZOOM*10).."   ")
+end
+
+end -- OK
diff --git a/share/grafx2/scripts/bru_db_ApplyColor.lua b/share/grafx2/scripts/bru_db_ApplyColor.lua
new file mode 100644
index 00000000..94db376e
--- /dev/null
+++ b/share/grafx2/scripts/bru_db_ApplyColor.lua
@@ -0,0 +1,125 @@
+--BRUSH Remap: Apply PenColor
+--by Richard Fhager 
+--http://hem.fyristorg.com/dawnbringer/
+
+-- Copyright 2010 Richard Fhager
+--
+-- This program is free software; you can redistribute it and/or
+-- modify it under the terms of the GNU General Public License
+-- as published by the Free Software Foundation; version 2
+-- of the License. See 
+
+--dofile("dawnbringer_lib.lua")
+require("dawnbringer_lib")
+
+OK,tin,clz,fade,amt,brikeep,falloff,nobg,briweight   = inputbox("Apply PenColor 2 Brush",
+                        
+                           "1. Tint",               1,  0,1,-1,
+                           "2. Colorize",           0,  0,1,-1,
+                           "BG->FG color Fade", 0,  0,1,0, 
+                           "AMOUNT % (0-100)", 100,  0,100,0,  
+                           "Preserve Brightness", 1,  0,1,0,
+                           "Bri/Dark FallOff", 1,  0,1,0,
+                           "Exclude Background",     1,0,1,0,
+                           "ColMatch Bri-Weight %", 25,  0,100,0                                                     
+);
+
+
+if OK == true then
+
+ function cap(v) return math.min(255,math.max(v,0)); end
+
+ w, h = getbrushsize()
+
+
+ fg = getforecolor()
+ bg = getbackcolor()
+ fR,fG,fB = getcolor(fg)
+ bR,bG,bB = getcolor(bg)
+
+ pal = db.fixPalette(db.makePalList(256))
+ if nobg == 1 then
+  pal = db.stripIndexFromPalList(pal,bg) -- Remove background color from pallist
+ end
+
+ amtA = amt / 100
+ amtR = 1 - amtA
+
+  -- Normalize Pen Color
+  lev = (fR+fG+fB)/3
+  fR = fR - lev
+  fG = fG - lev
+  fB = fB - lev
+
+ ---------------------------------------------------
+ -- Colorize (Colourant) (just apply colorbalance)
+ -- Tint (make grayscale and apply colorbalance)
+ --
+ -- I think it should be the other way around since colorize is the process of adding color to B&W film...
+ -- But this is the what Brilliance and others call it 
+ --
+ if clz == 1 or tin == 1 then
+  cols = {}
+  for n = 0, 255, 1 do
+
+  r,g,b = getcolor(n)
+  a = db.getBrightness(r,g,b)
+
+    
+  mR,mG,mB = fR,fG,fB
+
+  -- Fade between bg & fg pencolor across dark-bright
+  if fade == 1 then
+    lf = a / 255
+    lr = 1 - lf
+    mR = bR*lr + fR*lf
+    mG = bG*lr + fG*lf
+    mB = bB*lr + fB*lf
+    lev = (mR+mG+mB)/3
+    mR = mR - lev
+    mG = mG - lev
+    mB = mB - lev
+  end   
+
+  fr,fg,fb = mR,mG,mB
+  
+
+     if brikeep == 1 then
+       -- Loose Brightness preservation (ex: applying full red to dark colors)
+       brin = db.getBrightness(cap(r+mR),cap(g+mG),cap(b+mB))
+       itot = brin - a
+       fr = mR - itot 
+       fg = mG - itot
+       fb = mB - itot
+     end 
+
+     -- Falloff (Effect weakens at dark and bright colors)
+     if falloff == 1 then
+      fo =  1 - math.abs((a - 127.5)/127.5)^2
+      fr = fr * fo
+      fg = fg * fo
+      fb = fb * fo
+     end
+
+     if tin == 1 then
+      --cols[n+1] = matchcolor((a+fr)*amtA + r*amtR, (a+fg)*amtA + g*amtR, (a+fb)*amtA + b*amtR)
+      cols[n+1] = db.getBestPalMatchHYBRID({(a+fr)*amtA+r*amtR, (a+fg)*amtA + g*amtR, (a+fb)*amtA + b*amtR},pal,briweight / 100,true)
+     end
+     if clz == 1 then
+      --cols[n+1] = matchcolor((r+fr)*amtA + r*amtR, (g+fg)*amtA + g*amtR, (b+fb)*amtA + b*amtR)
+      cols[n+1] = db.getBestPalMatchHYBRID({(r+fr)*amtA+r*amtR, (g+fg)*amtA + g*amtR, (b+fb)*amtA + b*amtR},pal,briweight / 100,true)
+     end
+  end
+
+  if nobg == 1 then cols[getbackcolor()+1] = getbackcolor(); end
+
+  for x = 0, w - 1, 1 do
+   for y = 0, h - 1, 1 do
+    putbrushpixel(x, y, cols[getbrushpixel(x,y) + 1]);
+  end
+ end
+end; 
+-- eof Colorize & Tint
+--------------------------------------------------------
+
+end -- OK
diff --git a/share/grafx2/scripts/libs/dawnbringer_lib.lua b/share/grafx2/scripts/libs/dawnbringer_lib.lua
new file mode 100644
index 00000000..541a5cb0
--- /dev/null
+++ b/share/grafx2/scripts/libs/dawnbringer_lib.lua
@@ -0,0 +1,2471 @@
+--DawnBringer function library v1.1
+--** THIS IS NOT A RUNNABLE SCRIPT! **
+--by Richard Fhager 
+-- http://hem.fyristorg.com/dawnbringer/
+-- Email: dawnbringer@hem.utfors.se
+-- MSN:   annassar@hotmail.com
+--
+-- Many functions in here was adopted from Evalion, a Javascript codecrafting/imageprocessing project
+-- http://goto.glocalnet.net/richard_fhager/evalion/evalion.html 
+--
+--
+-- You may access these functions in your own scripts by loading this library,
+-- just add the follwing line as one of the first instructions:
+--
+-- require("dawnbringer_lib")
+--
+-- or
+--
+-- dofile("dawnbringer_lib.lua") 
+--
+--
+-- Note that the functions must be called with the full library object-name, "db.function_name..."
+--
+
+-- Global library object
+db = {} 
+
+
+-- *************************************
+-- ***      Text & Conversions       ***
+-- *************************************
+--
+--
+
+function db.rgb2HEX(r,g,b,prefix)
+  local c,n,s,t,z
+  c = {r,g,b}
+  z = {"0",""}
+  t = ""
+  for n = 1, 3, 1 do
+  s = string.upper(string.format("%x",c[n]))
+  t = t..z[#s]..s
+     --s = tonumber(c[n],16)
+     --t = t..s
+  end
+  return prefix..t
+end
+
+
+--
+-- ... eof Text & Conversions ...
+--
+
+
+
+-- *************************************
+-- ***    Custom Math Functions     ***
+-- *************************************
+--
+--
+function db.sign(v)
+    local s
+    s = 0
+    if v > 0 then s = 1; end
+    if v < 0 then s = -1; end
+    return s 
+end
+--
+
+--
+function db.rotation (rot_ang,hub_x,hub_y,x,y) -- Rotate coordinates x & y relative hub
+  local new_ang,dist,m,xd,yd,v; m = math 
+  xd=hub_x-x; 
+  yd=hub_y-y;
+  if (not(xd==0 and yd==0)) then
+   v = -90; if xd < 0 then v = 90; end
+   new_ang = m.atan(yd/xd) - (v+rot_ang) * m.pi/180;
+   dist = m.sqrt(xd*xd+yd*yd);
+   x = hub_x - m.sin(new_ang)*dist;
+   y = hub_y + m.cos(new_ang)*dist;
+  end
+  return math.floor(x),math.floor(y) -- For drawing purposes
+end
+--
+
+--
+-- ... eof Custom Math Functions ...
+--
+
+-- *************************************
+-- ***     Fractional Scenery        ***
+-- *************************************
+
+--
+function db.setSceneryPalette()
+ db.colorCigarr(10,28,true) -- 250 colors
+ setcolor(250, 208,48,48)
+ setcolor(251, 48,208,48)
+ setcolor(252, 48,48,208)
+ setcolor(253, 224,224,64)
+ setcolor(254, 224,64,224)
+ setcolor(255, 64,224,224)
+end
+--
+
+--
+function db.star(xf,yf,sx,sy,rgb,haz,out,lum)
+ local n,c,dist; c={}
+ dist = haz + out * math.sqrt((xf-sx)^2+(yf-sy)^2);
+ for n = 1, 3, 1 do c[n] = (rgb[n] * lum) / dist; end
+ return c;
+end
+--
+
+--
+function db.zoom(xf,yf,zoom,panx,pany) -- Zoom and Pan in a fractional coord-system
+  xf = (xf-0.5)/zoom + 0.5 + panx;
+  yf = (yf-0.5)/zoom + 0.5 + pany;
+  return xf,yf
+end
+--
+
+--
+function db.rotationFrac(rot_ang,hub_x,hub_y,x,y) -- Rotate coordinates x & y relative hub
+  local new_ang,dist,m,xd,yd,v; m = math 
+  xd=hub_x-x; 
+  yd=hub_y-y;
+  if (not(xd==0 and yd==0)) then
+   v = -90; if xd < 0 then v = 90; end
+   new_ang = m.atan(yd/xd) - (v+rot_ang) * m.pi/180;
+   dist = m.sqrt(xd*xd+yd*yd);
+   x = hub_x - m.sin(new_ang)*dist;
+   y = hub_y + m.cos(new_ang)*dist;
+  end
+  return x,y
+end
+--
+
+--
+function db.twirl(x,y,arms,trot,tpow,tang)
+ local b,ang,vx,vy,vr,m,deg,tw
+ m=math; deg=math.pi/180; tw=.5;
+ if (not(x==.5 and y==.5)) then
+  ang = m.atan((.5-y)/(.5-x)); 
+  b = 0; if (x>.5) then b = m.pi; end
+  vx = .5-x; vy = .5-y; vr = m.pow(m.sqrt(vx*vx+vy*vy),tpow);
+  tw = .5+m.sin(-tang*deg+vr*trot+(ang + b)*arms)*.5;
+ end
+ return tw;
+end
+--
+
+--- Alpha filters
+--
+function db.alpha1(x,y,amp) -- Coord, Amplify: 0..n
+ local p,a,xh,yh,m
+ xh=0.5-x; yh=0.5-y; m = math
+ p = m.pow(xh*xh+yh*yh,0.7);
+ a = m.cos(32*m.pi*p)*m.sin(8*m.pi*(xh+yh));
+ return 1 + (a * amp)
+end
+--
+
+--
+-- ... eof Fractional Scenery ...
+--
+
+-- *************************************
+-- ***    Custom Array Functions     ***
+-- *************************************
+--
+-- Ok, I don't know Lua that well (still unsure about some scopes & refs etc.)
+-- And some features may not be active in Grafx2. So, some of the follwing functions
+-- may exist in Lua/Grafx2...but since I'm not sure if and how they work - I'll prefer
+-- to add a set of my own of known performance.
+
+--
+function db.newArrayInit2Dim(xs,ys,val) 
+  local x,y,ary; ary = {}
+  for y = 1, ys, 1 do
+   ary[y] = {}
+   for x = 1, xs, 1 do
+     ary[y][x] = val
+   end
+  end
+  return ary
+end
+--
+
+--
+-- Merge two arrays into a NEW one: array_c = db.newArrayMerge(array_b,array_b) 
+--
+function db.newArrayMerge(a,b) 
+  local n,ary; ary = {}
+  for n = 1, #a, 1 do
+   ary[n] = a[n]
+  end
+  for n = 1, #b, 1 do
+   ary[n+#a] = b[n]
+  end
+  return ary
+end
+--
+
+--
+-- Generate a copy of an array with a new value added Last
+--
+function db.newArrayInsertLast(a,val) 
+  local n,ary; ary = {}
+  for n = 1, #a, 1 do
+   ary[n] = a[n]
+  end
+  ary[#a+1] = val
+  return ary
+end
+--
+
+--
+-- Generate a copy of an array with a new value added First
+--
+function db.newArrayInsertFirst(a,val) 
+  local n,ary; ary = {}
+  ary[1] = val
+  for n = 2, #a+1, 1 do
+   ary[n] = a[n-1]
+  end
+  return ary
+end
+--
+
+--
+function db.ary2txt(ary) -- One & two dimensions supported [a,b] -> "a,b". [[a,b],[c,d]] -> "a-b, c-d"
+ local t,n,m,v
+ t = ""
+ for n = 1, #ary, 1 do
+   if type(ary[n]) == "table" then 
+     t = t..ary[n][1]
+     for m = 2, #ary[n], 1 do
+       t = t.."-"..ary[n][m]
+     end
+   else t = t..ary[n]; 
+   end
+   t = t..", "
+ end
+ return t 
+end
+--
+
+
+--
+-- ... eof Custom Array Functions ...
+--
+
+
+-- *************************************
+-- ***   Misc. Logical Operations    ***
+-- *************************************
+
+--
+-- palList [r,g,b,palindex] is expected only to contain unique colors 
+-- index = -1 --> index of list
+--
+function db.makeIndexList(list,index)
+  local n,ilist
+  ilist = {}
+  for n = 1, #list, 1 do
+   if (index > 0) then ilist[n] = list[n][index]; end
+   if (index == -1) then ilist[n] = n; end
+  end
+  return ilist
+end
+--
+
+--
+-- Return a list of all possible (non-same) pairs from the entries in a list 
+-- [a,b,c] --> [[a,b],[a,c],[b,c]]
+-- (All entries are treated as unique. i.e it's only the INDEX that counts)
+-- mode = 0: Only unique pairs (m = (n^2 - n)/2), [a,b] --> [[a,b]]
+-- mode = 1: All pairs, i.e mirror versions as well. (m = n^2 - n), [a,b] --> [[a,b], [b,a]]
+--
+function db.pairsFromList(list,mode)
+ local a,b,l,n,pairs
+ pairs = {}
+ l = #list
+ n = 1
+ for a = 1, l, 1 do
+   for b = a+1, l, 1 do
+     pairs[n] = {list[a],list[b]}; n = n + 1
+     if mode == 1 then pairs[n] = {list[b],list[a]}; n = n + 1; end
+   end
+ end
+ return pairs
+end
+--
+
+function db.valueInArray(ary,val)
+ local n,res
+ res = false
+ for n = 1, #ary, 1 do
+   if ary[n] == val then res = true; break; end
+ end
+ return res
+end
+
+-- RAMP specific
+
+-- Remove initial pair (palList) colors from pallist
+function db.initiateRamp(pair,pallist,pal_index)
+  local n,found,plist
+  plist = {}
+
+  found = 1 
+  for n = 1, #pallist, 1 do
+   if db.valueInArray(pair,pallist[n]) == false then
+    plist[found] = pallist[n]; found = found + 1;
+   end
+  end
+
+  pair[pal_index] = plist -- ex: ["pal"]
+
+  return pair -- Is now a 2 color RAMP
+end
+--
+
+-- Remove new col entry from ramp's pallist and add it to the ramp, returns an updated ramp
+-- RampList = [1,2] ["pal"] = palList = [3,4,5], addindex = 3
+-- --> [1,2,3] palList = [4,5]
+function db.updateRamp(ramp,addindex,pal_index) 
+  local n,found,pallist,plist,newramp
+  plist = {}
+  pallist = ramp[pal_index]
+
+  -- New palList without added color to IndexList
+  found = 1 
+  for n = 1, #pallist, 1 do
+   if pallist[n] ~= addindex then
+    plist[found] = pallist[n]; found = found + 1;
+   end
+  end
+
+  newramp = db.newArrayInsertLast(ramplist,addindex) 
+  newramp[pal_index] = plist
+ 
+  return rlist
+end
+
+--
+-- Returns a list of all inital ramps from color pairs
+--
+-- Weeds out bad pairs, attaches remaining palette colors and the first rgb-vector
+--
+--
+function db.initiateRampList(pairs,pallist,pal_index,vec_index,min,maxmult,rw,gw,bw) 
+ local n,ramplist,newpairs,accept,dist,c1,c2,max,rD,gD,bD
+ ramplist = {}
+ max = min + (142 / math.sqrt(#pallist)) * maxmult -- min ex: 8-12
+ accept = 0
+
+ for n = 1, #pairs, 1 do
+  c1 = pallist[pairs[n][1]]
+  c2 = pallist[pairs[n][2]]
+  rD = c2[1] - c1[1]
+  gD = c2[2] - c1[2]
+  bD = c2[3] - c1[3]
+  dist = math.sqrt( (rw*rD)^2 + (gw*gD)^2 + (bw*bD)^2 ) 
+
+  if dist >= min and dist <= max then
+    accept = accept + 1; ramplist[accept] = db.initiateRamp(pairs[n],pallist,pal_index);
+    ramplist[accept][vec_index] = {rD, gD, bD, dist}; -- Add first color vector, ONLY KEEP DISTANCE?
+  end
+ end
+
+ return ramplist
+end
+
+
+function db.findRampExpansionColors(ramp)
+ local clist
+ clist = {}
+ -- Calculate vectors here?
+ return clist
+end
+
+
+function db.findRAMPS(min_len, max_len)
+ local i,n,c,pallist,ramp,ramplist,pairs,spairs,palindex,vecindex,found,donelist,newlist,dones
+ local colorlist
+ palindex = "pal"
+ vecindex = "vector"
+ pallist =  db.fixPalette(db.makePalList(256), 0)
+ pairs =    db.pairsFromList(db.makeIndexList(pallist,-1), 0)
+ ramplist = db.initiateRampList(pairs,pallist,palindex,vecindex, 8,0.75, 0.26,0.55,0.19) 
+
+ -- MIN_LEN = 5
+ -- MAX_LEN = 10
+
+ -- Split Ramp-build into two parts: 
+ -- 1. Build ramps >= MIN_LEN, NONE added to 'Done'
+ -- 2. Run til no more ramps can be expanded or reaches MAX_LEN, ALL ramps added to 'Done'
+
+ for i = 1, (min_len - 2), 1 do -- Assuming 2 for inital pairs (2 color ramps)
+  newlist = {}
+  found = 0
+  for n = 1, #ramplist, 1 do
+    ramp = ramplist[n]
+    colorlist = db.findRampExpansionColors(ramp) -- Colors that can split the current ramp into new expanded ramps
+    for c = 1, #colorlist, 1 do
+     found = found + 1; newlist[found] = db.updateRamp(ramp,colorlist[c],palindex); -- Ramp is expanded by 1 color
+    end
+  end
+  ramplist = newlist
+ end
+
+
+ donelist = {}; dones = 0
+
+ repeat
+  newlist = {}
+  found = 0
+  for n = 1, #ramplist, 1 do
+    ramp = ramplist[n]
+    if true == false then
+     found = found + 1; newlist[found] = db.updateRamp(ramp,color,palindex); 
+      else
+      dones = dones + 1; donelist[dones] = ramp;
+    end
+  end
+  --ramplist = newlist
+ until found == 0
+
+ return #pairs.." - "..#ramplist
+end
+
+--
+-- ... eof Misc. Logical Operations ...
+--
+
+
+-- ***************************************
+-- *** General RGB-Color Modifications ***
+-- ***************************************
+
+
+--
+function db.makeComplimentaryColor(r,g,b,brikeeplev) -- Lev: 0 = Normal, 1 = Loose, 2 = Strict
+
+ local bri_o,bri_n,bdiff
+
+ function cap(v) return math.max(0,math.min(v,255)); end
+
+ bri_o = db.getBrightness(r,g,b)
+ r,g,b = db.shiftHUE(r,g,b,180)
+
+ if brikeeplev > 0 then
+
+  for n = 0, brikeeplev*3-1, 1 do -- Must iterate to reduce brightness error
+    bri_n = db.getBrightness(r,g,b)
+    bdiff = (bri_o - bri_n) / 2 * brikeeplev
+    r = cap(r + bdiff)
+    g = cap(g + bdiff)
+    b = cap(b + bdiff)
+  end
+
+ end
+
+ return r,g,b
+
+end
+--
+
+
+-- *** Color balance ***
+--
+-- bri_flag:   Preserve brightness
+-- loose_flag: Loose preservation restrictions for brightness and balance
+--
+-- Jeez, was this a tricky sucker; color-balance is just adding and capping...
+-- but trying color-balance with preserved perceptual brightness is a different monster...
+-- ...so bad I could only solve it by iterative error correction.
+--
+function db.ColorBalance(r,g,b,rd,gd,bd,bri_flag,loose_flag) -- preserve brightness
+ local rw,gw,bw,ri,gi,bi,itot,rni,gni,bni,ro,go,bo,ovscale,lev,count,rt,gt,bt,rf,gf,bf,bri
+
+ -- Dawn 3.0, [0.26,0.55,0.19], 0-255 bri-colorscale adjust = 1.56905
+ rw,gw,bw = 0.26, 0.55, 0.19
+
+ function cap(v) return math.min(255,math.max(v,0)); end
+ 
+  bri = db.getBrightness(r,g,b)
+
+
+ -- Loose brightness & balance preservation, a good compromise.
+ if bri_flag == true and loose_flag == true then
+
+   lev = (rd + gd + bd) / 3
+   rd = rd - lev
+   gd = gd - lev
+   bd = bd - lev
+
+   brin = db.getBrightness(cap(r+rd),cap(g+gd),cap(b+bd))
+   itot = brin - bri
+   rd = rd - itot
+   gd = gd - itot
+   bd = bd - itot
+
+ end
+
+
+ if bri_flag == true and loose_flag == false then
+
+  itot = 255
+  count = 0
+
+   -- Normalize (Yup, it's right only to normalize once first..cont.norm. will have some counter-effect)
+  lev = (rd + gd + bd) / 3
+  rd = rd - lev
+  gd = gd - lev
+  bd = bd - lev
+
+ repeat
+
+  --messagebox("Norm:"..rd..", "..gd..", "..bd)
+
+  -- Calculate total brightness change
+  -- Note: Perceptual Brightness is exponential, and can't be delta-adjusted for anything other than greyscales.
+  -- Although the formula for the new brightness corrected normalization level can can be derived...
+  -- ...it doesn't do much good since the bigger problem is overflow outside the 0-255 boundary.
+  -- As for now, I see no other means to solve this issue than with iterative error-correction.
+
+  rt = r+rd 
+  gt = g+gd
+  bt = b+bd
+ 
+  itot = 9e99
+  rni = rd 
+  gni = gd 
+  bni = bd
+ 
+  -- We can get brightness of negative values etc. So bri-correction is put on hold until values are scaled down
+  if (rt>=0 and gt>=0 and bt>=0) and (rt<256 and gt<256 and bt<256) then
+    brin = db.getBrightness(rt,gt,bt)
+    itot = brin - bri
+    --messagebox("Bri Diff: "..itot)
+    -- Brightness adjusted balance
+     rni = rd - itot
+     gni = gd - itot
+     bni = bd - itot
+  end
+
+  --messagebox("Bri Adj Bal:"..rni..", "..gni..", "..bni)
+
+   -- Apply balance to find overflow (as fraction of the channel change)
+   ro = math.max( math.max((r + rni)-255,0), math.abs(math.min((r + rni),0)) ) / math.max(math.abs(rni),1)
+   go = math.max( math.max((g + gni)-255,0), math.abs(math.min((g + gni),0)) ) / math.max(math.abs(gni),1)
+   bo = math.max( math.max((b + bni)-255,0), math.abs(math.min((b + bni),0)) ) / math.max(math.abs(bni),1)
+
+  ovscale = 1 - math.max(ro,go,bo)
+
+  -- Scaling balances might be logically incorrect (as they can be seen as constant differences)
+  -- But scaling DOWN is quite harmless and I don't see how it could be done otherwise...
+  -- ex: +10 red, +5 blue: Scale x2   = +20 red, +10 blue -> More red over blue than ordered, a contrast behaviour.
+  --     +10 red, +5 blue: Scale x0.5 = +5 red, +2.5 blue -> Less of everything, but a part of the order. Harmless?
+  --
+  rd = rni * ovscale
+  gd = gni * ovscale  
+  bd = bni * ovscale
+
+  count = count + 1 
+
+  --messagebox("Final bal:"..rd..", "..gd..", "..bd)
+
+ until math.abs(itot) < 1 or count > 5
+
+ end 
+
+ rf = r + rd
+ gf = g + gd
+ bf = b + bd
+
+ --messagebox("Result color:"..rf..", "..gf..", "..bf)
+
+ return rf,gf,bf
+end
+--
+
+
+
+--
+-- bri_flag: Preserve brightness
+-- cap_flag: Cap new color at 0-255, has a desaturating effect for large values.
+--
+function db.ColorBalanceXXX(r,g,b,rd,gd,bd,bri_flag,cap_flag) -- preserve brightness
+ local rf,gf,bf
+
+ if cap_flag == true then
+  rd = math.min(255,math.max(0, r+rd)) - r
+  gd = math.min(255,math.max(0, g+gd)) - g
+  bd = math.min(255,math.max(0, b+bd)) - b
+ end
+
+ local rw,gw,bw,ri,gi,bi,itot,rni,gni,bni,ro,go,bo,ovscale
+
+
+ -- Dawn 3.0, [0.26,0.55,0.19], 0-255 bri-colorscale adjust = 1.56905
+ rw,gw,bw = 0.26, 0.55, 0.19
+
+ if bri_flag == true then
+
+  -- Calculate total brightness change
+  --ri = rd * rw
+  --gi = gd * gw
+  --bi = bd * bw
+  --itot = math.sqrt(ri^2 + gi^2 + bi^2) 
+
+  bri  = db.getBrightness(r,g,b)
+  brin = db.getBrightness(r+rd,g+gd,b+bd)
+  itot = brin - bri
+
+ 
+  -- Normalized and Brightness adjusted balance
+  rni = rd - itot
+  gni = gd - itot
+  bni = bd - itot
+
+  -- Apply balance to find overflow (as fraction of the channel change)
+  ro = math.max( math.max((r + rni)-255,0), math.abs(math.min((r + rni),0)) ) / math.max(math.abs(rni),1)
+  go = math.max( math.max((g + gni)-255,0), math.abs(math.min((g + gni),0)) ) / math.max(math.abs(gni),1)
+  bo = math.max( math.max((b + bni)-255,0), math.abs(math.min((b + bni),0)) ) / math.max(math.abs(bni),1)
+
+  ovscale = 1 - math.max(ro,go,bo)
+
+  rd = rni * ovscale
+  gd = gni * ovscale  
+  bd = bni * ovscale
+
+ end 
+
+ rf = r + rd
+ gf = g + gd
+ bf = b + bd
+
+ return rf,gf,bf
+end
+--
+
+--
+function db.getContrast(ch) -- Channel, returns fraction -1..0..1, negative for ch < 127.5
+ --return math.abs((ch / 127.5) - 1)
+ return (ch / 127.5) - 1
+end
+--
+
+--
+function db.getAvgContrast(r,g,b)
+ return (math.abs(db.getContrast(r)) + math.abs(db.getContrast(g)) + math.abs(db.getContrast(b))) / 3   
+end
+--
+
+--
+-- Mode = 0: Proportional - all colors reach max contrast at 100%
+--
+-- Mode = 1: Linear - percentage simply added
+-- 
+function db.changeContrastOLD(r,g,b,prc,mode)
+
+ local m,rd,gd,bd,rv,gv,bv,rc,gc,bc,base,sign
+
+ base = 1; sign = 1
+ if prc < 0 then base = 0; sign = -1; end -- decontrast
+
+ m = prc / 100 * sign
+
+ -- mode 0 
+ rc = db.getContrast(r)
+ rd = (base - math.abs(rc)) * m  * db.sign(rc)
+ rv = (rc+rd+1) * 127.5
+
+ gc = db.getContrast(g)
+ gd = (base - math.abs(gc)) * m  * db.sign(gc)
+ gv = (gc+gd+1) * 127.5
+
+ bc = db.getContrast(b)
+ bd = (base - math.abs(bc)) * m  * db.sign(bc)
+ bv = (bc+bd+1) * 127.5
+
+ return rv,gv,bv
+
+end
+--
+
+function db.changeContrast(r,g,b,prc) -- Photoshop style
+
+ local m,rd,gd,bd,rv,gv,bv,rc,gc,bc
+
+ m = 1 + math.pow((255 / 100 * prc),3) / (255*255)
+
+ -- decontrast
+ if prc < 0 then
+  m = 1 - math.abs(prc)/100
+ end 
+  
+ rc = db.getContrast(r)
+ rd = rc * m 
+ rv = (rd+1) * 127.5
+
+ gc = db.getContrast(g)
+ gd = gc * m 
+ gv = (gd+1) * 127.5
+
+ bc = db.getContrast(b)
+ bd = bc * m 
+ bv = (bd+1) * 127.5
+
+ return rv,gv,bv
+
+end
+
+
+
+--
+function db.getBrightness(r,g,b) -- 0-255
+ local bri
+ --bri = (r+g+b)/3
+ --bri = r*0.3 + g*0.59 + b*0.11 -- Luma Y'601
+ --bri = math.sqrt((r*0.3)^2 + (g*0.59)^2 + (b*0.11)^2) -- Luma Y'601
+ --bri = r*0.245 + g*0.575 + b*0.18 -- Dawn 2.0
+
+ bri = math.sqrt((r*0.26)^2 + (g*0.55)^2 + (b*0.19)^2) * 1.56905 -- Dawn 3.0
+ return bri  
+end
+--
+
+
+--
+-- Note on desaturation: These functions are all junk, the only way to desaturate
+--                       is to fade a color into it's corresponding greyscale.
+--
+
+--
+function db.desaturate(percent,r,g,b) -- V1.0 by Richard Fhager
+ local a,p
+ p = percent / 100
+ a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 * p
+ r = r + (a-r*p) -- a+r*(1-p)
+ g = g + (a-g*p)
+ b = b + (a-b*p)
+ return r,g,b
+end
+--
+
+--
+function db.desaturateA(percent,c) -- array version
+ local r,g,b,a
+ r = c[1]
+ g = c[2]
+ b = c[3]
+ p = percent / 100
+ a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5 * p
+ r = r + (a-r*p)
+ g = g + (a-g*p)
+ b = b + (a-b*p)
+ return {r,g,b}
+end
+--
+
+--
+function db.desatAVG(desat,c) -- Desaturation, simpe average
+ r = c[1]
+ g = c[2]
+ b = c[3]
+ p = desat / 100
+ a = (r+g+b)/3
+ r = r + p*(a-r) 
+ g = g + p*(a-g)
+ b = b + p*(a-b) 
+ return {r,g,b}
+end
+--
+
+
+--
+function db.getSaturation(r,g,b) -- HSL
+  local M,m,c,s,l
+  M = math.max(r,g,b)
+  m = math.min(r,g,b)
+  c = (M - m)/255
+  s = 0
+  if c ~= 0 then
+    --l = (0.3*r + 0.59*g + 0.11*b)/255 -- HSLuma: Y'601
+    l = (M+m)/510 -- This produces a quite "correct looking" divison of saturation
+    if l <= 0.5 then s = c / (2*l); end
+    if l  > 0.5 then s = c / (2-2*l); end
+  end
+  return math.min(255,s * 255)
+end
+--
+
+--
+function db.getTrueSaturationX(r,g,b) -- Distance from grayscale axis. Not HSV/HSL 
+ local sat,bri
+ bri = (r+g+b) / 3
+ sat = math.min(255, math.sqrt((r-bri)^2 + (g-bri)^2 + (b-bri)^2) * 1.224744875)
+ return sat
+end
+--
+
+-- WIP. Trying to find a more natural model for estimating Saturation
+-- Current: (HSL + True) / 2
+function db.getAppSaturation(r,g,b)
+  return  math.min(255, (db.getSaturation(r,g,b) + db.getTrueSaturationX(r,g,b)) / 2)
+end
+--
+
+--
+function db.saturate(percent,r,g,b) 
+  local a,m,p,mc
+  a = (math.min(math.max(r,g,b),255) + math.max(math.min(r,g,b),0)) * 0.5
+  m = math.min(255-math.max(r,g,b), math.min(r,g,b))
+  p = percent * (m / 100)
+  mc = math.max((r-a),(g-a),(b-a)) -- Can this be derived elsewhere?
+  if mc ~= 0 then
+   r = r + (r-a) * p / mc
+   g = g + (g-a) * p / mc
+   b = b + (b-a) * p / mc
+  end
+  return r,g,b
+end
+--
+
+--
+-- Super Saturate: Better than Photoshop etc.
+--
+-- Higher than 100% power is ok 
+--
+function db.saturateAdv(percent,r,g,b,brikeeplev,greydamp) -- brikeep = 0 - 2 
+  local a,m,p,mc,bri_o,bri_n,bdiff,mx,mi,adj,q,n
+   function cap(v) return math.max(0,math.min(v,255)); end
+  mx = math.max(r,g,b)
+  mi = math.min(r,g,b)
+  bri_o = db.getBrightness(r,g,b)
+  a = (math.min(mx,255) + math.max(mi,0)) * 0.5
+  m = math.min(255-mx, mi)
+  p = percent * (m / 100)
+  mc = math.max((r-a),(g-a),(b-a)) -- Can this be derived elsewhere?
+  if mc ~= 0 and m ~= 0 then
+   adj = math.min(1,(mx - mi) / m) -- Reduce effect on low saturation
+   if greydamp == false then adj = 1; end
+   q = p / mc * adj
+   r = cap( r + (r-a) * q )
+   g = cap( g + (g-a) * q )
+   b = cap( b + (b-a) * q )
+  end
+  for n = 0, brikeeplev*2, 1 do -- Must iterate to reduce brightness error
+    bri_n = db.getBrightness(r,g,b)
+    bdiff = (bri_o - bri_n) / 2 * brikeeplev
+    r = cap(r + bdiff)
+    g = cap(g + bdiff)
+    b = cap(b + bdiff)
+  end
+  return r,g,b
+end
+--
+
+
+--
+-- Lightness: Darken / Brighten color (Argument and returnvalue is a rgb-list)
+--            Rate of change is inversely proportional to the distance of the max/min. 
+--            i.e. all colors/channels will reach max/min at the same time (at 0 or 100 %)
+--            (As opposed to 'Brightness' where all channels are changed by a constant value)
+--
+function db.lightness(percent,c)
+ local v,r,g,b,p
+ r = c[1]
+ g = c[2]
+ b = c[3]
+ p = math.abs(percent/100)
+ v = 255
+ if percent < 0 then v = 0; end
+ r = r + (v - r)*p
+ g = g + (v - g)*p 
+ b = b + (v - b)*p 
+ return {r,g,b}
+end
+--
+
+--
+function db.changeLightness(r,g,b,percent)
+ local v
+ v = db.lightness(percent,{r,g,b})
+ return v[1],v[2],v[3]
+end
+--
+
+--
+function db.getLightness(r,g,b) -- HSL bi-hexcone
+  return (math.max(r,g,b) + math.min(r,g,b)) / 2
+end
+--
+
+--
+function db.shiftHUE(r,g,b,deg) -- V1.3 R.Fhager 2007, (Heavily derived code, hehe...)
+ local c,h,mi,mx,d,s,p,i,f,q,t
+ c = {g,b,r}
+ mi = math.min(r,g,b)
+ mx = math.max(r,g,b); v = mx;
+ d = mx - mi;
+ s = 0; if mx ~= 0 then s = d/mx; end
+ p = 1; if g ~= mx then p = 2; if b ~= mx then p = 0; end; end
+ 
+ if s~=0 then
+  h=(deg/60+(6+p*2+(c[1+p]-c[1+(p+1)%3])/d))%6; 
+  i=math.floor(h);
+  f=h-i;
+  p=v*(1-s);
+  q=v*(1-s*f);
+  t=v*(1-s*(1-f));
+  c={v,q,p,p,t,v}
+  r = c[1+i]
+  g = c[1+(i+4)%6]
+  b = c[1+(i+2)%6]
+ end
+
+ return r,g,b
+end
+--
+
+--
+function db.getHUE(r,g,b,greytol) -- 0-6 (6.5 = Greyscale), mult. with 60 for degrees
+ -- 1 Color diff is roughly detected by Tolerance = 0.0078125 (Tol. incr. with lightness etc.)
+ local c,h,mi,mx,d,s,p,i,f,q,t
+ c = {g,b,r}
+ mi = math.min(r,g,b)
+ mx = math.max(r,g,b); v = mx;
+ d = mx - mi;
+ s = 0; if mx ~= 0 then s = d/mx; end
+ p = 1; if g ~= mx then p = 2; if b ~= mx then p = 0; end; end
+
+ h = 6.5 -- for custom graphical purposes
+ if s>greytol then -- can't use >=
+  h=(6+p*2+(c[1+p]-c[1+(p+1)%3])/d)%6; 
+ end
+
+ return h
+end
+--
+
+--
+-- ... eof RGB color modifications ...
+--
+
+
+
+-- ****************************************
+-- *** Custom Color / Palette functions ***
+-- ****************************************
+
+
+--
+function db.rgbcap(r,g,b,mx,mi)
+ local m = math
+ return m.max(mi,m.min(r,mx)), m.max(mi,m.min(g,mx)), m.max(mi,m.min(b,mx))
+end
+--
+
+--
+function db.makePalList(cols)
+ local pal,n,r,g,b
+ pal = {}
+ for n = 0, cols-1, 1 do
+   r,g,b = getcolor(n)
+   pal[n+1] = {r,g,b,n}
+ end
+ return pal
+end
+--
+
+--
+function db.makeSparePalList(cols)
+ local pal,n,r,g,b
+ pal = {}
+ for n = 0, cols-1, 1 do
+   r,g,b = getsparecolor(n)
+   pal[n+1] = {r,g,b,n}
+ end
+ return pal
+end
+--
+
+
+--
+-- Use to remove the black colors (marks unused colors) from palette-list
+-- if it's known that no black color exists in the image.
+function db.stripBlackFromPalList(pallist)
+   local i,u,c,dummy; i = 257 -- Do 'nothing' If using a full 256 col palette with no blacks
+   for u = 1, #pallist, 1 do
+     c = pallist[u]
+     if (c[1]+c[2]+c[3]) == 0 then i = u; end
+   end
+   dummy = table.remove(pallist,i)
+   return pallist
+end
+--
+
+--
+function db.stripIndexFromPalList(pallist,colindex)
+   local i,u,c,dummy
+   for u = 1, #pallist, 1 do
+     c = pallist[u]
+     if c[4] == colindex then i = u; end
+   end
+   dummy = table.remove(pallist,i)
+   return pallist
+end
+--
+
+--
+function db.addHSBtoPalette(pallist)
+ local n,hue,sat,rgb
+ for n=1, #pallist, 1 do
+   rgb = pallist[n]
+   pallist[n][5] =        db.getHUE(rgb[1],rgb[2],rgb[3],0)
+   pallist[n][6] = db.getSaturation(rgb[1],rgb[2],rgb[3])
+   pallist[n][7] = db.getBrightness(rgb[1],rgb[2],rgb[3])
+ end
+ return pallist -- {r,g,b,n,bri,hue,sat}
+end
+--
+
+--
+function db.makePalListRange(start,ends)
+ local pal,n,r,g,b,a
+ pal = {}
+ a = 1
+ for n = start, ends, 1 do
+   r,g,b = getcolor(n)
+   pal[a] = {r,g,b,n}; a = a + 1;
+ end
+ return pal
+end
+--
+
+
+--
+function db.makePalListShade(cols,sha) -- Convert colors to less bits, colorcube operations etc.
+ local pal,n,r,g,b,mf,div
+ mf = math.floor
+ div = 256 / sha
+ pal = {}
+ for n = 0, cols-1, 1 do
+   r,g,b = getcolor(n)
+   pal[n+1] = {mf(r/div),mf(g/div),mf(b/div),n}
+ end
+ return pal
+end
+--
+--
+function db.makePalListShadeSPARE(cols,sha) -- Convert colors to less bits, colorcube operations etc.
+ local pal,n,r,g,b,mf,div
+ mf = math.floor
+ div = 256 / sha
+ pal = {}
+ for n = 0, cols-1, 1 do
+   r,g,b = getsparecolor(n)
+   pal[n+1] = {mf(r/div),mf(g/div),mf(b/div),n}
+ end
+ return pal
+end
+--
+
+
+
+--
+function db.getColorDistance_weight(r1,g1,b1,r2,g2,b2,rw,gw,bw)
+ return math.sqrt( (rw*(r1-r2))^2 + (gw*(g1-g2))^2 + (bw*(b1-b2))^2 ) 
+end
+--
+
+--
+function db.getBestPalMatch(r,g,b,pal,index_flag) -- pal = [r,g,b,palindex], index_flag -> return palindex if pal is sorted or reduced
+ local diff,best,bestcol,cols,n,c,p
+ cols = #pal
+ bestcol = -1
+ best = 9e99
+
+ for n=1, cols, 1 do
+  p = pal[n]
+  diff = db.getColorDistance_weight(r,g,b,p[1],p[2],p[3],0.26,0.55,0.19)  * 1.569
+  if diff < best then bestcol = n; best = diff; end
+ end 
+
+ if index_flag == true then
+  bestcol = pal[bestcol][4] + 1 
+ end
+
+ return bestcol-1 -- palList index start at 1, image-palette at 0
+end
+--
+
+
+-- Normally this function will return the (image)palette index of best color
+-- ...but if the palette has been sorted with 'fixPalette' it will return the index
+-- of the custom palList, setting index_flag will convert this value to image-palette index
+--
+-- HYBRID means the colormatch is a combo of color and (perceptual)brightness
+--
+--
+function db.getBestPalMatchHYBRID(rgb,pal,briweight,index_flag) -- Now correctly balanced
+ local diff,diffC,diffB,best,bestcol,cols,n,c,r,g,b,p,obri,pbri
+ cols = #pal
+ bestcol = -1
+ best = 9e99
+
+ --messagebox(briweight)
+
+ -- Note: Not secured against negative values (this algorithm is SLOW, we cannot afford it)
+ r = rgb[1]
+ g = rgb[2] 
+ b = rgb[3]
+
+ obri = db.getBrightness(r,g,b) -- 0-255
+
+ for n=1, cols, 1 do
+  p = pal[n]
+  pbri = db.getBrightness(p[1],p[2],p[3])
+  diffB = math.abs(obri - pbri)
+  -- we need to normalize the distance by the weights
+  diffC = db.getColorDistance_weight(r,g,b,p[1],p[2],p[3],0.26,0.55,0.19)  * 1.569
+
+  diff = briweight * (diffB - diffC) + diffC
+  if diff < best then bestcol = n; best = diff; end
+ end 
+
+ if index_flag == true then
+  bestcol = pal[bestcol][4] + 1 -- Since we detract 1 on return, God Lua is stupid 
+ end
+
+ return bestcol-1 -- palList index start at 1, image-palette at 0
+end
+--
+
+
+
+--
+-- Special version of Hybrid-remapping for mixPalette list
+--
+-- mixpal: {score,col#1,col#2,dist,rm,gm,bm, c1_r,c1_g,c1_b, c2_r,c2_g,c2_b}
+--
+-- returns: {col#1,col#2} (index of palette)
+--
+function db.getBestPalMatchHybridMIX(rgb,mixpal,briweight,mixreduction)
+ local diff,diffC,diffB,best,bestcol,cols,n,c,r,g,b,p,obri,pbri, distmult
+ cols = #mixpal
+ bestcol = -1
+ best = 9e99
+
+ -- We will simply add the the distance to the mix with the distance between the mixcolors and
+ -- employ a user tolerance to much the latter will matter.
+  --distmult = 255 / 9.56 / 100 * mixreduction -- 16 shades
+ distmult = 1.56902 / 100 * mixreduction  -- 24-bit, Dawn3.0 colormodel
+
+ -- Note: Not secured against negative values (this algorithm is SLOW, we cannot afford it)
+ r = rgb[1]
+ g = rgb[2] 
+ b = rgb[3]
+
+ obri = db.getBrightness(r,g,b) -- 0-255
+
+ for n=1, cols, 1 do
+  p = mixpal[n]
+  --pbri = db.getBrightness(p[5],p[6],p[7])
+
+ -- *** DawnBringer's exponetial color brightness dither resolution phenomena theorem ***
+ -- Bri = color value ^ 2
+ -- Two adjacent pixels displayed with "normal high resolution" will NOT have the perceptual
+ -- brightness of the resulting mixcolor. The brightness lies closer to that of the brightest pixel.
+ -- Bri[(C1+C2)/2] = SQRT( (C1bri^2 + C2bri^2) / 2 )
+ -- (Brightness according to Dawn-model: bri = SQRT( (r*.26)^2 + (g*.55)^2 + (b*.19)^2 ) )
+
+  pbri = math.sqrt((db.getBrightness(p[8],p[9],p[10])^2 + db.getBrightness(p[11],p[12],p[13])^2) / 2)
+
+  diffB = math.abs(obri - pbri)
+  -- we need to normalize the distance by the weights
+  diffC = db.getColorDistance_weight(r,g,b,p[5],p[6],p[7],0.26,0.55,0.19)  * 1.569 + p[4]*distmult
+
+  diff = briweight * (diffB - diffC) + diffC
+  if diff <= best then bestcol = n; best = diff; end
+ end 
+
+ return {mixpal[bestcol][2], mixpal[bestcol][3]}
+--return {mixpal[bestcol][2], 0}
+
+
+
+end
+--
+
+
+
+--
+function db.matchcolorHSB(h,s,b,pallist,index_flag)
+ --
+ -- why don't we just convert HSB-diagram to RGB and do normal colormatching?
+ -- Not the same...
+ --
+ local n,c,best,bestcol,pb,ph,ps,diff,huediff,huecorr,hue_adj,sat_adj,bri_adj
+ bestcol = -1
+ best = 9e99
+ 
+ -- higher adjust means more impact (higher hue gives more interpolation )
+ hue_adj = 4
+ sat_adj = 0.075
+ bri_adj = 2
+
+ huecorr = 255 / 6 -- Our Hue goes from 0.0 - 5.999 
+
+ for n=1, #pallist, 1 do
+  c = pallist[n]
+  ph = c[5]
+  ps = c[6]
+  pb = c[7]
+
+  huediff = math.abs(h-ph*huecorr)
+  if huediff > 127 then huediff = huediff - (huediff % 127) * 2; end
+
+  --if ph == 6.5 then huediff = 0; end 
+
+  -- With less saturation, exact hue becomes less important and brightness more usefull
+  -- This allows for greyscales and low saturation colors to work smoothly.
+  huediff = huediff * (ps /255)
+
+  diff = hue_adj*huediff^2 + (s-ps)^2 * sat_adj + (b-pb)^2 * bri_adj
+  
+  if diff <= best then bestcol = n; best = diff; end
+ end
+
+ if index_flag == true then
+  bestcol = palList[bestcol][4] + 1 -- Since we detract 1 on return, God Lua is stupid 
+ end
+
+ return bestcol-1
+
+end
+--
+
+--
+-- Used by PaletteAnalysis.lua, FindRamps(), MixColors()
+--
+function db.fixPalette(pal,sortflag) -- Arrange palette & only keep unique colors
+
+ local n,l,rgb,i,unique,bri,hue,sat,ulist,indexpal,newpal,dtot
+ ulist = {}
+ indexpal = {}
+ newpal = {}
+  local doubles; doubles = {}
+
+ l = #pal
+
+ unique = 1 -- ok, see how stupid lua is
+ dtot = 0
+ for n=1, l, 1 do
+   rgb = pal[n]; -- actually rgbn
+   i = 1 + rgb[1] * 65536 + rgb[2] * 256 + rgb[3];
+   bri = db.getBrightness(rgb[1],rgb[2],rgb[3])
+   if indexpal[i] == nil then 
+      indexpal[i] = rgb; ulist[unique] = {i,bri}; unique = unique+1;
+      else
+        doubles[rgb[4]] = true; -- Mark as double
+        dtot = dtot + 1
+   end
+ end
+
+ -- sort ulist
+ if sortflag == 1 then db.sorti(ulist,2); end -- sort by brightness
+
+ l = #ulist
+ for n=1, l, 1 do
+  newpal[n] = indexpal[ulist[n][1]]
+ end
+
+ newpal["doubles"] = doubles
+ newpal.double_total = dtot
+
+  --messagebox("unique colors", unique-1)
+
+ return newpal
+
+end
+--
+
+--
+-- InsertionSort Array, this is chaos...I'm confused and stomped...don't understand how Lua works...
+-- ...sorting seem be to ok but this code is ugly...
+-- Sort LO-HI
+--
+-- Screwed up or confused thing here I think, perhaps lo-hi/hi-lo. This is working lo-hi but the code
+-- looks like hi-lo...edit this some day
+-- 
+function db.sorti(d,idx) 
+   local a,j,tmp,l,e
+   l = #d
+
+   for a=2, l, 1 do
+    tmp = d[a];
+    e = a
+    for j=a, 2, -1 do  
+      e = j
+      if d[j-1][idx] > tmp[idx] then d[j] = d[j-1]; e = j-1; else break; end;
+    end;
+    d[e] = tmp; -- WHY THE F**K CAN'T YOU READ j HERE!?! STUPID ASSUCKING LANGUAGE 
+      
+   end;
+   --return d
+end
+--
+
+--
+function db.drawColorspace12bit(x,y,cols,size)
+ local r,g,b,c,rows,row,col,s16,rx,ry,xx,yy
+ s16 = size*16
+ rows = math.floor(16/cols)
+
+ for g = 0, 15, 1 do
+  col = g % cols
+  row = math.floor(g / cols)
+  for r = 0, 15, 1 do
+   for b = 0, 15, 1 do
+    c  = matchcolor(r*17,g*17,b*17)
+    xx = x+col*s16+r*size
+    yy = y+row*s16+b*size
+     for ry = 0, size-1, 1 do
+      for rx = 0, size-1, 1 do
+       putpicturepixel(xx+rx,yy+ry,c)
+     end;end
+   end
+  end
+ end
+end
+--
+
+--
+function db.drawHSBdiagram(pallist,posx,posy,width,height,size,sat)
+ --db.addHSBtoPalette(palList)
+ local x,y,c
+ for y = 0, height-1, 1 do
+  for x = 0, width-1, 1 do
+   hue = 255/width  * x
+   bri = 255/height * y
+   c = db.matchcolorHSB(hue,sat,bri,pallist,true)
+   db.drawRectangle(posx + x*size, posy + y*size,size,size, c)
+  end
+ end
+end
+--
+
+--
+-- Histograms, remapping etc.
+--
+
+--
+function db.makeHistogram() 
+  local n,y,x,c,w,h,list; list = {}
+  w, h = getpicturesize()
+  for n = 1, 256, 1 do list[n] = 0; end
+  for y = 0, h - 1, 1 do
+    for x = 0, w - 1, 1 do
+      c = getpicturepixel(x,y)
+      list[c+1] = list[c+1] + 1
+    end
+  end
+  return list
+end
+--
+
+--
+function db.makeSpareHistogram() 
+  local n,y,x,c,w,h,list; list = {}
+  w, h = getsparepicturesize()
+  --w,h = 512,360
+  for n = 1, 256, 1 do list[n] = 0; end
+  for y = 0, h - 1, 1 do
+    for x = 0, w - 1, 1 do
+      c = getsparepicturepixel(x,y)
+      list[c+1] = list[c+1] + 1
+    end
+  end
+  return list
+end
+--
+
+
+--
+-- Makes a palette-list from only the colors (histogram) that occurs in the image
+-- Assumes image/palette has not changed since histogram was created
+function db.makePalListFromHistogram(hist) 
+  local n,r,g,b,list,count
+  list = {}
+  count = 1
+  for n = 1, #hist, 1 do
+    if hist[n] > 0 then
+      r,g,b = getcolor(n-1)
+      list[count] = {r,g,b,n-1}
+      count = count + 1
+    end
+  end
+  return list
+end
+--
+
+function db.makePalListFromSpareHistogram(hist) 
+  local n,r,g,b,list,count
+  list = {}
+  count = 1
+  for n = 1, #hist, 1 do
+    if hist[n] > 0 then
+      r,g,b = getsparecolor(n-1)
+      list[count] = {r,g,b,n-1}
+      count = count + 1
+    end
+  end
+  return list
+end
+--
+
+
+--
+function db.remap(org) -- Working with a remap-list there's no need of reading backuppixel
+  --messagebox("Remapping")
+  local x,y,c,i,w,h,s,f,col
+  f = getpicturepixel
+  s = false
+  w, h = getpicturesize()
+  for y = 0, h - 1, 1 do
+   for x = 0, w - 1, 1 do
+    c = f(x,y)
+    i = org[c+1]
+    if i == null then i = matchcolor(getbackupcolor(getbackuppixel(x,y))); s = true; col = c; end -- Find color for a removed double
+    putpicturepixel(x,y,i)
+   end
+  end
+  if s then messagebox("Remapping: Not all image colors were found in remap-list (re-assign), probably due to duplicate removal. Matchcolor was used, ex: col# "..col); 
+  end
+end
+--
+
+--
+-- Palette DeCluster: Color-reduction by fusing similar colors into new ones, using a desired tolerance.
+--                    This is a method similar to Median-Cut, but more surgical.
+--
+-- pallist:   Palette list {r,g,b,palette_index}
+-- hist:      Histogram {color 0 pixels, color 1 pixels...etc} always a full 256 color list
+-- crad:      Cluster radius treshold in % of distance between black & white
+--            A value of 0 will only remove identical colors
+--            A value of 3-4 will usally fuse redundant colors without causing notice 
+-- prot_pow:  (0..10) Protect common colors in histogram. Distances are increased by ocurrence. 
+--            Also gives protection to fused colors even if not using histogram (combined nominal weights)
+-- pixels:    Pixels in image (so protection can be calculated)
+-- rw,gw,bw:  Color weights (rw+gw+bw = 1, 0.33,0.33,0.33 is nominal)
+--
+-- Returns:
+-- a new (c)palette list {r,g,b,{original palette_indices},fused flag, histogram_weight}
+-- a remap list (org) [image color + 1] = remap color (in the new palette)
+function db.deCluster(pallist, hist, crad, prot_pow, pixels, rw,gw,bw)
+
+ --messagebox(pixels)
+
+ local u,c,a,i,o,j,n,c1,c2,r,g,b,r1,g1,b1,r2,g2,b2,wt,rt,gt,bt,tot,pd
+ local worst,wtot,maxdist,maxDist,distfrac,clusterExists,clustVal,count,crad1
+ local cList,cPalList,clusterList,fuseCol,orgcols,newPalList,org
+
+ maxdist = math.sqrt(rw*rw*65025 + gw*gw*65025 + bw*bw*65025)
+ distfrac = 100 / maxdist
+
+ -- Let's just make a slightly more suitable format of the pallist (List for original color(s))
+ cPalList = {}
+ for u = 1, #pallist, 1 do
+  c = pallist[u]
+  cPalList[u] = {c[1],c[2],c[3],{c[4]},false,hist[c[4]+1]} -- r,g,b,{original colors},fuse_marker,histogram_weight
+ end
+
+ --table.insert(cPalList,{255,255,0,{257},false,1})
+
+ clusterExists = true
+ while clusterExists do
+  clusterExists = false
+  clusterList = {}
+
+  crad1 = crad + 1 -- avoid divison by zero
+  worst = 9999
+  for a = 1, #cPalList, 1 do 
+    c1 = cPalList[a]
+    r1,g1,b1 = c1[1],c1[2],c1[3]
+    wtot = c1[6]
+    cList = {a}
+    maxDist = 0
+    for b = 1, #cPalList, 1 do
+      if (b ~= a) then
+        c2 = cPalList[b]
+        r2,g2,b2 = c2[1],c2[2],c2[3]
+        wt = c2[6]
+        pd = math.pow((1 + wt / pixels),  prot_pow) -- Protection, increase distance
+        dist = db.getColorDistance_weight(r1,g1,b1,r2,g2,b2,rw,gw,bw) * distfrac * pd
+        if dist <= crad then 
+           wtot = wtot + wt
+          table.insert(cList,b)
+          maxDist = math.max(dist,maxDist)
+        end
+      end
+    end -- b
+    if #cList > 1 then 
+      clustVal = maxDist / (crad1 * #cList) * (wtot / #cList)   
+      if clustVal < worst then
+        worst = clustVal
+        clusterList = cList
+      end
+    end
+  end -- a
+  
+  --t = db.ary2txt(clusterList)
+  --messagebox("Worst cluster is "..t)
+  
+   -- Fuse
+  if #clusterList > 1 then
+    clusterExists = true -- Run another iteration and look for more clusters
+    fuseCol = {0,0,0,{}}
+    rt,gt,bt,tot = 0,0,0,0
+    for n = 1, #clusterList, 1 do
+     i = clusterList[n]
+     c = cPalList[i]
+       --o = c[4][1] -- Original color (always #1 in list since fused colors can't re-fuse) 
+     o = c[4] -- Original color list
+     --if c[5] == true then messagebox("Re-Fusing..."); end
+     r,g,b = c[1],c[2],c[3]
+       --wt = hist[o+1] -- Org. colors are 0-255
+     wt = c[6]
+     rt = rt + r * wt
+     gt = gt + g * wt
+     bt = bt + b * wt
+     tot = tot + wt
+     cPalList[i] = -1 -- Erase color
+       --table.insert(fuseCol[4],o)
+     orgcols = fuseCol[4]
+     for j = 1, #o, 1 do
+      table.insert(orgcols,o[j])
+     end
+     fuseCol[4] = orgcols
+    end
+
+    rt = rt / tot
+    gt = gt / tot
+    bt = bt / tot
+    fuseCol[1] = rt
+    fuseCol[2] = gt
+    fuseCol[3] = bt
+    fuseCol[5] = true -- fusecol marker
+    fuseCol[6] = tot
+    table.insert(cPalList,fuseCol)
+    --messagebox(#clusterList.." Colors was fused, resulting in "..rt..", "..gt..", "..bt)
+    newPalList = {}
+    for n = 1, #cPalList, 1 do
+     if cPalList[n] ~= -1 then
+       table.insert(newPalList,cPalList[n])
+      --newPalList = db.newArrayInsertLast(newPalList,cPalList[n])  
+     end
+    end
+    cPalList = newPalList
+    --messagebox("Pal length: "..#cPalList)
+   statusmessage("DeCluster - Image colors:"..#cPalList.."                       "); waitbreak(0)
+  end -- fuse
+
+ end -- while
+
+    -- Create remap-list
+    org = {}
+    count = 0
+    for u = 1, #cPalList, 1 do
+       c = cPalList[u]
+       for n = 1, #c[4], 1 do
+         i = c[4][n]
+         org[i+1] = count -- quick way to remap without matchcolor
+       end
+       count = count + 1
+    end
+
+ return org,cPalList
+
+end; -- decluster
+
+
+--
+-- ... eof Custom Color / Palette functions ...
+--
+
+
+-- *****************************
+-- *** Custom Draw functions ***
+-- *****************************
+
+--
+function db.lineTransp(x1,y1,x2,y2,c,amt) -- amt: 0-1, 1 = Full color
+ local n,st,m,x,y,r,g,b,r1,g1,b1,c2,org; m = math
+ org = 1 - amt
+ st = m.max(1,m.abs(x2-x1),m.abs(y2-y1));
+ for n = 0, st, 1 do
+   x = m.floor(x1+n*(x2-x1)/st)
+   y = m.floor(y1+n*(y2-y1)/st)
+   r,g,b = getcolor(getpicturepixel(x,y))
+   r1,g1,b1 = getcolor(c)
+   c2 = matchcolor(r1*amt+r*org, g1*amt+g*org, b1*amt+b*org) 
+   putpicturepixel(x, y, c2 );
+ end
+end
+--
+
+--
+function db.drawBrushRectangle(x1,y1,w,h,c)
+   local x,y
+   for y = y1, y1+h-1, 1 do
+    for x = x1, x1+w-1, 1 do
+       putbrushpixel(x,y,c);
+    end
+   end
+end
+--
+
+--
+function db.drawRectangle(x1,y1,w,h,c)
+	drawfilledrect(x1, y1, x1+w, y1+w, c);
+end
+--
+
+--
+function db.drawRectangleNeg(x1,y1,w,h,c)
+   local x,y,xs,ys
+   xs = db.sign(w)
+   ys = db.sign(h)
+   if xs == 0 then xs = 1; end
+   if ys == 0 then ys = 1; end
+   for y = y1, y1+h-1, ys do
+    for x = x1, x1+w-1, xs do
+       putpicturepixel(x,y,c);
+    end
+   end
+end
+--
+
+--
+function db.drawRectangleLine(x,y,w,h,c)
+ w = w-1
+ h = h-1
+ drawline(x,y,x+w,y,c)
+ drawline(x,y,x,y+h,c)
+ drawline(x,y+h,x+w,y+h,c)
+ drawline(x+w,y,x+w,y+h,c)
+end
+--
+
+
+--
+function db.drawRectangleMix(x1,y1,w,h,c1,c2)
+   local x,y,c,n
+   c = {c1,c2}
+   n = 0
+   for y = y1, y1+h-1, 1 do
+     n = n + 1
+    for x = x1, x1+w-1, 1 do
+       n = n + 1
+       putpicturepixel(x,y,c[n%2+1]);
+    end
+   end
+end
+--
+
+--
+function db.drawBrushCircle(x1,y1,r,c) -- ok, lottsa weird adjustments here, can probably be optimized...
+   local x,y,d
+   for y = 0, r*2, 1 do
+    for x = 0, r*2, 1 do
+       d = math.sqrt((x-r-0.5)^2 + (y-r-0.5)^2)
+       if d < r-0.25 then putbrushpixel(x1+x-r-0.5,y1+y-r-0.5,c); end
+    end
+   end
+end
+--
+
+--
+-- Rotation in degrees
+-- Step is # of line segments (more is "better")
+-- a & b are axis-radius
+function db.ellipse2(x,y,a,b,stp,rot,col) 
+ local n,m=math,rad,al,sa,ca,sb,cb,ox,oy,x1,y1,ast
+ m = math; rad = m.pi/180; ast = rad * 360/stp;
+ sb = m.sin(-rot * rad); cb = m.cos(-rot * rad)
+ for n = 0, stp, 1 do
+  ox = x1; oy = y1;
+  sa = m.sin(ast*n) * b; ca = m.cos(ast*n) * a
+  x1 = x + ca * cb - sa * sb
+  y1 = y + ca * sb + sa * cb
+  if (n > 0) then drawline(ox,oy,x1,y1,col); end
+ end
+end
+--
+
+
+
+--[[
+var ER = 0.3
+var DR = 0.15
+
+ellipse(0.5*xx,0.5*yy,DR*xx,6,Math.PI*0)
+
+function ellipse(x,y,r,stp,rot){
+ var n,deg=360,m=Math,rad=Math.PI/180,rn
+ var ox,oy,x1,y1,x2,y2,d1,r1 = ER * xx
+ 
+ for (n=0; n<=deg; n+=stp){
+
+  ox = x2; oy = y2, rn = rad * n
+  d1 = rn - rot
+  x1 = x + m.sin(d1) * r
+  y1 = y + m.cos(d1) * r
+
+  x2 = x1 + m.sin(-rn) * r1
+  y2 = y1 + m.cos(-rn) * r1
+  if (n > 0){ line_rgb(MX,[0,0,0],0,ox,oy,x2,y2) }
+ }
+}
+
+}
+
+ellipse2(0.5*xx,0.5*yy,15,8,200,22,[0,0,0],0.5)
+
+function ellipse2(x,y,a,b,stp,rot,rgb,transp){
+ var n,m=Math,rad=m.PI/180,al,sa,ca,sb,cb,ox,oy,x1,y1
+ sb = m.sin(-rot * rad); cb = m.cos(-rot * rad)
+ for (n=0; n<=stp; n++){
+  ox = x1; oy = y1; al = rad * 360/stp * n 
+  sa = m.sin(al) * b; ca = m.cos(al) * a
+  x1 = x + ca * cb - sa * sb
+  y1 = y + ca * sb + sa * cb
+  if (n > 0){ line_rgb(MX,rgb,transp,ox,oy,x1,y1) }
+ }
+}
+
+
+]]
+
+
+
+function db.obliqueCube(side,x,y,r,g,b,bri)
+ local n,c,depth,x1,y1,x2,y2,f
+
+  f = matchcolor
+  c = f(r,g,b)
+  cP50 =  f(r+bri*0.5,g+bri*0.5,b+bri*0.5)
+  cP75 =  f(r+bri*0.75,g+bri*0.75,b+bri*0.75)
+  cM50 =  f(r-bri*0.5,g-bri*0.5,b-bri*0.5)
+  cM100 = f(r-bri,g-bri,b-bri)
+
+ depth = math.floor(side / 2)
+
+ for n = 0, depth-1, 1 do
+  drawline(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50)
+ end
+
+ for n = 0, depth-1, 1 do
+  drawline(x+n,y-1-n,x+side+n-1,y-1-n,cP50)
+ end
+
+ --  /
+ --   
+ --drawline(x+side,y-1,x+side+depth-1,y-depth,c)
+
+ -- Smoothing & Shade
+
+ --
+ --  /
+ --drawline(x+side,y+side-1,x+side+depth-1,y+side-depth,cM100)
+
+ --drawline(x,y,x+side-2,y,cP75)
+ --drawline(x,y,x,y+side-2,cP75)
+
+ db.drawRectangle(x,y,side,side,c)
+
+end
+
+
+function db.obliqueCubeBRI(side,x,y,r,g,b,bri,pallist,briweight,index_flag)
+ local n,c,depth,x1,y1,x2,y2
+
+  --f = db.getBestPalMatchHYBRID
+  c =  db.getBestPalMatchHYBRID({r,g,b},    pallist, briweight, index_flag)
+  cP50 =  db.getBestPalMatchHYBRID({r+bri*0.5,g+bri*0.5,b+bri*0.5},    pallist, briweight, index_flag)
+  cP75 =  db.getBestPalMatchHYBRID({r+bri*0.75,g+bri*0.75,b+bri*0.75}, pallist, briweight, index_flag)
+  cM50 =  db.getBestPalMatchHYBRID({r-bri*0.5,g-bri*0.5,b-bri*0.5},    pallist, briweight, index_flag)
+  cM100 = db.getBestPalMatchHYBRID({r-bri,g-bri,b-bri},                pallist, briweight, index_flag)
+
+ depth = math.floor(side / 2)
+
+ db.drawRectangle(x,y,side,side,c)
+
+ for n = 0, depth-1, 1 do
+  drawline(x+side+n,y-1-n,x+side+n,y+side-n-1,cM50)
+ end
+
+ for n = 0, depth-1, 1 do
+  drawline(x+n,y-1-n,x+side+n-1,y-1-n,cP50)
+ end
+
+ --  /
+ --   
+ drawline(x+side,y-1,x+side+depth-1,y-depth,c)
+
+ -- Smoothing & Shade
+
+ --
+ --  /
+ --drawline(x+side,y+side-1,x+side+depth-1,y+side-depth,cM100)
+
+ --drawline(x,y,x+side-2,y,cP75)
+ --drawline(x,y,x,y+side-2,cP75)
+
+
+end
+
+
+--
+-- ... eof Custom Draw functions ...
+--
+
+
+-- ******************************
+-- *** Filters & Convolutions ***
+-- ******************************
+
+
+function db.applyConvolution2Pic(convmx,divisor,bias,neg,amt)
+ local r,g,b,mx,my,cx,cy,mxh,myh,mp,rb,gb,bb,xx,yy,x,y,w,h,div,n1,n2,amtr,ro,go,bo
+
+ n1 = 1
+ n2 = bias
+ if neg == 1 then
+  n1 = -1
+  n2 = 255 + bias
+ end
+ 
+ amtr = 1 - amt
+ w, h = getpicturesize()
+ cy = #convmx
+ cx = #convmx[1]
+ mxh = math.floor(cx / 2) + 1
+ myh = math.floor(cy / 2) + 1
+
+  for y = 0, h-1, 1 do
+  for x = 0, w-1, 1 do
+   r,g,b = 0,0,0
+   ro,go,bo = getcolor(getbackuppixel(x,y))
+   div = divisor
+   for my = 1, cy, 1 do
+    for mx = 1, cx, 1 do
+     xp = mx-mxh
+     yp = my-myh
+     mp = convmx[my][mx]
+     xx = x + xp
+     yy = y + yp
+      if yy>=0 and yy=0 and xx 0 and n 0 and n0) then 
+    x = x*px - fx
+    y = y*py - fy
+    nfrac = nfrac + spfrac
+   end
+  n = n+1
+ end
+ --return 1 - n/i;
+ return 1 - nfrac/i
+end
+--
+
+
+--
+function db.mandel(x,y,l,r,o,i) -- pos. as fraction of 1, left coord, right coord, y coord, iterations 
+
+  local w,s,a,p,q,n,v,w
+
+  s=math.abs(r-l);
+
+  a = l + s*x;
+  p = a;
+  b = o - s*(y-0.5);
+  q = b;
+  n = 1;
+  v = 0;
+  w = 0; 
+
+  while (v+w<4 and n weakest then weakest = w; weak_i = {z,y,x}; end
+      end 
+    
+  end;end;end
+  return weak_i[1],weak_i[2],weak_i[3]
+end
+--
+
+--
+--
+-- Nearest color version: void is selected by the point that has the greatest distance
+-- to the nearest color. Higher value means greater void.
+--
+function db.addColor2Cube(cube,sha,r,g,b,rw,gw,bw)
+  local star,x,y,z,d,rd,gd,bd,cu1,cu2
+  star = 0
+  cube[r+1][g+1][b+1] = {false, star}
+  for z = 0, sha-1, 1 do
+   rd = (rw*(z-r))^2
+   cu2 = cube[z+1]
+   for y = 0, sha-1, 1 do
+    gd = (gw*(y-g))^2
+    cu1 = cu2[y+1]
+    for x = 0, sha-1, 1 do
+  
+      d = rd + gd + (bw*(x-b))^2 
+
+      --cube[z+1][y+1][x+1][2] = math.min(d, cube[z+1][y+1][x+1][2]) -- Don't add, use nearest color
+
+      cu1[x+1][2] = math.min(d, cu1[x+1][2]) 
+
+  end;end;end
+end
+--
+
+-- Should be same as original, but not 100% verified. Using a rgb+1 trick to speed up handling
+--
+function db.addColor2Cube_test(cube,sha,r,g,b,rw,gw,bw)
+  local star,x,y,z,d,rd,gd,bd,cu1,cu2
+  star = 0
+  r = r+1; g = g+1; b = b+1
+  cube[r][g][b] = {false, star}
+  for z = 1, sha, 1 do
+   rd = (rw*(z-r))^2
+   cu2 = cube[z]
+   for y = 1, sha, 1 do
+    gd = (gw*(y-g))^2
+    cu1 = cu2[y]
+    for x = 1, sha, 1 do 
+      cu1[x][2] = math.min(rd+gd+(bw*(x-b))^2, cu1[x][2]) 
+  end;end;end
+end
+--
+
+
+
+-- Create new allowed colorlines in colorspace (ramps from which colors can be picked)
+function db.enableRangeColorsInCube(cube,sha,r1,g1,b1,r2,g2,b2) 
+
+    local div,r,g,b,n,rs,gs,bs
+    div = 256 / sha
+    rs = (r2 - r1) / sha / div 
+    gs = (g2 - g1) / sha / div
+    bs = (b2 - b1) / sha / div
+
+    for n = 0, sha-1, 1 do
+
+     r = math.floor(r1/div + rs * n)
+     g = math.floor(g1/div + gs * n)
+     b = math.floor(b1/div + bs * n)
+
+     cube[r+1][g+1][b+1][1] = true
+
+    end
+end
+--
+
+
+function db.colorCigarr(shades,radius,fill_flag)
+ local s,rad,radsq,step,shalf,bas,cols,found,x,y,z,bri,con,d,n
+ radius = radius / 100
+ step = math.floor(255 / (shades-1))
+ shalf = math.floor(shades / 2)
+ s = shades - 1
+ rad = math.floor(shades / 2 * radius) 
+ radsq = rad^2
+
+ bas = 0
+ cols = {}
+ found = 0
+
+ for z = 0, s, 1 do 
+  for y = 0, s, 1 do
+   for x = 0, s, 1 do
+
+  --0.26,0.55,0.19
+  bri = (x + y + z ) / 3
+  --bri = math.sqrt(((x*0.26)^2 + (y*0.55)^2 + (z*0.19)^2)) * 1.5609
+  con = math.floor((shades - math.abs(bri - shalf)*2) * radius)
+
+  d = math.floor(math.sqrt((bri-x)^2 + (bri-y)^2 + (bri-z)^2))
+  --d = math.floor(math.sqrt(((bri-x)*0.26)^2 + ((bri-y)*0.55)^2 + ((bri-z)*0.19)^2)) * 1.5609
+
+  -- Filled cigarr: Less or Equal, cigarr shell: Equal
+   if d == con or (d < con and fill_flag) then 
+      found = found + 1
+      r = bas + x * step
+      g = bas + y * step
+      b = bas + z * step
+      cols[found] = {r,g,b}
+   end
+
+  end; end; end
+
+  --messagebox("Colors found: "..found.."\n\n".."Run AnalyzePalette to examine")
+
+  for n = 0, 255, 1 do
+   if n < found then
+    c = cols[n+1]
+    setcolor(n,c[1],c[2],c[3]) 
+     else
+     setcolor(n,0,0,0) 
+    end
+  end
+end -- eof colorcigarr
+
+
+--
+-- ... eof Color Cube ...
+--
+
+
+
+-- COLORMIX --
+--
+-- Returns a list of mixcolors palette entries, that are ranked by by quality & usefulness
+--
+-- This whole junk my partly locked on 16 shades (4096 colors/ 12bit palette precision) so don't use anything else...
+--
+--
+function db.colormixAnalysis(sha,spare_flag,cust_dist) -- Interface
+  local shades,pallist,ilist,custom_max_distance
+
+  shades = sha -- 16 is good
+  --messagebox(shades)
+
+  custom_max_distance = -1
+  if cust_dist ~= null then
+    custom_max_distance = cust_dist -- in % 
+  end
+
+  if spare_flag == true then -- No shades here for now
+    --pallist = db.makePalListShadeSPARE(256,shades)     -- 16 shades so Colorcube processes is possible
+    pallist = db.makeSparePalList(256)
+    pallist = db.fixPalette(pallist,0)            -- Remove doubles, No need to sort?
+    ilist = db.makeIndexList(pallist, -1) 	  -- -1, use list order as index
+   else
+     pallist = db.makePalListShade(256,shades)       -- 16 shades so Colorcube processes is possible
+     pallist = db.fixPalette(pallist,0)              -- Remove doubles, No need to sort?
+     ilist = db.makeIndexList(pallist, -1) 	     -- -1, use list order as index
+  end 
+
+   if shades > 0 then
+    return db.colormixAnalysisEXT(shades,pallist,ilist,custom_max_distance) -- max distance in %
+   end
+   if shades == -1 then
+     return db.colormixAnalysisEXTnoshade(pallist,ilist,custom_max_distance) -- max distance in %
+   end
+end
+--
+--
+function db.colormixAnalysisEXT(SHADES,pallist,ilist,custom_max_distance) -- Shades, most number of mixes returned
+ local n,m,c1,c2,pairs,cube,rm,gm,bm
+ local mix,total,found,dist,void,ideal,mini,maxi,bestmix,bestscore
+ 
+ --messagebox("will now make pairs")
+
+  pairs = db.pairsFromList(ilist,0)            -- 0 for unique pairs only, pairs are entries in pallist
+
+  --messagebox(#pairs.." will now add colors to cube")
+
+ cube = db.initColorCube(SHADES,{true,9999})
+ for n = 1, #pallist, 1 do
+   c1 = pallist[n]
+   db.addColor2Cube_test(cube,SHADES,c1[1],c1[2],c1[3],0.26,0.55,0.19)
+ end
+
+ -- these values are adjusted for a 12bit palette (0-15) and perceptual weight where r+g+b = 1.0
+ -- Ideal distance = 2.5 Green steps = 1.375
+ -- Minimum distance = 1 Green step  = 0.55
+
+ --messagebox("colorcube done")
+
+ VACT = 1
+ DACT = 1
+
+ total = 9.56 -- Max distance possible with 16 shades
+ ideal = 0.45  -- 1 step = 0.637
+ mini = 0.35
+ maxi = ideal + (total - ideal) / math.max(1, #pallist / 16)
+  if custom_max_distance ~= -1 then
+   maxi = total * (custom_max_distance / 100)
+  end
+ mix = {}
+ --mix[1] =  {9e99,0,0,9e99,0,0,0}
+ bestmix = -1
+ bestscore = 9e99
+ found = 0
+ for n = 1, #pairs, 1 do
+
+   c1 = pallist[pairs[n][1]]
+   c2 = pallist[pairs[n][2]]
+   --0.26,0.55,0.19
+   dist = db.getColorDistance_weight(c1[1],c1[2],c1[3],c2[1],c2[2],c2[3],0.26,0.55,0.19) -- Not normalized
+
+   rm = math.floor((c1[1]+c2[1])/2) 
+   gm = math.floor((c1[2]+c2[2])/2)
+   bm = math.floor((c1[3]+c2[3])/2)
+
+  -- Mix color adjustment (perhaps less than perfect, but probably good enough)
+   mixbri = db.getBrightness(rm,gm,bm)
+   truebri = math.sqrt((db.getBrightness(c1[1],c1[2],c1[3])^2 + db.getBrightness(c2[1],c2[2],c2[3])^2) / 2)
+   diff = truebri - mixbri
+   rm = math.max(0,math.min(15,math.floor(rm + diff)))
+   gm = math.max(0,math.min(15,math.floor(gm + diff)))
+   bm = math.max(0,math.min(15,math.floor(bm + diff)))
+    newbri = db.getBrightness(rm,gm,bm)
+    delta =  math.abs(newbri - truebri)
+    --if delta > 0.9 then 
+    -- messagebox(pallist[pairs[n][1]][4]..", "..pallist[pairs[n][2]][4].." delta = "..delta) 
+    --end
+  --
+
+   --rm = math.floor(math.sqrt((c1[1]^2 + c2[1]^2) / 2))
+   --gm = math.floor(math.sqrt((c1[2]^2 + c2[2]^2) / 2))
+   --bm = math.floor(math.sqrt((c1[3]^2 + c2[3]^2) / 2))
+
+   void = cube[rm+1][gm+1][bm+1][2]
+
+   if dist >= mini and dist <= maxi then
+     found = found + 1
+     score = ((1+DACT*(dist - ideal)^2) / (1+void*VACT)) -- Lowest is best
+     mix[found] = {score,pallist[pairs[n][1]][4],pallist[pairs[n][2]][4],dist,rm*SHADES,gm*SHADES,bm*SHADES,c1[1]*SHADES,c1[2]*SHADES,c1[3]*SHADES,c2[1]*SHADES,c2[2]*SHADES,c2[3]*SHADES} -- mix holds palette entry
+     if score < bestscore then bestscore = score; bestmix = found; end
+   end
+
+ end
+
+
+ if true == false then
+ -- 2nd pass, add bestmix to colorspace. This reduces many similar mixes.
+ m = mix[bestmix]
+ db.addColor2Cube(cube,SHADES,m[5],m[6],m[7],0.26,0.55,0.19)
+ for n = 1, #mix, 1 do
+  if n ~= bestmix then
+   m = mix[n]
+   dist = m[4]
+   void = cube[m[5]+1][m[6]+1][m[7]+1][2]
+   score = ((1+DACT*(dist - ideal)^2) / (1+void*VACT)) 
+   m[1] = score
+  end
+ end
+ end
+
+ c1,c2 = -1,-1
+ if found > 0 then
+  db.sorti(mix,1) 
+  best = mix[1]
+  c1 = best[2]
+  c2 = best[3]
+ end
+
+ --return found,c1,c2
+ return mix,found,c1,c2
+end
+--
+
+
+--
+-- Mixcolor without colorcube - no scoring or sorting, 24bit colors, faster...
+--
+function db.colormixAnalysisEXTnoshade(pallist,ilist,custom_max_distance)
+ local n,m,c1,c2,pairs,cube,rm,gm,bm
+ local mix,total,found,dist,void,ideal,mini,maxi,bestmix,bestscore
+
+  pairs = db.pairsFromList(ilist,0)            -- 0 for unique pairs only, pairs are entries in pallist
+
+ total = 162.53 -- Max distance possible with 24-bit palette ad Dawn3.0 color weights  162.53
+ ideal = 0
+ mini  = 0
+ maxi  = ideal + (total - ideal) / math.max(1, #pallist / 16)
+
+  if custom_max_distance ~= -1 then
+   maxi = total * (custom_max_distance / 100)
+  end
+
+ mix = {}
+ found = 0
+ for n = 1, #pairs, 1 do
+
+   c1 = pallist[pairs[n][1]]
+   c2 = pallist[pairs[n][2]]
+   --0.26,0.55,0.19
+   dist = db.getColorDistance_weight(c1[1],c1[2],c1[3],c2[1],c2[2],c2[3],0.26,0.55,0.19) -- Not normalized
+
+   rm = math.floor((c1[1]+c2[1])/2) 
+   gm = math.floor((c1[2]+c2[2])/2)
+   bm = math.floor((c1[3]+c2[3])/2)
+
+  -- Mix color adjustment
+   mixbri = db.getBrightness(rm,gm,bm)
+   truebri = math.sqrt((db.getBrightness(c1[1],c1[2],c1[3])^2 + db.getBrightness(c2[1],c2[2],c2[3])^2) / 2)
+   diff = truebri - mixbri
+   rm = math.max(0,math.min(255,math.floor(rm + diff)))
+   gm = math.max(0,math.min(255,math.floor(gm + diff)))
+   bm = math.max(0,math.min(255,math.floor(bm + diff)))
+    newbri = db.getBrightness(rm,gm,bm)
+    delta =  math.abs(newbri - truebri)
+    --if delta > 0.9 then 
+    --  messagebox(pallist[pairs[n][1]][4]..", "..pallist[pairs[n][2]][4].." delta = "..delta) 
+    --end
+  --
+
+   if dist >= mini and dist <= maxi then
+     found = found + 1
+     score = 1
+     mix[found] = {score,pallist[pairs[n][1]][4],pallist[pairs[n][2]][4],dist,rm,gm,bm,c1[1],c1[2],c1[3],c2[1],c2[2],c2[3]} -- mix holds palette entry
+   end
+
+ end
+
+ --messagebox(#mix)
+
+ return mix,found,-1,-1
+end
+--
+
+
+
+-- Fuse a palettelist into an extended mix-anlysis list
+function db.fusePALandMIX(pal,mix,max_score,max_dist)
+ local n,c,mixlist,tot,score,dist,c1,c2,rm,gm,bm
+
+  mixlist = {}
+  tot = 0
+
+  -- {r,g,b,n}
+  for n = 1, #pal, 1 do
+   tot = tot + 1
+   c = pal[n]
+   mixlist[tot] = {0,c[4],c[4],0,c[1],c[2],c[3],c[1],c[2],c[3],c[1],c[2],c[3]}
+  end
+
+  -- {score,col#1,col#2,dist,rm,gm,bm} low score is best
+  for n = 1, #mix, 1 do
+   score = mix[n][1]
+   dist  = mix[n][4]
+   if score <= max_score and dist <= max_dist then
+     tot = tot + 1
+      mixlist[tot] = mix[n]
+   end
+  end
+
+ return mixlist
+end
+--
+
+
+-- ********************************************
+-- *** L-system (fractal curves & "plants") ***
+-- ********************************************
+--
+
+--
+function db.Lsys_makeData(a)
+  local n,i; i = {}
+  for n = 1, #a, 1 do i[a[n][2]] = a[n]; end
+  return i
+end
+--
+
+--
+function db.Lsys_makeSet(seed,iter,data)
+  local s,n,i,nset,set
+  set = seed
+  for n = 1, iter, 1 do
+   nset = ''
+   for i = 1, #set, 1 do
+    s = string.sub(set,i,i)
+    nset = nset..data[s][3]
+   end
+   set = nset
+  end
+  return set
+end
+--
+
+function db.Lsys_draw(set,data,cx,cy,size,rot,rgb,rng,transp, speed)
+  local p,M,DEG,l,n,d,i,v,q,c,tx,ty,posx,posy,dval,col,w,h,s,cl,count
+
+  if speed == nil then speed = 50; end -- speed is drawing operations per update
+
+  function ang(d) return (d % 360 + 360) * DEG; end
+  
+  w,h = getpicturesize()
+
+  p   = 0
+  M   = math
+  DEG = math.pi/180
+  l   = #set
+
+  posx={}; posy={}; dval={}
+
+  if (rgb == null) then rgb = {0,0,0}; end
+  if (transp == null) then transp = 0; end
+  col = db.newArrayMerge(rgb,{})
+  q = 255 / l
+
+  count = 0
+  for n = 1, l, 1 do
+    s = string.sub(set,n,n)
+    d = data[s]
+    i = d[1]
+    v = d[4]
+
+    --messagebox(i)
+
+    if (i == 'Left')  then rot = rot - v; end
+    if (i == 'Right') then rot = rot + v; end
+ 
+    if (i == 'Save') then p=p+1; posx[p] = cx; posy[p] = cy; dval[p] = rot; end
+    if (i == 'Load') then cx = posx[p]; cy = posy[p]; rot = dval[p]; p=p-1; end
+
+    if (i == 'Draw') then
+      tx = cx +  M.sin(ang(rot)) * size
+      ty = cy + -M.cos(ang(rot)) * size
+         for c = 1, 3, 1 do
+          if (rng[c] > 0) then col[c] = rgb[c] + (n * q) * rng[c]; end
+          if (rng[c] < 0) then col[c] = rgb[c] + (n * q) * rng[c]; end
+         end 
+
+      cl = matchcolor(col[1],col[2],col[3])
+      --putpicturepixel(cx*w,cy*h,cl); 
+      --drawline(cx*w,cy*h,tx*w,ty*h,cl)
+      db.lineTransp(cx*w,cy*h,tx*w,ty*h,cl,transp) 
+  
+      cx = tx; cy = ty
+    end
+   count = count + 1
+   if count == speed then count = 0; updatescreen(); if (waitbreak(0)==1) then return end; end
+  end
+
+  return {cx,cy,rot}
+end -- draw
+
+
+--
+-- eof L-system
+--
+
+
+
+
+
+
+
+
diff --git a/src/factory.c b/src/factory.c
index 94f466d3..7bb7f565 100644
--- a/src/factory.c
+++ b/src/factory.c
@@ -348,6 +348,85 @@ int L_PutPicturePixel(lua_State* L)
   return 0; // no values returned for lua
 }
 
+
+int L_DrawLine(lua_State* L)
+{
+  int x1, y1, x2, y2, c;
+
+  int nb_args = lua_gettop(L);
+
+  LUA_ARG_LIMIT(5, "drawline");
+  LUA_ARG_NUMBER(1, "drawline", x1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(2, "drawline", y1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(3, "drawline", x2, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(4, "drawline", y2, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(5, "drawline", c,  INT_MIN, INT_MAX);
+
+  Pixel_figure = Display_pixel;
+  Draw_line_general(x1, y1, x2, y2, c);
+
+  return 0;
+}
+
+
+int L_DrawFilledRect(lua_State* L)
+{
+  int x1, y1, x2, y2, c;
+
+  int nb_args = lua_gettop(L);
+
+  LUA_ARG_LIMIT(5, "drawfilledrect");
+  LUA_ARG_NUMBER(1, "drawfilledrect", x1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(2, "drawfilledrect", y1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(3, "drawfilledrect", x2, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(4, "drawfilledrect", y2, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(5, "drawfilledrect", c,  INT_MIN, INT_MAX);
+
+  Draw_filled_rectangle(x1, y1, x2, y2, c);
+
+  return 0;
+}
+
+
+int L_DrawCircle(lua_State* L)
+{
+  int x1, y1, r, c;
+
+  int nb_args = lua_gettop(L);
+
+  LUA_ARG_LIMIT(4, "drawcircle");
+  LUA_ARG_NUMBER(1, "drawcircle", x1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(2, "drawcircle", y1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(3, "drawcircle", r, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(4, "drawcircle", c, INT_MIN, INT_MAX);
+
+  Pixel_figure = Display_pixel;
+  Circle_limit = r*r;
+  Draw_empty_circle_general(x1, y1, r, c);
+
+  return 0;
+}
+
+
+int L_DrawDisk(lua_State* L)
+{
+  int x1, y1, r, c;
+
+  int nb_args = lua_gettop(L);
+
+  LUA_ARG_LIMIT(4, "drawdisk");
+  LUA_ARG_NUMBER(1, "drawdisk", x1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(2, "drawdisk", y1, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(3, "drawdisk", r, INT_MIN, INT_MAX);
+  LUA_ARG_NUMBER(4, "drawdisk", c, INT_MIN, INT_MAX);
+
+  Circle_limit = r*r;
+  Draw_filled_circle(x1, y1, r, c);
+
+  return 0;
+}
+
+
 int L_GetPicturePixel(lua_State* L)
 {
   int x;
@@ -1308,36 +1387,54 @@ void Run_script(const char *script_subdirectory, const char *script_filename)
   L = lua_open();
   putenv("LUA_PATH=libs\\?.lua");
   
+  // writing and reading pixels
   lua_register(L,"putbrushpixel",L_PutBrushPixel);
+  lua_register(L,"putpicturepixel",L_PutPicturePixel);
+  lua_register(L, "drawline",L_DrawLine);
+  lua_register(L, "drawfilledrect",L_DrawFilledRect);
+  lua_register(L, "drawcircle",L_DrawCircle);
+  lua_register(L, "drawdisk",L_DrawDisk);
+
   lua_register(L,"getbrushpixel",L_GetBrushPixel);
   lua_register(L,"getbrushbackuppixel",L_GetBrushBackupPixel);
-  lua_register(L,"putpicturepixel",L_PutPicturePixel);
   lua_register(L,"getpicturepixel",L_GetPicturePixel);
   lua_register(L,"getlayerpixel",L_GetLayerPixel);
   lua_register(L,"getbackuppixel",L_GetBackupPixel);
+  lua_register(L,"getsparelayerpixel",L_GetSpareLayerPixel);
+  lua_register(L,"getsparepicturepixel",L_GetSparePicturePixel);
+
+
+  // resizing stuff
   lua_register(L,"setbrushsize",L_SetBrushSize);
   lua_register(L,"setpicturesize",L_SetPictureSize);
+
   lua_register(L,"getbrushsize",L_GetBrushSize);
   lua_register(L,"getpicturesize",L_GetPictureSize);
+  lua_register(L,"getsparepicturesize",L_GetSparePictureSize);
+
+  // color and palette
   lua_register(L,"setcolor",L_SetColor);
+  lua_register(L,"setforecolor",L_SetForeColor);
+  lua_register(L,"setbackcolor",L_SetBackColor);
+
   lua_register(L,"getcolor",L_GetColor);
   lua_register(L,"getbackupcolor",L_GetBackupColor);
-  lua_register(L,"matchcolor",L_MatchColor);
   lua_register(L,"getbrushtransparentcolor",L_GetBrushTransparentColor);
+  lua_register(L,"getsparecolor",L_GetSpareColor);
+  lua_register(L,"getsparetranscolor",L_GetSpareTransColor);
+  lua_register(L,"getforecolor",L_GetForeColor);
+  lua_register(L,"getbackcolor",L_GetBackColor);
+  lua_register(L,"gettranscolor",L_GetTransColor);
+  
+  lua_register(L,"matchcolor",L_MatchColor);
+
+  // ui
   lua_register(L,"inputbox",L_InputBox);
   lua_register(L,"messagebox",L_MessageBox);
   lua_register(L,"statusmessage",L_StatusMessage);
   lua_register(L,"selectbox",L_SelectBox);
-  lua_register(L,"getforecolor",L_GetForeColor);
-  lua_register(L,"getbackcolor",L_GetBackColor);
-  lua_register(L,"setforecolor",L_SetForeColor);
-  lua_register(L,"setbackcolor",L_SetBackColor);
-  lua_register(L,"gettranscolor",L_GetTransColor);
-  lua_register(L,"getsparepicturesize",L_GetSparePictureSize);
-  lua_register(L,"getsparelayerpixel",L_GetSpareLayerPixel);
-  lua_register(L,"getsparepicturepixel",L_GetSparePicturePixel);
-  lua_register(L,"getsparecolor",L_GetSpareColor);
-  lua_register(L,"getsparetranscolor",L_GetSpareTransColor);
+
+  // misc. stuff
   lua_register(L,"clearpicture",L_ClearPicture);
   lua_register(L,"wait",L_Wait);
   lua_register(L,"waitbreak",L_WaitBreak);