I had previously posted a file in which a b&w image was colorized with jsDraw2d. That, however was an ad hoc file for just that one image.
This is a general app that can easily do it for all b&w (grayscale) images.
It can also be used for colored images, in which case colors can be modified, as well as allowing for freehand drawing.
This is the Control Panel:
Here is the image with selection points visible:
with the skin colored:
and with the lips colored:
It is of course personal, but I think an opacity of .3 to .4 works best.
Since the color is blended, it is better to have more saturation than you would want in the final product.
If any step is unsatisfactory, an Undo button reverts the drawing.
Clicking load with a new image chosen changes the embedded image and clicking load with Freehand checked removes the image to allow drawing rather then tracing.
Here is the code with most of the point arrays removed, since they would add length to this post, with no enlightenment:
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head profile=”http://gmpg.org/xfn/11″>
<title>Colorize</title>
<style>
body {margin-left:0;margin-right:0;font:normal normal normal 15px Arial;}
a{ text-decoration: }:link { color: rgb(0, 0, 255) }:visited {color :rgb(100, 0,100) }:hover { }:active { }
#frame, #frame2{position: absolute; left:0 ; top:30px ; width:100% ; height: 650px; }
#form1 {position: absolute; width: 1000px; left: 50%; margin-left: -500px; height: 30px}
#files{position: relative; width: 100px; font:normal normal 700 15px Arial}
#limt, #pts, #opac{position: relative; width: 25px; font:normal normal 700 15px Arial}
#opac{width: 40px}
</style>
</head>
<body>
<form id=”form1″ >
<center><input type=”button” id=”b1″ name=”b1″ value=”New” onclick=’nw();’/>
<input type=”button” id=”b3″ name=”b3″ value=”Undo” onclick=’document.getElementById(“frame”).innerHTML = bk;’ />
<input type=”file” id=”files” name=”files” />
<input type=”button” id=”b2″ name=”b2″ value=”Load” onclick=’ld();’ />
<input type=’color’ id=’color’ value= simple color />
# pts
<input id = “limt” type=”text” name=”limt” value=”0″ />
<input id = “pts” type=”text” name=”pts” value=”” />
<input id = “opac” type=”text” name=”opac” value=”Opac” />
<input type=”checkbox” id=”ch1″ style = “width: 15px” name=”ch1″ value=” ” />Freehand
</center>
</form>
<di id=’frame’><img id=”img1″ style=”position: absolute; top: 40px” src= “Karen3.jpg”height=”600px” /> </di>
<di id=’frame2′></di>
<di id = “instruc” style= “position:absolute ; width:500px ; height: ; left:50% ; margin-left: -250px; top: ; font: normal normal 600 15px Arial; color: #000000; background: white; border: 3px solid black; padding: 10px” onclick=’document.getElementById(“instruc”).style.visibility=”hidden”;’>
<center>Click Here to Close This Box</center><br />
If you do not want the default image, choose and load an image from the same folder as the app<br />
Choose the number of points, opacity, color<br />
For a Circle set the point number at 2<br />
Trace part of the image with clicks. The number of expired clicks will be indicated and dots will be drawn. When the entered number is reached, the selection will be filled or drawn and the dots will be removed<br />
Click New to start a new area<br />
Checking Freehand and clicking Load removes the image<br />
If not satisfied with any step click Undo<br />
</di>
if (document.getElementById(“ch1”).checked ) document.getElementById(“frame”).innerHTML = “”;
var IE = document.all?true:false;
document.onclick = getMouse2;
var tempX = 0;
var tempY = 0;
var lim = 0;
var xoff = 0;
var yoff = 0;
var cl = “”;
var cnt = 0; var cnt2 = 0;
var fname = “”;
var tmpstr = “”;
var px = new Array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
var py = new Array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
var xmin; var xmax; var ymin; var ymax; var el; var done = false; var bk = document.getElementById(“frame”).innerHTML;
var str2 = ‘ ‘; var bk2 = “”;
var str2a = ‘ ‘;
var gr= new jsGraphics(document.getElementById(“frame2”));
function nw() {
tempX = 0; tempY = 0; lim = 0; xoff = 0; yoff = 0; cl = “”; done = false; cnt = 0; fname = “”; tmpstr = “”; px = new Array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); py = new Array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); xmin = 0; xmax = 0; ymin = 0; ymax = 0;
document.getElementById(“limt”).value = “0”; document.getElementById(“pts”).value = “0”;
for (var i = 1; i -1) fil = fil.substr(12);
document.getElementById(“frame”).innerHTML = ‘‘;
if (document.getElementById(“ch1”).checked ) document.getElementById(“frame”).innerHTML = “”;
}
function getMouse2(e) {
cl = document.getElementById(“color”).value;
if (cnt 50) {
cnt ++;
document.getElementById(“pts”).value = cnt;
px[cnt] = tempX;
py[cnt]= tempY;
gr.fillCircle(new jsColor(“blue”),new jsPoint(px[cnt],py[cnt] – 30) , 3);
if (cnt == 1) {
xmin = px[1];
xmax = px[1];
ymin = py[1];
ymax = py[1];
}
if (cnt > 1) {
if (px[cnt] xmax) xmax = px[cnt];
if (py[cnt] ymax) ymax = py[cnt];
}
}
if (cnt == lim && cnt >= 2 && ! done) {
bk = document.getElementById(“frame”).innerHTML;
cnt2 ++;
var wd = xmax – xmin;
var ht = ymax – ymin;
for (var i = 1; i ‘;
gr[el2] = new jsGraphics(document.getElementById(“canvas”+ cnt2. toString() ));
if (cnt == 4) var points = new Array(new jsPoint( px[1], py[1]), new jsPoint( px[2], py[2]), new jsPoint( px[3], py[3]), new jsPoint( px[4], py[4]));
if (cnt == 30) var points = new Array(new jsPoint( px[1], py[1]), new jsPoint( px[2], py[2]), new jsPoint( px[3], py[3]), new jsPoint( px[4], py[4]), new jsPoint( px[5], py[5]), new jsPoint( px[6], py[6]), new jsPoint( px[7], py[7]), new jsPoint( px[8], py[8]), new jsPoint( px[9], py[9]), new jsPoint( px[10], py[10]), new jsPoint( px[11], py[11]), new jsPoint( px[12], py[12]), new jsPoint( px[13], py[13]), new jsPoint( px[14], py[14]), new jsPoint( px[15], py[15]), new jsPoint( px[16], py[16]), new jsPoint( px[17], py[17]), new jsPoint( px[18], py[18]), new jsPoint( px[19], py[19]), new jsPoint( px[20], py[20]), new jsPoint( px[21], py[21]), new jsPoint( px[22], py[22]), new jsPoint( px[23], py[23]), new jsPoint( px[24], py[24]), new jsPoint( px[25], py[25]), new jsPoint( px[26], py[26]), new jsPoint( px[27], py[27]), new jsPoint( px[28], py[28]), new jsPoint( px[29], py[29]), new jsPoint( px[30], py[30]));
gr[el2].fillClosedCurve(new jsColor(cl), points) ;
document.getElementById(“frame2”).innerHTML = “”;
done = true;
}
}
</body></html>
The app uses a listener to get get the screen location of clicks:
if (IE) {
tempX = event.clientX + document.body.scrollLeft;
tempY = event.clientY + document.body.scrollTop;
}
else {
tempX = e.pageX;
tempY = e.pageY;
}
if (tempX < 0){tempX = 0;}
if (tempY < 0){tempY = 0;}
Every time the screen is clicked, a variable cnt is incremented and its value placed in a box. This is how the number of clicks is tracked:
if (tempY > 50) {
cnt ++;
document.getElementById(“pts”).value = cnt;
also, the values of arrays px and py are set:
px[cnt] = tempX;
py[cnt]= tempY;
A small dot is placed at the point of the click:
var gr= new jsGraphics(document.getElementById(“frame2”));
gr.fillCircle(new jsColor(“blue”),new jsPoint(px[cnt],py[cnt] – 30) , 3);
There was a problem at this point.
I wanted the dots removed on filling the selections, but the method usually used would not work. Since the dots are drawn before the area is filled, a reversion to an earlier version would either remove the fill as well as the dots, or leave the dots and remove only the fill.
I resolved this by creating an identical background free layer on top of the base layer, drawing the dots in this layer. When the base layer is filled with a tinting layer, the dots can be removed from the second layer without affecting the drawing:
gr[el2].fillClosedCurve(new jsColor(cl), points) ;
document.getElementById(“frame2”).innerHTML = “”;
As in the previous colorizing post, each fill needed its own layer because of the transparency requirement.
The coordinates for the dots are drawn in the space of the base layer, while the fills require daughter layer coordinates.
A method to convert from one space to another was needed. This is how it was done:
if (cnt == 1) {
xmin = px[1];
xmax = px[1];
ymin = py[1];
ymax = py[1];
}
On the first click I set minimum and maximum x and y values.
if (cnt > 1) {
if (px[cnt] < xmin) xmin = px[cnt];
if (px[cnt] > xmax) xmax = px[cnt];
if (py[cnt] < ymin) ymin = py[cnt];
if (py[cnt] > ymax) ymax = py[cnt];
}
Each subsequent click did a comparison with the minimum and maximum values and replaced them if outside the limits.
if (cnt == lim && cnt >= 2 && ! done) {
cnt2 ++;
var wd = xmax – xmin;
var ht = ymax – ymin;
for (var i = 1; i <= lim; i ++) {
px[i] -= xmin;
py[i] -= (ymin + 30);
}
if (document.getElementById(“opac”).value != “Opac” ) var opc = document.getElementById(“opac”).value;
var el2 = cnt2 + 1;
document.getElementById(“frame”).innerHTML += ‘<di id = “canvas’ + cnt2 + ‘” style = “position: absolute; left:’ + xmin + ‘px; top:’ + ymin + ‘px; width:’ + wd + ‘px; height:’ + ht + ‘px; opacity:’ + opc + ‘” ></di> ‘;
If the number of clicks was reached, the layer dimensions were calculated:
var wd = xmax – xmin;
var ht = ymax – ymin;
the position of each click was placed in the daughter layer’s space:
for (var i = 1; i <= lim; i ++) {
px[i] -= xmin;
py[i] -= (ymin + 30);
}
and the layer was created with the assigned transparency.
A graphic had to be created for the new layer:
gr[el2] = new jsGraphics(document.getElementById(“canvas”+ cnt2. toString() ));
An array, points had to be created for the number of set clicks:
if (cnt == 4) var points = new Array(new jsPoint( px[1], py[1]), new jsPoint( px[2], py[2]), new jsPoint( px[3], py[3]), new jsPoint( px[4], py[4]));
and fillClosedCurve was used:
gr[el2].fillClosedCurve(new jsColor(cl), points) ;