Drawing bezier curve on html canvas - javascript

I need to draw a bezier curve defined by N control points which are stored in an array. I have a canvas which is 500px x 500px.
Here's the JSFiddle link : jsfiddle.net/HswXy
Entire JS code:
<script>
window.onload=function(){
var old_n=0,n=0;
var nrSelect = document.getElementById("mySelect");
var submit = document.getElementById("submit");
nrSelect.addEventListener("change",function(){
old_n=n;
n = nrSelect.selectedIndex;
var inputx,inputy,br;
if(document.getElementById("pointsdiv"))
{
for(i=1;i<=n;i++){
inputx = document.createElement('input');
inputy = document.createElement('input');
br = document.createElement('br');
inputx.type = "text";
inputy.type = "text";
inputx.size = 3;
inputy.size = 3;
inputx.id = "x_" + i;
inputy.id = "y_" + i;
inputx.value = "x_" + i;
inputy.value = "y_" + i;
inputx.addEventListener("focus",function(){if(this.value==this.id) this.value="";});
inputy.addEventListener("focus",function(){if(this.value==this.id) this.value="";});
document.getElementById("pointsdiv").appendChild(inputx);
document.getElementById("pointsdiv").appendChild(inputy);
document.getElementById("pointsdiv").appendChild(br);
}
document.getElementById("pointsdiv").id="pointsdiv_after";
}
else
{
if( old_n < n )
{
for(i=old_n+1;i<=n;i++){
inputx = document.createElement('input');
inputy = document.createElement('input');
br = document.createElement('br');
inputx.type = "text";
inputy.type = "text";
inputx.size = 3;
inputy.size = 3;
inputx.id = "x_" + i;
inputy.id = "y_" + i;
inputx.value = "x_" + i;
inputy.value = "y_" + i;
inputx.addEventListener("focus",function(){if(this.value==this.id) this.value="";});
inputy.addEventListener("focus",function(){if(this.value==this.id) this.value="";});
document.getElementById("pointsdiv_after").appendChild(inputx);
document.getElementById("pointsdiv_after").appendChild(inputy);
document.getElementById("pointsdiv_after").appendChild(br);
}
}
else
{
var parent;
for(i=n+1;i<=old_n;i++){
parent = document.getElementById("pointsdiv_after");
parent.removeChild(parent.lastChild);
parent.removeChild(parent.lastChild);
parent.removeChild(parent.lastChild);
}
}
}
});
//BEZIER CURVE
function factorial(n){
var result=1;
for(i=2;i<=n;i++){
result = result*i;
}
return result;
}
function Point(x,y){
this.x=x;
this.y=y;
}
var points = new Array();
function getPoint(t){
var i;
var x=points[0].x;
var y=points[0].y;
var factn = factorial(n);
for(i=0;i<n;i++){
var b = factn / (factorial(i)*factorial(n-i));
var k = Math.pow(1-t,n-i)*Math.pow(t,i);
// console.debug( i+": ",points[i] );
x += b*k*points[i].x;
y += b*k*points[i].y;
}
return new Point(x, y);
}
//--BEZIER CURVE
submit.addEventListener("click",function(){
if(n){
for(i=1;i<=n;i++){
var px = document.getElementById("x_"+i);
var py = document.getElementById("y_"+i);
points.push(new Point(parseInt(px.value,10),parseInt(py.value,10)));
// console.debug( points[i-1] );
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
console.debug( points[0].x, points[0].y );
context.moveTo(points[0].x, points[0].y);
var t=0.01;
while (t<=1)
{
//get coordinates at position
var p=getPoint(t);
// console.debug( p.x, p.y );
//draw line to coordinates
context.lineTo(p.x, p.y);
//increment position
t += 0.01;
}
context.stroke();
}
});
}
</script>
The problem is that it doesn't seem to work correctly. I'm testing it for 2 points now and it always starts in the upper left corner o the canvas (even if my first point is at let's say (250,250) and the length of the line is not the number of pixels it should be.. it's longer.

I'm no expert on canvas or geometry, but here is what I think: you shouldn't moveTo points[0] before drawing your faux line, since your getPoint function seems to lead you there. What I could see while debugging your code is that you first moved to (20,20), then drew a straight line to (roughly) (40,40), then started drawing gradually back to (20,20), generating that closed shape that wasn't what you were looking for.
A small (quick and dirty) change to your code made it start drawing a curve:
// DO NOT MOVE TO HERE
//context.moveTo(points[0].x, points[0].y);
var t = 0.01;
// MOVE TO THE FIRST POINT RETURNED BY getPoint
var p0 = getPoint(t);
context.moveTo(p0.x, p0.y);
t+=0.01;
// now loop from 0.02 to 1...
I'm sure you can refactor that into something better, as I said my changes were quick and dirty.
http://jsfiddle.net/HswXy/3/

Related

Paint by number Illustrator script

I am from supercoloring and we decided to convert our vector illustrations in color to color by number worksheets. Our input files are color and outline images in svg format.
Outline version (like a coloring page) + Color version
outline version and
color version
What we want to get is the following
result
We would like that a color palette is generated under the outline version of the image based on the color data from the color version of the image. Moreover, numbers corresponding to this palette are placed inside each color space of the outlined version.
I understand that no script in the world would do this properly, but at least I am striving to reduce the time spent by the editor (person) to put these numbers manually in the Illustrator. I understand that our color vector images may have too many colors and shades so we need somehow to limit the result colors of the palette ( to fuse them into large groups of basic colors).
I searched all over the stackoverflow solutions and found some ingenious like Paint with numbers with Adobe Illustrator Javascript and
I'm looking to create an automated numbering system for custom paint by number kits in photoshop (Kudos to Yuri Khristich). However, they are not exactly adapted to our needs.
Most of scripts on the web generate outlined images from color version, but the quality is compromised. We have already a proper outline version that we want to use as a base for color by number worksheet.
Here is the script to make a 'color palette' for selected artwork.
And here, as you know, is the script to add color names to all filled areas.
So I took the two script, made a couple of minimal tweaks and get almost the result you want. All you need after the scripts is to copy the layer with numbers and 'palette' from a colored artwork to a outline version.
Script #1
// Modified version
// https://stackoverflow.com/questions/75344674/paint-by-number-illustrator-script
// Original:
// https://productivista.com/make-a-list-of-colors-from-your-selection/
/*
Date: July, 2020
Author: Katja Bjerrum, email: katja#productivista.com, www.productivista.com
============================================================================
NOTICE:
This script is provided "as is" without warranty of any kind.
Free to use, not for sale.
============================================================================
Released under the MIT license.
http://opensource.org/licenses/mit-license.php
============================================================================
*/
//#target illustrator
var doc = app.activeDocument;
var myLayer = doc.activeLayer;
app.coordinateSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM;
var swGrps = doc.swatchGroups;
var mainSwGr = doc.swatchGroups[0];
var sel = doc.selection;
var actionSet = 'CreateSwatchGroup';
var actionName = 'ColourGroup';
var actionPath = Folder.myDocuments + '/Adobe Scripts/';
if (!Folder(actionPath).exists) Folder(actionPath).create();
//app.doScript("Colorgroup", "ToSwatchScript"); // Action, that creates swatch group
var actionDoc =
[ '/version 3',
'/name [' + actionSet.length + ' ' + ascii2Hex(actionSet) + ']',
'/isOpen 1',
'/actionCount 1',
'/action-1 {',
'/name [' + actionName.length + ' ' + ascii2Hex(actionName) + ']',
' /keyIndex 0',
' /colorIndex 0',
' /isOpen 1',
' /eventCount 1',
' /event-1 {',
' /useRulersIn1stQuadrant 0',
' /internalName (ai_plugin_swatches)',
' /localizedName [ 8',
' 5377617463686573',
' ]',
' /isOpen 0',
' /isOn 1',
' /hasDialog 1',
' /showDialog 1',
' /parameterCount 1',
' /parameter-1 {',
' /key 1835363957',
' /showInPalette 4294967295',
' /type (enumerated)',
' /name [ 15',
' 4e657720436f6c6f722047726f7570',
' ]',
' /value 17',
' }',
' }',
'}'].join('');
createAction(actionDoc, actionName, actionPath);
app.redraw();
app.doScript (actionName, actionSet);
app.redraw();
app.unloadAction(actionSet, '');
var convMM = 2.8346456692; // initialization of the variable to convert points to mm
var colorgroup = doc.swatchGroups[doc.swatchGroups.length - 1]; // Choose the last swatch group
var stY = -200; //
var stX = 20;
var recW = 25;
var recH = 25;
var offX = recW / 5;
var offY = recH / 4;
var textoffY = recH / 4;
var rows = 4;
var cols = 4;
var black = new GrayColor();
black.gray = 80;
var white = new GrayColor() ;
white.gray = 0;
var noStroke = doc.swatches[0].color;
if (swGrps.length <=1){
alert ("Please create swatch group from your selection");
}
else if (sel <= 0){
//docRef.placedItems[0].selected == false;
alert ("Please make a selection");
delSwatchGr(colorgroup); //delete swatch group
}
else{
swatchGroupList(colorgroup, stY, stX);//create corlor list
// delSwatchGr(colorgroup);//delete swatch group
}
//Function, that creates color list
function swatchGroupList(swatchGroup, stY, stX) {
// Groups everything in the list
var mainGroup = myLayer.groupItems.add();
mainGroup.name = "Colors";
mainGroup.moveToBeginning(myLayer);
//Name of the color list
var nameText = myLayer.textFrames.add();
nameText.contents = swatchGroup.name; // the name of the swatch group
nameText.position = [stX, stY + recH];
var nameStyle = nameText.textRange.characterAttributes;
nameStyle.size = 12;//size in punkt
//nameStyle.textFont = textFonts.getByName("Avenir-Book");//the font
nameStyle.capitalization = FontCapsOption.ALLCAPS;//ALL CAPITALS
var swatches = swatchGroup.getAllSwatches();
var swatchArray = [];
for (i = swatches.length-1; i>=0; i--) {
var mySwatch = swatches[i];
mySwatch.name = i + 1;
var subGroup = createSwatchGroup(mySwatch, textoffY);
swatchArray.push(subGroup);
}
nameText.moveToEnd(mainGroup);
var myGroup = swatchArray;
var maxW = maxWidth(myGroup);
for (var j = 0; j < myGroup.length; j++) {
var mySubGroup = myGroup[j];
mySubGroup.moveToBeginning(mainGroup);
}
for (var i = 0; i < mainGroup.groupItems.length; i++) {
var mySubGroup = mainGroup.groupItems[i];
if (mainGroup.groupItems.length > 7) {
rows = 7;
var c = i%rows;
var r = Math.floor(i/rows);
mySubGroup.position = [stX + r * (maxW + 10), stY - c * (recH + offY)];
}
else {
rows = 7;
var c = i % rows;
var r = Math.floor(i / rows);
mySubGroup.position = [stX, stY - c * (recH + offY)];
}
}
// textSwatch.moveToBeginning(SubGroup);
// path_ref.moveToBeginning(SubGroup);
// SubGroup.position = [stX + c * 140, stY - r * (path_ref.height + offY)];
subGroup.moveToBeginning(mainGroup);
}
function lightColor(c){
if(c.typename)
{
switch(c.typename)
{
case "CMYKColor":
return (c.black>=10 || c.cyan>10 || c.magenta>10 || c.yellow > 10) ? true : false;
case "RGBColor":
return (c.red<230 || c.green<230 || c.blue<230) ? true : false;
case "GrayColor":
return c.gray >= 10 ? true : false;
case "SpotColor":
return lightColor(c.spot.color);
//return false;
}
}
}
function fitItem(item, itemW, itemH, diff) {
var oldWidth = item.width
var oldHeight = item.height
if (item.width > item.height) {
// landscape, scale height using ratio from width
item.width = itemW - diff.deltaX
var ratioW = item.width / oldWidth
item.height = oldHeight * ratioW
} else {
// portrait, scale width using ratio from height
item.height = itemH - diff.deltaY
var ratioH = item.height / oldHeight
item.width = oldWidth * ratioH
}
}
function itemBoundsDiff(item) {
var itemVB = item.visibleBounds
var itemVW = itemVB[2] - itemVB[0] // right - left
var itemVH = itemVB[1] - itemVB[3] // top - bottom
var itemGB = item.geometricBounds
var itemGW = itemGB[2] - itemGB[0] // right - left
var itemGH = itemGB[1] - itemGB[3] // top - bottom
var deltaX = itemVW - itemGW
var deltaY = itemVH - itemGH
var diff = { deltaX: deltaX, deltaY: deltaY }
return diff
}
function delSwatchGr(swGr){
var swGrSws = swGr.getAllSwatches();
for (var j = 0; j < swGrSws.length; j++){
var sw = swGrSws[j];
sw.color = new CMYKColor();
}
swGr.remove();
}
//Function finds the max group width
function maxWidth(myGroup) {
var maxFound = 0;
for (var j = 0; j < myGroup.length; j++) {
var GrWidth = myGroup[j].width;
//var Widthmax = GrWidth.width;
maxFound = Math.max(maxFound, GrWidth);
}
return maxFound;
}
function createSwatchGroup(sw, myOffset) {
//Is "MyForm" path exists?
try{
var path_ref_ori = app.activeDocument.pathItems.getByName("MyForm" || "myform" || "MYFORM");
}
catch(e) {
var path_ref_ori = false;
}
if (path_ref_ori) {
myPath = path_ref_ori.duplicate();
var boundsDiff = itemBoundsDiff(myPath);
fitItem(myPath, recW, recH, boundsDiff);
myPath.name = "NewForm";
myPath.position = [0, 0];
}
else {
var myPath = createMyPath()
}
myPath.fillColor = sw.color;
myPath.stroked = true;
myPath.strokeWidth = 0.3;
myPath.strokeColor = lightColor(myPath.fillColor) ? noStroke : black;
var textSwatch = myLayer.textFrames.add(); //swatch text
textSwatch.contents = sw.name;
textSwatch.position = [myPath.width + 1.3 * convMM, -myOffset];
var textSwStyle = textSwatch.textRange.characterAttributes;
textSwStyle.size = 10; //size in punkt
//textSwStyle.textFont = textFonts.getByName("MyriadPro-Semibold"); //the font
var SubGroup = myLayer.groupItems.add(); //groups path and text
SubGroup.name = sw.name;
SubGroup.position = [0, 0];
textSwatch.moveToBeginning(SubGroup);
myPath.moveToBeginning(SubGroup);
return SubGroup;
}
function createMyPath(){
//Is "MyForm" path exists?
try{
var path_ref_ori = app.activeDocument.pathItems.getByName("MyForm" || "myform" || "MYFORM");
}
catch(e) {
var path_ref_ori = false;
}
if (path_ref_ori) {
path_ref = path_ref_ori.duplicate();
var boundsDiff = itemBoundsDiff(path_ref);
fitItem(path_ref, recW, recH, boundsDiff);
path_ref.name = "NewForm";
path_ref.position = [0, 0];
}
else {
var path_ref = myLayer.pathItems.rectangle(0, 0, recW, recH); //swatch path item
}
return path_ref
};
function createAction(str, set, path) {
var f = new File('' + path + '/' + set + '.aia');
f.open('w');
f.write(str);
f.close();
app.loadAction(f);
f.remove();
};
function ascii2Hex(hex) {
return hex.replace(/./g, function (a) { return a.charCodeAt(0).toString(16) });
};
Input (after select the artwork and run the script):
Result (added the global swatches and the 'color palette' at the bottom):
Script #2
// Based on:
// https://stackoverflow.com/questions/73705368/paint-with-numbers-with-adobe-illustrator-javascript
var doc = app.activeDocument,
lays = doc.layers,
WORK_LAY = lays.add(),
NUM_LAY = lays.add(),
i = lays.length - 1,
lay;
// main working loop
for (; i > 1; i--) {
//process each layer
lay = lays[i];
lay.name = lay.name + " Num:" + (i - 1); // i-1 as 2 layers beed added.
process(lay.pathItems, false);
process(lay.compoundPathItems, true); // if any
}
//clean up
NUM_LAY.name = "Numbers";
WORK_LAY.remove();
function process(items, isCompound) {
var j = 0,
b, xy, s, p, op;
for (; j < items.length; j++) {
// process each pathItem
op = items[j];
try { color = op.fillColor.spot.name } catch(e) { continue } // <-- HERE
// add stroke
if (isCompound) {
// strokeComPath(op);
} else {
// !op.closed && op.closed = true;
// op.filled = false;
// op.stroked = true;
};
b = getCenterBounds(op);
xy = [b[0] + (b[2] - b[0]) / 2, b[1] + (b[3] - b[1]) / 2];
s = (
Math.min(op.height, op.width) < 20 ||
(op.area && Math.abs(op.area) < 150)
) ? 20 : 40; // adjust font size for small area paths.
add_nums(color, xy, s); // <--- HERE
}
}
function getMinVisibleSize(b) {
var s = Math.min(b[2] - b[0], b[1] - b[3]);
return Math.abs(s);
}
function getGeometricCenter(p) {
var b = p.geometricBounds;
return [(b[0] + b[2]) / 2, (b[1] + b[3]) / 2];
}
// returns square of distance between p1 and p2
function getDist2(p1, p2) {
return Math.pow(p1[0] + p2[0], 2) + Math.pow(p1[1] + p2[1], 2);
}
// returns visibleBounds of a path in a compoundPath p
// which is closest to center of the original path op
function findBestBounds(op, p) {
var opc = getGeometricCenter(op);
var idx = 0,
d;
var minD = getDist2(opc, getGeometricCenter(p.pathItems[0]));
for (var i = 0, iEnd = p.pathItems.length; i < iEnd; i++) {
d = getDist2(opc, getGeometricCenter(p.pathItems[i]));
if (d < minD) {
minD = d;
idx = i;
}
}
return p.pathItems[idx].visibleBounds;
}
function applyOffset(op, checkBounds) {
var p = op.duplicate(WORK_LAY, ElementPlacement.PLACEATBEGINNING),
// offset value the small the better, but meantime more slow.
offset = function() {
var minsize = Math.min(p.width, p.height);
if (minsize >= 50) {
return '-1'
} else if (20 < minsize && minsize < 50) {
return '-0.5'
} else {
return '-0.2' // 0.2 * 2 (both side ) * 50 (Times) = 20
}
},
xmlstring = '<LiveEffect name="Adobe Offset Path"><Dict data="I jntp 2 R mlim 4 R ofst #offset"/></LiveEffect>'
.replace('#offset', offset()),
TIMES = 100; // if shapes are too large, should increase the value.
if (checkBounds) {
// check its size only if it needs, because it's too slow
while (TIMES-- && getMinVisibleSize(p.visibleBounds) > 3) p.applyEffect(xmlstring);
} else {
while (TIMES--) p.applyEffect(xmlstring);
}
return p;
}
function getCenterBounds(op) {
var originalMinSize = getMinVisibleSize(op.visibleBounds);
var p = applyOffset(op, false);
if (getMinVisibleSize(p.visibleBounds) > originalMinSize) {
// in some cases, path p becomes larger for some unknown reason
p.remove();
p = applyOffset(op, true);
}
var b = p.visibleBounds;
if (getMinVisibleSize(b) > 10) {
activeDocument.selection = [p];
executeMenuCommand("expandStyle");
p = activeDocument.selection[0];
if (p.typename == "CompoundPathItem") {
b = findBestBounds(op, p);
}
}
p.remove();
return b;
}
function add_nums(n, xy, s) {
var txt = NUM_LAY.textFrames.add();
txt.contents = n;
txt.textRange.justification = Justification.CENTER;
txt.textRange.characterAttributes.size = s;
txt.position = [xy[0] - txt.width / 2, xy[1] + txt.height / 2];
}
function strokeComPath(compoundPath) {
var p = compoundPath.pathItems,
l = p.length,
i = 0;
for (; i < l; i++) {
// !p[i].closed && p[i].closed = true;
// p[i].stroked = true;
// p[i].filled = false;
}
};
Result (added the layer with numbers after run the script):
Final outlined version with numbers and the 'color palette'
Note: you have to ungroup and unmask the color artwork before you run the Script #2.
Here is the results for the rest examples:
As you can see the 'final' artwork still need a quite amount of additional manual work: to move or remove extra numbers.
And it makes sense to reduce the number of colors in original color artworks (perhaps it's possible to do with a script to some extent, as well).

Replicating child elements in JavaScript [Bug]

I am trying to solve a problem for an assignment, where the objective is to build a game. The game loads with smiley faces shown across two columns(divs) laid out side by side. There is one extra smiley on the left div, and if the user clicks on that extra smiley, the next run of the game should:
Increase the number of smileys by 5, for the next run
Generate a whole new set of smileys for the second run(by removing existing smileys from the current run).
The code I have so far has a bug. If I include the code for removing child nodes from the current run of the game, in the next run 5 more smileys are not added. If I remove this code, then in the next run, 5 more smileys are added but the previous smileys are at the same position (meaning they are not removed).
Here's the JS code:
var numberOfFaces = 5;
var theLeftSide = document.getElementById("leftSide");
var theRightSide = document.getElementById("rightSide");
var theBody = document.getElementsByTagName("body")[0];
function generateFaces() {
//remove current children
while(theLeftSide.firstChild){
theLeftSide.removeChild(theLeftSide.firstChild);
}
while(theRightSide.firstChild){
theRightSide.removeChild(theRightSide.firstChild);
}
while(numberOfFaces > 0) {
var face = document.createElement("img");
face.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
var topValue = generateRandomNumber(0,400);
face.style.top = topValue+"px";
var leftValue = generateRandomNumber(0,400);
face.style.left = leftValue+"px";
theLeftSide.appendChild(face);
//changes
var leftSideImages = theLeftSide.cloneNode(true);
var leftSideLastImage = leftSideImages.lastChild;
leftSideImages.removeChild(leftSideLastImage);
theRightSide.appendChild(leftSideImages);
//4th - generate 5 more faces for correct click in left side
theLeftSide.lastChild.onclick = function nextLevel(event){
event.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
numberOfFaces--;
}
}
function generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max-min + 1));
}
And this is the link to the Fiddle.
I also made a Codepen pen that has the same code.
Would appreciate any help.
Update -
My function structure was somewhat different in earlier attempts. I revised it based on some forum comments. Here's the earlier structure-
function generateFaces() {
//remove current children
while(theLeftSide.firstChild){
theLeftSide.removeChild(theLeftSide.firstChild);
}
while(theRightSide.firstChild){
theRightSide.removeChild(theRightSide.firstChild);
}
while(numberOfFaces > 0) {
var face = document.createElement("img");
face.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
var topValue = generateRandomNumber(0,400);
face.style.top = topValue+"px";
var leftValue = generateRandomNumber(0,400);
face.style.left = leftValue+"px";
theLeftSide.appendChild(face);
numberOfFaces--;
}
//changes
var leftSideImages = theLeftSide.cloneNode(true);
var leftSideLastImage = leftSideImages.lastChild;
leftSideImages.removeChild(leftSideLastImage);
theRightSide.appendChild(leftSideImages);
//4th - generate 5 more faces for correct click in left side
theLeftSide.lastChild.onclick = function nextLevel(event){
event.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
//numberOfFaces--;
}
You are decrementing numberOfFaces to zero in the while loop and add 5 again, so the value is always 5.
Change the while loop to
for(var n = 0; n < numberOfFaces; n++) {
var face = document.createElement("img");
face.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
var topValue = generateRandomNumber(0,400);
face.style.top = topValue+"px";
var leftValue = generateRandomNumber(0,400);
face.style.left = leftValue+"px";
theLeftSide.appendChild(face);
...
}
and each run adds an extra 5 smiley set. Altered fiddle here
Append your right child after while loop:
var numberOfFaces = 5;
var theLeftSide = document.getElementById("leftSide");
var theRightSide = document.getElementById("rightSide");
var theBody = document.getElementsByTagName("body")[0];
function generateFaces() {
//remove current children
while(theLeftSide.firstChild){
theLeftSide.removeChild(theLeftSide.firstChild);
}
while(theRightSide.firstChild){
theRightSide.removeChild(theRightSide.firstChild);
}
while(numberOfFaces > 0) {
var face = document.createElement("img");
face.setAttribute("src", "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png");
//face.src = "http://home.cse.ust.hk/~rossiter/mooc/matching_game/smile.png";
var topValue = generateRandomNumber(0,400);
face.style.top = topValue+"px";
var leftValue = generateRandomNumber(0,400);
face.style.left = leftValue+"px";
theLeftSide.appendChild(face);
//changes
var leftSideImages = theLeftSide.cloneNode(true);
var leftSideLastImage = leftSideImages.lastChild;
leftSideImages.removeChild(leftSideLastImage);
//4th - generate 5 more faces for correct click in left side
theLeftSide.lastChild.onclick = function nextLevel(event){
event.stopPropagation();
numberOfFaces += 5;
generateFaces();
};
theBody.onclick = function gameOver() {
alert("Game Over!");
theBody.onclick = null;
theLeftSide.lastChild.onclick = null;
};
numberOfFaces--;
}
theRightSide.appendChild(leftSideImages);
}
function generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max-min + 1));
}
Check out codepen

