--# Main
-- colorize
-- This program takes three colors, a pure color, a highlight and a shade, to
-- produce a smooth gradient from black to white that gives a natural
-- looking range of colors from dark to light.
-- The approach is different from regular gradient functions like in photoshop.
-- normally you would build up a gradient with white and black in the end stops
-- and put in color stops somewhere in between and make liniar transitions
-- from one to the next. In the 3d rgb color cube that would look like a
-- polygon with straight lines and hard edges at every stop.
-- Those hard edges show up in your artwork as ugly spikes of oversaturated
-- stripes. I made this program to come up with something better that looks
-- more natural.
-- This program takes out those hard edges. Instead of a polygon, it approaches
-- the gradient as a bezier curve through the rgb color cube. The gradient goes
-- from black to white and inbetween it is pulled away from the gray diagonal
-- line, by the three input colors that work as atractor points.
-- Similar to the curve tools that you find in Inkscape or Illustrator,
-- the line never touches the atractors at any point in the line.
-- After that it redistributes the color stops to compensate for their perceived
-- luminosity.
-- Use this function to perform your initial setup
function setup()
parameter.color("highlight",color(255,255,128))
parameter.color("purecolor",color(255,0,0))
parameter.color("shade",color(0,0,127))
n=255 -- the number of color stops
-- first build a small test image.
photo=image(240,240)
setContext(photo)
for i=25,255 do
fill(i)
ellipse(120+i/6,120+i/5,255-i)
end
setContext()
range=image(5,1000) -- this image stores the color range.
white=color(255) -- white and black are used as constants.
black=color(0)
photo=decolorize(photo) -- the test image is actually grayscale already
photo2=photo -- photo2 will contain the colorized image.
imageX=WIDTH/2-240 -- the image can be dragged around.
imageY=HEIGHT/2-240 -- for future purpose of building a drawing program.
print("Change the colors to create different color ranges.")
print("Tap the colored gradient bar on the right to apply the range to the image.")
end
-- This function gets called once every frame
function draw()
background(127, 127, 127, 255)
noStroke()
spriteMode(CORNER)
sprite(photo2,imageX,imageY,500)
setContext(range)
dg(0,1,shade,purecolor,highlight) -- build up the gradient map in range.
setContext()
sprite(range,WIDTH-40,0,40,HEIGHT) -- put the range on the screen on the right.
-- indicate the atractor points next to the range for user reference.
drwAtractors(shade,purecolor,highlight)
end
function luminence(c) -- this fuctions returns the perceived lightness of a color
-- the mathematical equasion is highly debated on forums, but this one got
-- the most consensus.
return math.sqrt(0.299*c.r*c.r + 0.587*c.g*c.g + 0.114*c.b*c.b)/255
end
-- I didn't use the b3 and b4 bezier functions with resp. 1 and 2 attractors.
-- but if you want to make your own version with on or two attractors, it is there.
function b3(t,p0,p1,p2)
return math.pow((1-t),2)*p0
+2*t*(1-t)*p1
+math.pow(t,2)*p2
end
function b4(t,p0,p1,p2,p3)
return math.pow((1-t),3)*p0
+3*t*math.pow((1-t),2)*p1
+3*math.pow(t,2)*(1-t)*p2
+math.pow(t,3)*p3
end
function b5(t,p0,p1,p2,p3,p4) -- this is the bezier function actually used
return math.pow((1-t),4)*p0
+4*t*math.pow((1-t),3)*p1
+6*math.pow(t,2)*math.pow(1-t,2)*p2
+4*math.pow(t,3)*(1-t)*p3
+math.pow(t,4)*p4
end
function dg(t0,t1,c1,c2,c3)
local k0=b5(t0,black, c1,c2, c3,white)
local k1=b5(t1,black, c1,c2, c3,white)
local l0=luminence(k0)
local l1=luminence(k1)
if l1>l0 then
if l1-l0 < 1/n then
fill(k0)
rect(0,l0*1000,5,l1*1000)
else
local t3=t0+(t1-t0)/2
dg(t0,t3,c1,c2,c3)
dg(t3,t1,c1,c2,c3)
end
end
end
function drwAtractors(c1,c2,c3)
-- this function plots the atractor points next to the gradient on the screen
-- according to their luminence to get an idea where they influence the
-- gradient most. Yellows tend to pull up and blues pull down quite
-- considerably.
local k0=b5(1/4,black, c1,c2, c3,white)
local k1=b5(1/2,black, c1,c2, c3,white)
local k2=b5(3/4,black, c1,c2, c3,white)
local l0=luminence(k0)
local l1=luminence(k1)
local l2=luminence(k2)
fill(black)
rect(WIDTH-60,0,20,HEIGHT)
fill(c1)
ellipse(WIDTH-50,HEIGHT*l0,20)
fill(c2)
ellipse(WIDTH-50,HEIGHT*l1,20)
fill(c3)
ellipse(WIDTH-50,HEIGHT*l2,20)
end
function touched(t)
if t.x> WIDTH-40 then
if t.state==ENDED
then
photo2=colorize(photo)
else
photo2=photo
end
end
if t.state== MOVING -- to drag the image around
then
imageX=imageX+ t.deltaX
imageY=imageY+ t.deltaY
end
end
function decolorize(img)
-- this function turns a photo into black and white.
-- it doesn't just average the rgb values but actually calculates
-- luminosity.
local r,g,b,a,l,t,c,k
img2 = img:copy()
for x= 1, img2.width do
for y = 1, img2.height do
r,g,b,a = img:get(x,y)
l=math.floor(255*luminence(color(r,g,b)))
img2:set(x,y,l,l,l,a)
end
end
popMatrix()
popStyle()
return img2
end
function colorize(img)
-- this function replaces the grays in the image with a color from the
-- color range pixel by pixel in two for loops.
local r,g,b,a,l,t,c,k
img2 = img:copy()
for x= 1, img2.width do
for y = 1, img2.height do
r,g,b,a = img:get(x,y)
l=math.floor(999*r/255)+1
r,g,b=range:get(3,l)
img2:set(x,y,r,g,b,a)
end
end
popMatrix()
popStyle()
return img2
end