for j = 1 to 255 while not done do
(
c2.v = j
done = (GetLuminance c2) > l1
)
for j = c2.g to 255 while not done do
(
c2.g += 1
done = (GetLuminance c2) > l1
)
the method is slow now. but as I see it can simply be changed to use the bisection method instead of the linear algorithm.
If you need to perform many operations, then this iterative approach isn’t any good.
As usual, the first step was to get the algorithm working, so now that we have 50% of the task solved, we can try to improve it.
My first attempt was to solve this in a direct way, but since I failed to solve one exponential equation, I decided to go with an iterative solver.
Here is a starting idea for a “direct algorithm”. This new functions is around 65 times faster than the iterative approach, but when values of blue goes over 255 it uses a LUT to solve the GREEN curve.
It would be good to get it solved in an equation. However, I don’t know how much faster it would perform.
(
try destroydialog ::RO_SWAP_CHANNELS catch()
rollout RO_SWAP_CHANNELS "" width:348 height:258
(
group "Master Color: "
(
colorPicker cp1 fieldwidth:315 height:76 color:gray align:#left
label lb0 offset:[0,-14]
)
group "Swapped Channels: "
(
colorPicker cp2 fieldwidth:100 height:76 color:gray align:#left enabled:off across:3
colorPicker cp3 fieldwidth:100 height:76 color:gray align:#left enabled:off
colorPicker cp4 fieldwidth:100 height:76 color:gray align:#left enabled:off
label lb2 "Direct Swap" align:#center across:3
label lb3 "Brightness [Iterative]"
label lb4 "Brightness [Direct]"
label lb2c "[0,0]" align:#center across:3
label lb3c "[0,0]"
label lb4c "[0,0]"
)
local LUTGreen = #()
fn GetLuminance1 colour =
(
return (colour.r^2 * 0.299 + colour.g^2 * 0.587 + colour.b^2 * 0.114)
)
fn GetLuminance2 colour =
(
-- Removed RED component
return (colour.g^2 * 0.587 + colour.b^2 * 0.114)^0.5
)
fn SwapBlueGreenChannels1 colour =
(
if colour.g == colour.b do return colour
c1 = color 0 colour.g colour.b
c2 = color 0 colour.b colour.g
l1 = GetLuminance1 c1
done = false
for j = 1 to 255 while not done do
(
c2.v = j
done = (GetLuminance1 c2) > l1
)
for j = c2.g to 255 while not done do
(
c2.g += 1
done = (GetLuminance1 c2) > l1
)
c2.r = colour.r
c2.g = int (c2.g + 0.5)
c2.b = int (c2.b + 0.5)
return c2
)
-- This function is around 65 times faster than SwapBlueGreenChannels1()
fn SwapBlueGreenChannels2 colour =
(
if colour.g != colour.b then
(
c1 = color 0 colour.g colour.b
c2 = color 0 colour.b colour.g
l1 = GetLuminance2 c1
l2 = GetLuminance2 c2
delta = l1 / l2
c2.r = int colour.r
c2.g = int ((c2.g * delta)+0.5)
c2.b = int ((c2.b * delta)+0.5)
-- We need to solve the equation for the green
-- component when the blue value goes over 255
if c2.b > 255 do
(
c2.g = LUTGreen[c1.g]
c2.b = 255
)
-----------------------------------------------
)else(
c2 = colour
)
return c2
)
fn BuildLUTGreen =
(
for j = 100 to 255 do
(
LUTGreen[j] = (SwapBlueGreenChannels1 (color 0 j 0)).g
)
)
fn UpdateControls =
(
c = cp1.color
cp2.color = color c.r c.b c.g
cp3.color = SwapBlueGreenChannels1 c
cp4.color = SwapBlueGreenChannels2 c
lb2c.text = [cp2.color.g, cp2.color.b] as string
lb3c.text = [cp3.color.g, cp3.color.b] as string
lb4c.text = [cp4.color.g, cp4.color.b] as string
)
on RO_SWAP_CHANNELS open do
(
BuildLUTGreen()
c = random black white
cp1.color = [int c.r, int c.g, int c.b]
UpdateControls()
)
on cp1 changed arg do UpdateControls()
)
createdialog RO_SWAP_CHANNELS style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
)
PD: haven’t tried bisecting the previous algorithm, but it may be also good, since we would reduce the iterations from a maximum of 512 to 16 in worst case. Yet 16 looks like a big number for intensive use.
How about solvig all the variants with a LUT? That should be a lot faster than any other approach.
EDIT: Confirmed, a LUT version is around 200 times faster than the iterative version for a 128×128 image.
I have played with my math skills too…
here is my ‘direct algorithm’ version:
(
try destroydialog ::SWAP_GB_Rollout catch()
rollout SWAP_GB_Rollout "" width:840 --height:244
(
local sw = 280
local sw2 = sw+sw
groupbox org_gr "Origin Color: " width:270 height:410 pos:[6,4]
colorPicker org_cp color:black fieldwidth:256 height:40 pos:[9,24] modal:off
label org_rgb_lb "RGB:" align:#left pos:[20, 70]
label org_rgb_vb "0" align:#left pos:[50, 70]
label org_lum_lb "Lume:" align:#left pos:[210, 70]
label org_lum_vb "0" align:#left pos:[245, 70]
imgtag org_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 pos:[13,90]
groupbox dir_gr "Direct Swap: " width:270 height:410 pos:[6+sw,4]
colorPicker dir_cp color:black fieldwidth:256 height:40 pos:[9+sw,24] modal:off
label dir_rgb_lb "RGB:" align:#left pos:[20+sw, 70]
label dir_rgb_vb "0" align:#left pos:[50+sw, 70]
label dir_lum_lb "Lume:" align:#left pos:[210+sw, 70]
label dir_lum_vb "0" align:#left pos:[245+sw, 70]
imgtag dir_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 pos:[13+sw,90]
groupbox scr_gr "Correct Swap: " width:270 height:410 pos:[6+sw2,4]
colorPicker scr_cp color:black fieldwidth:256 height:40 pos:[9+sw2,24] modal:off
label scr_rgb_lb "RGB:" align:#left pos:[20+sw2, 70]
label scr_rgb_vb "0" align:#left pos:[50+sw2, 70]
label scr_lum_lb "Lume:" align:#left pos:[210+sw2, 70]
label scr_lum_vb "0" align:#left pos:[245+sw2, 70]
imgtag scr_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 pos:[13+sw2,90]
checkbox squared_cb "Squared" checked:on pos:[580, 354] enabled:off
radiobuttons factor_type_rb labels:#("Luminance: (0.213, 0.715, 0.072)", "Brightness: (0.299, 0.587, 0.114)") default:2 columns:1 pos:[620, 354+22]
local luminance_factor = [0.213, 0.715, 0.072]
local brightness_factor = [0.299, 0.587, 0.114]
fn luminance v factor:brightness_factor =
(
R = factor.x
G = factor.y
B = factor.z
if not ispoint3 v do v = [v.r, v.g, v.b]
sqrt(v.x^2 * R + v.y^2 * G + v.z^2 * B)
)
fn quadEquation a b c =
(
d = b*b - 4*a*c
if (d >= 0) do
(
[(-b + sqrt(d))/(2*a), (-b - sqrt(d))/(2*a)]
)
)
--fn colorClamp v = clamppoint v max:255
fn swapGreenBlue cp factor:brightness_factor =
(
R = factor.x
G = factor.y
B = factor.z
local org = copy [cp.r, cp.g, cp.b]
local scr = [org.x, org.z, org.y]
lume = luminance org factor:factor
lumc = luminance scr factor:factor
dx = 0
dy = 0
dz = 0
if (lumc > lume) then
(
y = sqrt((scr.z^2 * (G - B) + scr.y^2 * B)/ G)
scr = [scr.x, y, scr.z]
)
else
(
if (lumc < lume) then
(
z = sqrt((scr.y^2 * (B - G) + scr.z^2 * G) / B)
if z >= 255.0 then
(
y = sqrt(scr.z^2 + scr.y^2*B/G - 255^2*B/G)
scr = [scr.x, y, 255]
)
else scr = [scr.x, scr.y, z]
)
)
--scr = clamppoint scr max:255
scr = color scr.x scr.y scr.z
)
fn stringColor col =
(
ss = stringstream ""
format "% % %" (int col.r) (int col.g) (int col.b) to:ss
ss as string
)
fn stringValue val =
(
ss = stringstream ""
format "%" (int val) to:ss
ss as string
)
fn updateColors col factor: squared: =
(
factor = if factor_type_rb.state == 1 then luminance_factor else brightness_factor
col0 = col
dir_cp.color = col1 = color col.r col.b col.g
d = color col.r col.b col.g
s = swapGreenBlue col factor:factor
scr_cp.color = col2 = s --vlerp s d
org_rgb_vb.text = stringColor col0
dir_rgb_vb.text = stringColor col1
scr_rgb_vb.text = stringColor col2
org_lum_vb.text = stringValue (luminance col0 factor:factor)
dir_lum_vb.text = stringValue (luminance col1 factor:factor)
scr_lum_vb.text = stringValue (luminance col2 factor:factor)
)
fn hsv_to_rgb hsv =
(
col = black -- it might be any color
col.v = hsv.b
col.s = hsv.g
col.h = hsv.r
col
)
fn updateUI col: complete:on =
(
setwaitcursor()
factor = if factor_type_rb.state == 1 then luminance_factor else brightness_factor
if col != unsupplied then org_cp.color = col else col = org_cp.color
updateColors col factor:factor
local bmc = org_bm.bitmap
local bmd = dir_bm.bitmap
local bms = scr_bm.bitmap
for y=0 to 255 do
(
cc = #()
cd = #()
cs = #()
for x=0 to 255 collect
(
c = hsv_to_rgb (color x 255.0 (255.0-y))
d = color c.r c.b c.g
if complete do
(
append cc c
append cd d
)
s = swapGreenBlue c factor:factor
append cs s --(vlerp s d)
)
if complete do
(
setPixels bmc [0,y] cc
setPixels bmd [0,y] cd
)
setPixels bms [0,y] cs
)
if complete do
(
org_bm.bitmap = bmc
dir_bm.bitmap = bmd
)
scr_bm.bitmap = bms
setarrowcursor()
)
on org_cp changed col do updateColors col
on factor_type_rb changed state do updateUI complete:off
on SWAP_GB_Rollout open do
(
org_cp.color = col = green
updateUI()
)
)
createdialog SWAP_GB_Rollout style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
)
what do you think?
hmm … no. I’m still wrong
fixed
now it seems correct:
(
try destroydialog ::SWAP_GB_Rollout catch()
rollout SWAP_GB_Rollout "" width:840 --height:244
(
local sw = 280
local sw2 = sw+sw
groupbox org_gr "Origin Color: " width:270 height:410 pos:[6,4]
colorPicker org_cp color:black fieldwidth:256 height:40 pos:[9,24] modal:off
label org_rgb_lb "RGB:" align:#left pos:[20, 70]
label org_rgb_vb "0" align:#left pos:[50, 70]
label org_lum_lb "Lume:" align:#left pos:[210, 70]
label org_lum_vb "0" align:#left pos:[245, 70]
imgtag org_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 pos:[13,90]
groupbox dir_gr "Direct Swap: " width:270 height:410 pos:[6+sw,4]
colorPicker dir_cp color:black fieldwidth:256 height:40 pos:[9+sw,24] modal:off
label dir_rgb_lb "RGB:" align:#left pos:[20+sw, 70]
label dir_rgb_vb "0" align:#left pos:[50+sw, 70]
label dir_lum_lb "Lume:" align:#left pos:[210+sw, 70]
label dir_lum_vb "0" align:#left pos:[245+sw, 70]
imgtag dir_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 pos:[13+sw,90]
groupbox scr_gr "Correct Swap: " width:270 height:410 pos:[6+sw2,4]
colorPicker scr_cp color:black fieldwidth:256 height:40 pos:[9+sw2,24] modal:off
label scr_rgb_lb "RGB:" align:#left pos:[20+sw2, 70]
label scr_rgb_vb "0" align:#left pos:[50+sw2, 70]
label scr_lum_lb "Lume:" align:#left pos:[210+sw2, 70]
label scr_lum_vb "0" align:#left pos:[245+sw2, 70]
imgtag scr_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 pos:[13+sw2,90]
checkbox squared_cb "Squared" checked:on pos:[580, 354] enabled:off across:2
spinner blend_direct_sp "Blend Direct: " type:#float range:[0,1,0] scale:0.01 fieldwidth:46 pos:[702, 355]
radiobuttons factor_type_rb labels:#("Luminance: (0.213, 0.715, 0.072)", "Brightness: (0.299, 0.587, 0.114)") default:2 columns:1 pos:[600, 354+22]
local luminance_factor = [0.213, 0.715, 0.072]
local brightness_factor = [0.299, 0.587, 0.114]
fn luminance v factor:brightness_factor =
(
R = factor.x
G = factor.y
B = factor.z
(v.x^2*R + v.y^2*G + v.z^2*B)
)
fn luminanceSquared col factor: =
(
v = [col.r, col.g, col.b]
sqrt(luminance v factor:factor)
)
fn swapGreenBlue cp factor:brightness_factor =
(
R = factor.x
G = factor.y
B = factor.z
local org = copy [cp.r, cp.g, cp.b]
local scr = [org.x, org.z, org.y]
lume = luminance org factor:factor
lumc = luminance scr factor:factor
v = org.y^2*G + org.z^2*B
if (lumc > lume) then
(
scr.y = sqrt((v - org.y^2*B)/G)
)
else
(
if (lumc < lume) then
(
z = sqrt((v - org.z^2*G)/B)
if z > 255.0 then
(
y = sqrt((v - 255^2 * B)/G)
scr.y = y
scr.z = 255
)
else scr.z = z
)
)
scr = color scr.x scr.y scr.z
)
fn stringColor col =
(
ss = stringstream ""
format "% % %" (int col.r) (int col.g) (int col.b) to:ss
ss as string
)
fn stringValue val =
(
ss = stringstream ""
format "%" (int val) to:ss
ss as string
)
fn updateColors col factor: squared: =
(
factor = if factor_type_rb.state == 1 then luminance_factor else brightness_factor
t = blend_direct_sp.value
col0 = col
dir_cp.color = col1 = color col.r col.b col.g
s = swapGreenBlue col factor:factor
scr_cp.color = col2 = s*(1 - t) + col1*t
org_rgb_vb.text = stringColor col0
dir_rgb_vb.text = stringColor col1
scr_rgb_vb.text = stringColor col2
org_lum_vb.text = stringValue (luminanceSquared col0 factor:factor)
dir_lum_vb.text = stringValue (luminanceSquared col1 factor:factor)
scr_lum_vb.text = stringValue (luminanceSquared col2 factor:factor)
)
fn hsv_to_rgb hsv =
(
col = black -- it might be any color
col.v = hsv.b
col.s = hsv.g
col.h = hsv.r
col
)
fn updateUI col: complete:on =
(
setwaitcursor()
factor = if factor_type_rb.state == 1 then luminance_factor else brightness_factor
t = blend_direct_sp.value
if col != unsupplied then org_cp.color = col else col = org_cp.color
updateColors col factor:factor
local bmc = org_bm.bitmap
local bmd = dir_bm.bitmap
local bms = scr_bm.bitmap
for y=0 to 255 do
(
cc = #()
cd = #()
cs = #()
for x=0 to 255 collect
(
c = hsv_to_rgb (color x 255.0 (255.0-y))
d = color c.r c.b c.g
if complete do
(
append cc c
append cd d
)
s = swapGreenBlue c factor:factor
v = s*(1 - t) + d*t
append cs v
)
if complete do
(
setPixels bmc [0,y] cc
setPixels bmd [0,y] cd
)
setPixels bms [0,y] cs
)
if complete do
(
org_bm.bitmap = bmc
dir_bm.bitmap = bmd
)
scr_bm.bitmap = bms
setarrowcursor()
)
on org_cp changed col do updateColors col
on factor_type_rb changed state do updateUI complete:off
on blend_direct_sp entered do updateUI complete:off
on SWAP_GB_Rollout open do
(
org_cp.color = col = green
updateUI()
)
)
createdialog SWAP_GB_Rollout style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
)
this is optimized … and ‘blend with direct’ feature added.
Nice
Although it produces different results than the iterative version. Not sure which one is better, but you have some options to choose from now.
This was the remaining piece I needed to complete my code:
y = sqrt((v - 255^2 * B)/G)
Here is the updated version:
(
try destroydialog ::RO_SWAP_CHANNELS catch()
rollout RO_SWAP_CHANNELS "" width:348 height:258
(
group "Master Color: "
(
colorPicker cp1 fieldwidth:315 height:76 color:gray align:#left
label lb0 offset:[0,-14]
)
group "Swapped Channels: "
(
colorPicker cp2 fieldwidth:100 height:76 color:gray align:#left enabled:off across:3
colorPicker cp3 fieldwidth:100 height:76 color:gray align:#left --enabled:off
colorPicker cp4 fieldwidth:100 height:76 color:gray align:#left --enabled:off
label lb2 "Direct Swap" align:#center across:3
label lb3 "Brightness [Iterative]"
label lb4 "Brightness [Direct]"
label lb2c "[0,0]" align:#center across:3
label lb3c "[0,0]"
label lb4c "[0,0]"
)
fn GetLuminance colour =
(
return (colour.g^2 * 0.587 + colour.b^2 * 0.114)
)
fn SwapBlueGreenChannels1 colour =
(
if colour.g == colour.b do return colour
c1 = color 0 colour.g colour.b
c2 = color 0 colour.b colour.g
l1 = GetLuminance c1
done = false
for j = 1 to 255 while not done do
(
c2.v = j
done = (GetLuminance c2) > l1
)
for j = c2.g to 255 while not done do
(
c2.g += 1
done = (GetLuminance c2) > l1
)
c2.r = colour.r
c2.g = int (c2.g + 0.5)
c2.b = int (c2.b + 0.5)
return c2
)
-- Can be optimized -----------------------------------------------
fn SwapBlueGreenChannels2 colour =
(
if colour.g != colour.b then
(
c1 = color 0 colour.g colour.b
c2 = color 0 colour.b colour.g
l1 = GetLuminance c1
l2 = GetLuminance c2
delta = l1^0.5 / l2^0.5
c2.r = int colour.r
c2.g = int ((c2.g * delta)+0.5)
c2.b = int ((c2.b * delta)+0.5)
if c2.b > 255 do
(
c2.g = int (sqrt ((l1 - (255^2 * 0.114))/0.587)+0.5) -- DenisT ;)
c2.b = 255
)
)else(
c2 = colour
)
return c2
)
fn UpdateControls =
(
c = cp1.color
cp2.color = color c.r c.b c.g
cp3.color = SwapBlueGreenChannels1 c
cp4.color = SwapBlueGreenChannels2 c
lb2c.text = [cp2.color.g, cp2.color.b] as string
lb3c.text = [cp3.color.g, cp3.color.b] as string
lb4c.text = [cp4.color.g, cp4.color.b] as string
)
on RO_SWAP_CHANNELS open do
(
c = random black white
cp1.color = [int c.r, int c.g, int c.b]
UpdateControls()
)
on cp1 changed arg do UpdateControls()
)
createdialog RO_SWAP_CHANNELS style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
)
I added ‘color-box gradient’… now it looks almost identical:
(
try destroydialog ::RO_SWAP_CHANNELS catch()
rollout RO_SWAP_CHANNELS "" width:348
(
group "Master Color: "
(
colorPicker cp1 fieldwidth:315 height:76 color:gray align:#left
label lb0 offset:[0,-14]
)
group "Swapped Channels: "
(
colorPicker cp2 fieldwidth:100 height:76 color:gray align:#left enabled:off across:3
colorPicker cp3 fieldwidth:100 height:76 color:gray align:#left enabled:off
colorPicker cp4 fieldwidth:100 height:76 color:gray align:#left enabled:off
label lb2 "Direct Swap" align:#center across:3
label lb3 "Brightness [Iterative]"
label lb4 "Brightness [Direct]"
label lb2c "[0,0]" align:#center across:3
label lb3c "[0,0]"
label lb4c "[0,0]"
)
imgtag scr_bm bitmap:(bitmap 256 256) transparent:(color -1 -1 -1) width:256 height:256 align:#center
fn GetLuminance colour =
(
return (colour.g^2 * 0.587 + colour.b^2 * 0.114)
)
fn GetLuminance1 colour =
(
return (colour.r^2 * 0.299 + colour.g^2 * 0.587 + colour.b^2 * 0.114)
)
fn SwapBlueGreenChannels1 colour =
(
if colour.g == colour.b do return colour
c1 = color 0 colour.g colour.b
c2 = color 0 colour.b colour.g
l1 = GetLuminance c1
done = false
for j = 1 to 255 while not done do
(
c2.v = j
done = (GetLuminance c2) > l1
)
for j = c2.g to 255 while not done do
(
c2.g += 1
done = (GetLuminance c2) > l1
)
c2.r = colour.r
c2.g = int (c2.g + 0.5)
c2.b = int (c2.b + 0.5)
return c2
)
-- Can be optimized -----------------------------------------------
fn SwapBlueGreenChannels2 colour =
(
if colour.g != colour.b then
(
c1 = color 0 colour.g colour.b
c2 = color 0 colour.b colour.g
l1 = GetLuminance c1
l2 = GetLuminance c2
delta = l1^0.5 / l2^0.5
c2.r = int colour.r
c2.g = int ((c2.g * delta)+0.5)
c2.b = int ((c2.b * delta)+0.5)
if c2.b > 255 do
(
c2.g = int (sqrt ((l1 - (255^2 * 0.114))/0.587)+0.5) -- DenisT ;)
c2.b = 255
)
)else(
c2 = colour
)
return c2
)
fn UpdateControls =
(
c = cp1.color
cp2.color = color c.r c.b c.g
cp3.color = c3 = SwapBlueGreenChannels1 c
cp4.color = c4 = SwapBlueGreenChannels2 c
lb2c.text = [cp2.color.g, cp2.color.b, round(sqrt(GetLuminance1 c))] as string
lb3c.text = [cp3.color.g, cp3.color.b, round(sqrt(GetLuminance1 c3))] as string
lb4c.text = [cp4.color.g, cp4.color.b, round(sqrt(GetLuminance1 c4))] as string
)
fn HSV_to_RGB hsv =
(
rgb = black -- it might be any color
rgb.v = hsv.b
rgb.s = hsv.g
rgb.h = hsv.r
rgb
)
on RO_SWAP_CHANNELS open do
(
c = random black white
cp1.color = [int c.r, int c.g, int c.b]
UpdateControls()
local bms = scr_bm.bitmap
for y=0 to 255 do
(
cs = #()
for x=0 to 255 collect
(
c = HSV_to_RGB (color x 255.0 (255.0-y))
d = color c.r c.b c.g
s = SwapBlueGreenChannels2 c
append cs s --(vlerp s d)
)
setPixels bms [0,y] cs
)
scr_bm.bitmap = bms
)
on cp1 changed arg do UpdateControls()
)
createdialog RO_SWAP_CHANNELS style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)
)
but I’m happy enough. It was a fun to play with a new toy. Thank you guys.
I just want you to know why I went for this “research”…
In my real-time shader I found that an image desaturation is going with different visual ‘speed’ for greenish and blueish images… that’s was the reason to search for solution.