Repeat Horizontally the same image on div

Im making a game with javascript, css and html only, it's a simple game, where the character must hit the cakes so you get more points. Im having an issue, on the cakes movement and positioning. I could set them on the screen randomly and going horizontally to the right, but I can't that my function repeats itself nor limit the cakes to a certain zone of the screen.
This is my HTML:
<script language ="javascript">
function createNewCake() {
var myDiv = document.createElement('div');
myDiv.id = "cake0";
myDiv.className = "cake-div";
return myDiv;
}
function rotateElement(elem, degrees) {
var rad=degrees*(Math.PI / 180.0);
var text = "rotate(" + degrees + "rad)";
elem.style.transform = text;
elem.style.webkitTransform = text;
elem.style.mozTransform = text;
elem.style.oTransform = text;
elem.style.msTransform = text;
}
var cakes = new Array();
var colors = new Array();
colors[0] = "images/bolo1.png";
colors[1] = "images/bolo2.png";
colors[2] = "images/bolo3.png";
colors[3] = "images/maca.png";
for (var i=0; i<20; i++) {
var aCake = createNewCake();
aCake.id = "cake" + i;
var index = Math.floor(Math.random() * (colors.length + 1));
aCake.style.backgroundImage = "url('" + colors[index]+ "')";
aCake.style.left = Math.floor(Math.random() * window.innerWidth);
aCake.style.top = Math.floor(Math.random() * window.innerHeight);
document.body.appendChild(aCake);
cakes.push(aCake);
}
animate();
function animate() {
requestAnimationFrame(animate);
for (var i=0; i < cakes.length; i++) {
var pixel = Number(cakes[i].style.left.replace("px",""));
cakes[i].style.left = pixel + 2 + "px";
}
}
</script>
</body>
I got it done, thanks all!
It was just if checking if the image pixel is hitting the max width! :D

How can I access imageData from a renderTarget?

I'm a university masters degree student in Computer Graphics, I'm having difficulty using three.js to access the image data(pixels) of a texture created with a EffectComposer.
The first composer (composer) is using a line detection shader to find road lines in a lane, and put the result in a renderTarget (rt_Binary). My second composer (fcomposer2) uses a shader that paints an area green if is within a certain space.
The plan was to render the composer first and after analysing the rt_Binary image i could determine the limits.
I found some functions that allow me to get the imagedata (getImageData(image) and getPixel(imagedata, x, y)) but they only work on these occasions:
// before image
var imagedata = getImageData(videoTexture.image);
// processed image
var imagedata2 = getImageData(renderer.domElement);
If a put the first composer to render to screen, i get the correct values for the limits, but when i put the second composer, i get the wrong values for the limits.
Is there any way to get the imageData from a renderTarget? is so, how?
Edit1:
Here's the code for script that i use for the html:
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<title>Tests WebGL</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="three.js/build/three.js"></script>
<script src="js/CopyShader.js"></script>
<script src="js/EffectComposer.js"></script>
<script src="js/MaskPass.js" ></script>
<script src="js/RenderPass.js" ></script>
<script src="js/ShaderPass.js"></script>
<script src="js/stats.min.js" ></script>
<!-- Shaders -->
<script src="js/shaders/KernelShader.js" ></script>
<script src="js/shaders/SimpleShader.js"></script>
<script src="js/shaders/MyShader.js"></script>
<script src="js/shaders/BinaryShader.js"></script>
<script type="text/javascript">
var scene, fscene, sceneF;
var camera;
var renderer, rt_Binary;
var composer;
var stats;
var fmaterial;
var videoTexture;
var videoWidth = 480;
var videoHeight = 270;
var rendererWidth = videoWidth;
var rendererHeight = videoHeight;
var x_max = 345;//videoWidth*0.72; //
var x_min = 120;//videoWidth*0.25; //
var y_max = 189;//videoHeight*0.7 ;
var y_min = 148;//videoHeight*0.55;
// var ml=0.0, mr=0.0, mm=0.0;
// var bl=0.0, br=0.0, bm=0.0;
var yMaxL = 0, yMinL = 0, yMaxR = 0, yMinR = 0;
var xMaxL = 0, xMinL = 0, xMaxR = 0, xMinR = 0;
var frame = 0;
// init the scene
window.onload = function() {
renderer = new THREE.WebGLRenderer(
{
antialias: true, // to get smoother output
preserveDrawingBuffer: true // to allow screenshot
});
renderer.setClearColor(0xffffff, 1);
renderer.autoClear = false;
renderer.setSize(rendererWidth, rendererHeight);
document.getElementById('container').appendChild(renderer.domElement);
//add stats
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
document.getElementById('container').appendChild(stats.domElement);
// create Main scene
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(35, rendererWidth / rendererHeight, 1, 10000);
camera.position.set(0, 1, 6);
scene.add(camera);
// define video element
video = document.createElement('video');
// video.src = 'GOPR0007.webm';
video.src = 'output.webm';
video.width = videoWidth;
video.height = videoHeight;
video.autoplay = true;
video.loop = true;
//create 3d object and apply video texture to it
var videoMesh = new THREE.Object3D();
scene.add(videoMesh);
videoTexture = new THREE.Texture(video);
var geom = new THREE.PlaneGeometry(1, 1);
material = new THREE.MeshBasicMaterial({map: videoTexture});
var mesh = new THREE.Mesh(geom, material);
videoMesh.add(mesh);
var renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBufer: false };
rt_Binary = new THREE.WebGLRenderTarget( videoWidth, videoHeight, renderTargetParameters );
// Composers
// composer = new THREE.EffectComposer(renderer, renderTarget2);
composer = new THREE.EffectComposer(renderer, rt_Binary );
composer.addPass(new THREE.RenderPass(scene, camera));
var simple = new SimpleShader.Class(videoWidth, videoHeight);
var simEffect = new THREE.ShaderPass(simple.shader);
composer.addPass(simEffect);
var ef = new BinaryShader.Class(videoWidth, videoHeight, 1.1, [-2,-2,-2,0,0,0,2,2,2]);
var effect = new THREE.ShaderPass(ef.shader);
composer.addPass(effect);
var copyPass = new THREE.ShaderPass(THREE.CopyShader);
// copyPass.renderToScreen = true;
composer.addPass(copyPass);
//New scene
sceneF = new THREE.Scene();
sceneF.add(camera);
var videoMesh2 = new THREE.Object3D();
sceneF.add(videoMesh2);
var geomF = new THREE.PlaneGeometry(1, 1);
var materialF = new THREE.MeshBasicMaterial({map: videoTexture});
var meshF = new THREE.Mesh(geomF, materialF);
sceneF.add(meshF);
fcomposer2 = new THREE.EffectComposer(renderer );
fcomposer2.addPass(new THREE.RenderPass(sceneF, camera));
fcomposer2.addPass(simEffect);
var ef1 = new MyShader.Class(videoWidth, videoHeight, [yMaxL,yMinL,xMaxL,xMinL,yMaxR,yMinR,xMaxR,xMinR], videoTexture);
var effect1 = new THREE.ShaderPass(ef1.shader);
fcomposer2.addPass(effect1);
var copyPass2 = new THREE.ShaderPass(THREE.CopyShader);
copyPass2.renderToScreen = true;
fcomposer2.addPass(copyPass2);
animate();
}
// animation loop
function animate() {
// loop on request animation loop
// - it has to be at the begining of the function
requestAnimationFrame(animate);
// do the render
render();
stats.update();
if ((frame % 50) == 0) {
console.log("frame ", frame, " ");
console.log("yMaxL: ", yMaxL, " ");
console.log("yMinL: ", yMinL, " ");
console.log("xMaxL: ", xMaxL, " ");
console.log("xMinL: ", xMinL, " ");
console.log("yMaxR: ", yMaxR, " ");
console.log("yMinR: ", yMinR, " ");
console.log("xMaxR: ", xMaxR, " ");
console.log("xMinR: ", xMinR, " ");
manipulatePixels();
}
frame = frame + 1;
yMaxL = 0, yMinL = 0, yMaxR = 0, yMinR = 0;
xMaxL = 0, xMinL = 0, xMaxR = 0, xMinR = 0;
}
// render the scene
function render() {
if (video.readyState === video.HAVE_ENOUGH_DATA) {
videoTexture.needsUpdate = true;
}
// actually render the scene
renderer.clear();
composer.render();
var left_x = new Array();
var left_y = new Array();
var l = 0;
var right_x = new Array();
var right_y = new Array();
var r = 0;
if (frame == 200) {
var imagedata2 = getImageData(renderer.domElement);
var middle = imagedata2.width / 2;
for (var x=x_min; x < x_max; x=x+1) {
for (var y=y_min; y < y_max; y=y+1) {
var pixel = getPixel(imagedata2, x, y);
if (pixel.g > 0)
{
//console.log(pixel);
if (x < middle) {
left_x[l] = x;
left_y[l] = y;
l++;
}
else {
right_x[r] = x;
right_y[r] = y;
r++;
}
}
}
}
lineEquation(left_x, left_y, right_x, right_y);
}
fcomposer2.render();
}
function lineEquation(left_x,left_y,right_x,right_y) {
var newYMAX = left_y[0];
var newYMIN = left_y[0];
var maximosL = new Array();
var minimosL = new Array();
//left
for (var i=1; i < left_y.length; i++) {
if (left_y[i]>newYMAX) newYMAX = left_y[i];
else {
if (left_y[i]<newYMIN) newYMIN = left_y[i];
}
}
yMaxL = newYMAX;
yMinL = newYMIN;
// yMaxL = ymaxL/videoHeight;
// yMinL = yminL/videoHeight;
var pmin=0, pmax=0;
for (var i=0; i < left_y.length; i++) {
if (left_y[i] === newYMAX) {
// console.log(left_y[i]);
// console.log(left_x[i]);
maximosL[pmax] = left_x[i];
pmax++;
}
}
for (var j=0; j < left_y.length; j++) {
if (left_y[j] === newYMIN) {
// console.log(left_y[j]);
// console.log(left_x[j]);
minimosL[pmin] = left_x[j];
pmin++;
}
}
// console.log(maximosL);
// console.log(minimosL);
var sumMAX = 0, sumMIN = 0;
for (var i=0; i< maximosL.length; i++) {
sumMAX = sumMAX + maximosL[i];
}
for (var j=0; j< minimosL.length; j++) {
sumMIN = sumMIN + minimosL[j];
}
xMaxL = sumMAX/maximosL.length;
xMinL = sumMIN/minimosL.length;
// xMaxL /= videoWidth;
// xMinL /= videoWidth;
//right
var maximosR = new Array();
var minimosR = new Array();
newYMAX = right_y[0];
newYMIN = right_y[0];
pmin=0; pmax=0;
for (var i=0; i < right_y.length; i++) {
if (right_y[i]> newYMAX) newYMAX = right_y[i];
else {
if (right_y[i]< newYMIN) newYMIN = right_y[i];
}
}
yMaxR = newYMAX;
yMinR = newYMIN;
// yMaxR = ymaxR/videoHeight;
// yMinR = yminR/videoHeight;
for (var i=0; i < right_y.length; i++) {
if (right_y[i] === newYMAX)
{maximosR[pmax] = right_x[i]; pmax++;}
if (right_y[i] === newYMIN)
{minimosR[pmin] = right_x[i]; pmin++;}
}
// console.log(maximosR);
// console.log(minimosR);
xMaxR=0;
for (var i=0; i< maximosR.length; i++) {
xMaxR += maximosR[i];
}
xMinR=0;
for (var i=0; i< minimosR.length; i++) {
xMinR += minimosR[i];
}
// console.log(xMaxR);
// console.log(xMinR);
xMaxR /= maximosR.length;
xMinR /= minimosR.length;
// console.log(xMaxR);
// console.log(xMinR);
// xMinR /= videoWidth;
// xMaxR /= videoWidth;
}
function manipulatePixels() {
// imagem antes
var imagedata = getImageData(videoTexture.image);
// imagem processada
var imagedata2 = getImageData(renderer.domElement);
// console.log(getPixel(imagedata, 480 - 1, 270 - 1));
// console.log(getPixel(imagedata2, 480 - 1, 270 - 1));
}
function getImageData(image) {
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
return context.getImageData(0, 0, image.width, image.height);
}
function getPixel(imagedata, x, y) {
var position = (x + imagedata.width * y) * 4, data = imagedata.data;
return {r: data[ position ], g: data[ position + 1 ], b: data[ position + 2 ], a: data[ position + 3 ]};
}
function findLineByLeastSquares(values_x, values_y) {
var sum_x = 0;
var sum_y = 0;
var sum_xy = 0;
var sum_xx = 0;
/*
* We'll use those variables for faster read/write access.
*/
var x = 0;
var y = 0;
var values_length = values_x.length;
if (values_length != values_y.length) {
throw new Error('The parameters values_x and values_y need to have same size!');
}
/*
* Nothing to do.
*/
if (values_length === 0) {
return [ [], [] ];
}
/*
* Calculate the sum for each of the parts necessary.
*/
for (var v = 0; v < values_length; v++) {
x = values_x[v];
y = values_y[v];
sum_x += x;
sum_y += y;
sum_xx += (x*x);
sum_xy += (x*y);
}
console.log (sum_x);
console.log(sum_y);
console.log(sum_xx);
console.log(sum_xy);
console.log(values_length);
/*
* Calculate m and b for the formular:
* y = x * m + b
*/
var m = (sum_x*sum_y - values_length*sum_xy) / (sum_x*sum_x - values_length*sum_xx);
var b = (sum_y - (m*sum_x))/values_length;
//console.log([m,b]);
return [m, b];
}
//resize method
/**window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
} */
</script>
Edit2 : Some images of what i'm trying to do: Image 1 shows the results from composer on the console, the limits i get from the lineEquation function are the correct ones for what i intend to do, but in Image 2 shows the results from fcomposer2 (fixed area) and on the console, the limits are the wrong ones.
![Image1]: http://prntscr.com/1ays73
![Image2]: http://prntscr.com/1ays0j
Edit3 :
By "access" i mean to be able to read the values of the pixels from the texture created by the binaryShader.
For example, in image1 the lines are painted in blue/green tone, I wanted to search the position of the pixels (x,y) in the image that the renderTarget would save. If i could find those pixels, i could adapt the green area in image2 to fit between the road lines.
This processing is need to make the green area overlap the current driving lane the user is currently on, if i can't get those points, i can't identify a lane.
I got it to work. Apparently i forgot to declare the fcomposer2 in the beginning of the script.
Thanks for the responses/comments, and sorry for the inconvenience.

Faulty Logic in this image color equalization algorithm

Somewhere in my code, I seem to be doing something wrong and I cannot tell which part is going awry. I've printed to console the values I'm getting from the various arrays and it seems to match up. Then when I run the equalization function (a la Wikipedia-Histogram Equalization) my output image is close to total black. I was trying to interpret this guy's php into javascript just to test some stuff out and figured I did a decent job. But I'm not an expert.
The pertinent parts:
function imageLoaded(ev) {
element = document.getElementById("canvas1");
c = element.getContext("2d");
im = ev.target; // the image
// read the width and height of the canvas
width = element.width;
height = element.height;
// stamp the image on the left of the canvas:
c.drawImage(im, 0, 0);
// get all canvas pixel data
imageData = c.getImageData(0, 0, width, height);
w2 = width / 2;
var reds = new Array();
var greens = new Array();
var blues = new Array();
var freqR = new Array();
var freqG = new Array();
var freqB = new Array();
if (imageData){
buildHistograms(reds, greens,blues);
buildFrequencies(reds, greens, blues, freqR, freqG, freqB);
}
var alpha = 255/(w2*height);
// run through the image
for (y = 0; y < height; y++) {
inpos = y * width * 4; // *4 for 4 ints per pixel
outpos = inpos + w2 * 4;
for (x = 0; x < w2; x++) {
//reads pixel data(of img c)to each channel of rgb
r = imageData.data[inpos++];
g = imageData.data[inpos++];
b = imageData.data[inpos++];
a = imageData.data[inpos++];
//using histogram eqalization formula:
adjR = freqR[r]*alpha;
adjG = freqG[g]*alpha;
adjB = freqB[b]*alpha;
//assigns pixel data of output image
imageData.data[outpos++] = adjR;
imageData.data[outpos++] = adjG;
imageData.data[outpos++] = adjB;
imageData.data[outpos++] = a;
}
}
// put pixel data on canvas
c.putImageData(imageData, 0,0);
}
im = new Image();
im.onload = imageLoaded;
im.src = "Lenna.png";
function buildHistograms(reds,greens,blues){
//run through image building histogram
for (y=0; y < height; y++){
inpos = y * width *4;
for (x=0; x < w2; x++){
rd = imageData.data[inpos++];
g = imageData.data[inpos++];
b = imageData.data[inpos++];
a = imageData.data[inpos++];
// Add counts to our histogram arrays for each color.
reds.push(rd);
greens.push(g);
blues.push(b);
}
}
// Sort them by keys into order
reds.sort(function(a,b){return a-b});
greens.sort(function(a,b){return a-b});
blues.sort(function(a,b){return a-b});
}
function buildFrequencies(reds, greens, blues, freqR, freqG, freqB){
// Build frequency charts for all colors: takes REDS GREENS BLUES from buildHistograms and places them on top of each other accordingly
for(i=0; i<=255; i++){
sumR=0;
sumG=0;
sumB=0;
for(j=0; j<= i; j++){
if (reds[j]){sumR+=reds[j];}
if (greens[j]){sumG+=greens[j];}
if (blues[j]){sumB+=blues[j];}
}
freqR[i] = sumR;
freqG[i] = sumG;
freqB[i] = sumB;
}
}
Any help is appreciated, Thanks.
Looks like my build frequencies section was all wrong. I modified it in this way:
var len = reds.length;
for (j=0; j < len; j++) {
var rCurrVal = reds[j];
var gCurrVal = greens[j];
var bCurrVal = blues[j];
if (freqR.hasOwnProperty(rCurrVal)) {
freqR[rCurrVal] += 1;
} else {
freqR[rCurrVal] = 1;
}
if (freqG.hasOwnProperty(gCurrVal)) {
freqG[gCurrVal] += 1;
} else {
freqG[gCurrVal] = 1;
}
if (freqB.hasOwnProperty(bCurrVal)) {
freqB[bCurrVal] += 1;
} else {
freqB[bCurrVal] = 1;
}
}
for (i=0; i<255; i++){
if ($.inArray(i,reds)===-1){freqR[i]=0;}
if ($.inArray(i,greens)=== -1){freqG[i]=0;}
if ($.inArray(i,blues)=== -1){freqB[i]=0;}
if (i>0){
freqR[i]+=freqR[i-1];
freqG[i]+=freqG[i-1];
freqB[i]+=freqB[i-1];
}
}

Categories

Resources