I have an interactive application mockup made with PaperJS but it still lacks a small feature. I need to draw a 2D grid (you know... that uniform mesh of lines that repeat endlessly over a surface), it will be used as guides for user interactions when dragging things over the screen (but the grid itself can be completely static).
I just don't know how to implement it in PaperJS. It can't be just a background image since it will be presented in different scales, also I wanted it to be rendered very fast since it will always be visible.
The type of grid I would like to draw is a 2D mesh centered grid, like in the example (a) of this picture:
Any enlightenment is welcome.
If all you want is lines:
var drawGridLines = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
var width_per_rectangle = boundingRect.width / num_rectangles_wide;
var height_per_rectangle = boundingRect.height / num_rectangles_tall;
for (var i = 0; i <= num_rectangles_wide; i++) {
var xPos = boundingRect.left + i * width_per_rectangle;
var topPoint = new paper.Point(xPos, boundingRect.top);
var bottomPoint = new paper.Point(xPos, boundingRect.bottom);
var aLine = new paper.Path.Line(topPoint, bottomPoint);
aLine.strokeColor = 'black';
}
for (var i = 0; i <= num_rectangles_tall; i++) {
var yPos = boundingRect.top + i * height_per_rectangle;
var leftPoint = new paper.Point(boundingRect.left, yPos);
var rightPoint = new paper.Point(boundingRect.right, yPos);
var aLine = new paper.Path.Line(leftPoint, rightPoint);
aLine.strokeColor = 'black';
}
}
drawGridLines(4, 4, paper.view.bounds);
If you want each rectangle to be a separate Path to hitTest for the individual rectangles:
var drawGridRects = function(num_rectangles_wide, num_rectangles_tall, boundingRect) {
var width_per_rectangle = boundingRect.width / num_rectangles_wide;
var height_per_rectangle = boundingRect.height / num_rectangles_tall;
for (var i = 0; i < num_rectangles_wide; i++) {
for (var j = 0; j < num_rectangles_tall; j++) {
var aRect = new paper.Path.Rectangle(boundingRect.left + i * width_per_rectangle, boundingRect.top + j * height_per_rectangle, width_per_rectangle, height_per_rectangle);
aRect.strokeColor = 'white';
aRect.fillColor = 'black';
}
}
}
drawGridRects(4, 4, paper.view.bounds);
Related
Trying to create multiple canvas element on a single html page and then draw different graphics on each of them . But the issue is , the same graphics is drawn on all of them without any randomization
canvas_container_div = document.getElementById('canvas_container_div');
let animation_frame_id;
let canvas_array = [];
let context_array = [];
let number_of_canvas = 5;
//creating canvas and storing it in an array
for(var i = 0;i < number_of_canvas ; i++){
var canvas1 = document.createElement('canvas');
canvas1.style.width = '200px';
canvas1.style.height = '200px';
canvas1.style.margin = '10px';
canvas1.style.border = '1px solid white';
canvas_array.push(canvas1);
}
//displaying all canvas inside the div element
for(var i = 0;i < canvas_array.length ; i++){
canvas_container_div.appendChild(canvas_array[i]);
}
//getting all the contex for all the canvas
for(var i = 0;i < canvas_array.length ; i++){
context_array.push(canvas_array[i].getContext('2d'));
}
//random values generating
let hue = Math.random()*360;
//or other radom parameters
//updating each graphics
function update(ctx){
ctx.fillStyle = 'hsl('+hue+',100%,50%)';
}
function render(){
//getting all the context
for(var i = 0;i < context_array.length ; i++){
//clearing bg for perticular canvas
context_array[i].clearRect(0,0,canvas_array[i].width,canvas_array[i].height);
//passing perticular canvas context to update method
update(context_array[i]);
//drawing with pertucular context
context_array[i].beginPath();
context_array[i].arc(canvas_array[i].width/2,canvas_array[i].height/2,40,0,Math.PI *2);
context_array[i].closePath();
context_array[i].fill();
}
animation_frame_id = requestAnimationFrame(render);
}
render();
Wanted to have different color for all the circle drawn on different canvas , but all the circles are of same color . cannot randomize
You are defining your hue outside of your renders for loop, so each time it runs the loop and calls on the hue, it is getting the same color for the fill style. You could create a function that randomizes the hue inside a for loop to to create an array of colors that is the same length as your canvas array.
Then in your for loop within the render function call on the array and its index to make each individual circle a different color fill style.
NOTE: This only randomizes the colors, it does not check if a color already exists, so you may want additional code to check if a number is already in the array before pushing the value into the array. Furthermore, you will notice some hues randomize within a certain number close enough to each other that they actually look like they are the same, you could also include code within the setHue() function that checks to see if the the numbers are within a certain restraint of each other, this would likely be a .include() or even a conditional that checks the difference between the hue array and the current value within the loop.
let hue = [];
function setHue() {
for (let h = 0; h < context_array.length; h++) {
hue.push(Math.trunc(Math.random() * 360));
}
}
setHue();
canvas_container_div = document.getElementById('canvas_container_div');
let animation_frame_id;
let canvas_array = [];
let context_array = [];
let number_of_canvas = 5;
//creating canvas and storing it in an array
for (var i = 0; i < number_of_canvas; i++) {
var canvas1 = document.createElement('canvas');
canvas1.style.width = '200px';
canvas1.style.height = '200px';
canvas1.style.margin = '10px';
canvas1.style.border = '1px solid white';
canvas_array.push(canvas1);
}
//displaying all canvas inside the div element
for (var i = 0; i < canvas_array.length; i++) {
canvas_container_div.appendChild(canvas_array[i]);
}
//getting all the contex for all the canvas
for (var i = 0; i < canvas_array.length; i++) {
context_array.push(canvas_array[i].getContext('2d'));
}
//Randomize your hue value and make an array to hold the value
//Then in your for loop within the render function call on the
//array and its index to make each individual circle a different color fill style
let hue = [];
function setHue() {
for (let h = 0; h < context_array.length; h++) {
let color = Math.trunc(Math.random() * 360);
hue.push(color);
}
console.log(hue);
}
setHue();
function render() {
//getting all the context
for (var i = 0; i < context_array.length; i++) {
//clearing bg for perticular canvas
context_array[i].clearRect(0, 0, canvas_array[i].width, canvas_array[i].height);
//drawing with particular context
context_array[i].beginPath();
context_array[i].arc(canvas_array[i].width / 2, canvas_array[i].height / 2, 40, 0, Math.PI * 2);
context_array[i].closePath();
context_array[i].fill();
context_array[i].fillStyle = 'hsl(' + hue[i] + ',100%,50%)';
}
animation_frame_id = requestAnimationFrame(render);
}
render();
<div id="canvas_container_div"></div>
My brain is not working properly today and I can't seem to figure this out.
I have a RGBA image data stored in an Uint8Array() and need to scale the width only.
var w = 160;
var h = 200;
var depth=4;
var pixels = new Uint8Array(w*h*depth);
I need to scale the pixels array to 320x200 and every attempt I did ended up with a garbled image.
Thanks to Yves Daoust I revisited some of my old attempts at solving this by duplicating every chunk of 4 to the destination, and now I got it working. So thank you Yves :) I do not know what I did wrong earlier.
This is the working code that I ended up with. I'm 100% sure it can be done differently and better, but at this point I am satisfied :)
Utils.prototype.scalePixelsInWidth = function(pixels) {
var w = 320;
var h = 200;
var scanlineWidth = w*4;
var scaledPixels = new Uint8Array(w*h*4);
var a = 0;
for(let row=0;row<h;row++) {
var col2 = 0;
for(let col=0;col<w;col++) {
var srcIndex = col2*4 + (row*(w/2)*4);
var destIndex = col*4 + (row * scanlineWidth);
scaledPixels[destIndex+0] = pixels[srcIndex+0];
scaledPixels[destIndex+1] = pixels[srcIndex+1];
scaledPixels[destIndex+2] = pixels[srcIndex+2];
scaledPixels[destIndex+3] = pixels[srcIndex+3];
a++;
if (a > 1) {
a = 0;
col2++;
}
}
}
return scaledPixels;
}
I am writing an HTML5 game using the engine Phaser, in which I am implementing what are essentially live backgrounds, backgrounds that respond to the movements of the game objects. The first I am working with is a water ripple effect that uses area sampling on the bitmapData object. I thought I had a performance issue in my code, but it turns out that Firefox runs it like a dream. Chrome runs a little slower to begin with and slows to less than 10 FPS when my game objects go too close to the top or bottom of the screen. (I am at a loss for why that makes a difference.)
This thread suggests that Chrome has poor image processing performance and suggests to break large image data up into smaller pieces. I don't know if this is possible in my case, because this is not simply an image displaying on the screen but an effect based on pixels next to each other that refreshes each frame. Even if it is possible, I think Chrome would end up having to do the same amount of work or more to get the four individual bitmaps to interact with each other as if they were one.
I've been doing performance tests in Chrome for a few hours, and the issue is definitely that it is getting caught up on the method that actually creates the effect by reading pixels from a source imageData and writing them to another location in a target imageData (the ws.displace(x,y) method below).
function waterStage(canvas) {
var ws = new Object();
ws.dampFactor = 16;
ws.magFactor = 150;
ws.dispFactor = 0.5;
ws.lumFactor = 1;
ws.width = canvas.width;
ws.height = canvas.height;
// Initialize height data caches
ws.pMaps = [];
var map1 = new Array(ws.width+2);
var map2 = new Array(ws.width+2);
for (x=0; x < map1.length; x++) {
map1[x] = new Array(ws.height+2);
map2[x] = new Array(ws.height+2);
}
for (x=0; x < map1.length; x++) {
for (y=0; y < map1[x].length; y++) {
map1[x][y] = 0;
map2[x][y] = 0;
}
}
ws.pMaps.push(map1, map2);
ws.stageInit = function(canvas) {
canvas.fill(100,100,100);
canvas.ctx.strokeStyle = "#000000";
canvas.ctx.lineWidth = 2;
canvas.ctx.moveTo(0,0);
for (y=0; y < ws.height; y+=10) {
canvas.ctx.beginPath();
canvas.ctx.moveTo(0,y);
canvas.ctx.lineTo(ws.width,y);
canvas.ctx.closePath();
canvas.ctx.stroke();
}
ws.sourceData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
ws.targetData = canvas.ctx.getImageData(0, 0, ws.width, ws.height);
}
ws.setWave = function(pnt) {
ws.pMaps[0][pnt.x-1][pnt.y-1] = ws.magFactor//*pnt.magnitude;
}
ws.resolveWaves = function(x,y) {
// Calculate the net result of the wave heights
ws.pMaps[1][x][y] = ((ws.pMaps[0][x-1][y]+ws.pMaps[0][x+1][y]+ws.pMaps[0][x][y-1]+ws.pMaps[0][x][y+1]) / 2)
-ws.pMaps[1][x][y];
ws.pMaps[1][x][y] -= (ws.pMaps[1][x][y]/ws.dampFactor);
}
ws.displace = function(x,y) {
var displace = Math.floor(ws.pMaps[1][x][y]*ws.dispFactor);
var xCorrect = x-1, yCorrect = y-1;
var targetIndex = (xCorrect + yCorrect * ws.width)*4;
if (displace == 0) {
ws.targetData.data[targetIndex] = ws.sourceData.data[targetIndex];
ws.targetData.data[targetIndex+1] = ws.sourceData.data[targetIndex+1];
ws.targetData.data[targetIndex+2] = ws.sourceData.data[targetIndex+2];
}
else {
if (displace < 0) {
displace += 1;
}
var sourceX = displace+xCorrect;
var sourceY = displace+yCorrect;
var sourceIndex = (sourceX + sourceY * ws.width)*4;
//var lum = ws.pMaps[1][x][y]*ws.lumFactor;
ws.targetData.data[targetIndex] = ws.sourceData.data[sourceIndex];//+lum;
ws.targetData.data[targetIndex+1] = ws.sourceData.data[sourceIndex+1];//+lum;
ws.targetData.data[targetIndex+2] = ws.sourceData.data[sourceIndex+2];//+lum;
}
}
ws.stageRefresh = function(moves, canvas) {
canvas.clear();
for (j=0; j < moves.length; j++) {
ws.setWave(moves[j]);
}
for (x=1; x <= ws.width; x++) {
if (ws.pMaps[1][x][0] != 0 || ws.pMaps[0][x][0] != 0) {
alert("TOP ROW ANOMALY");
}
for (y=1; y <= ws.height; y++) {
ws.resolveWaves(x,y);
ws.displace(x,y);
}
}
ws.pMaps.sort(function(a,b) { return 1 });
//ws.pMaps[0] = ws.pMaps[1];
//ws.pMaps[1] = temp;
canvas.ctx.putImageData(ws.targetData, 0, 0);
}
return ws;
}
canvas is the bitmapData that is given as the texture for the background (not an HTML5 canvas; sorry if that's confusing). ws.stageRefresh(moves,canvas) is called on every frame update.
Before I try to make the split-into-four-bitmaps solution work, does anyone have any guidance for other ways to improve the performance of this effect on Chrome?
I can't seem to figure out why my code doesn't work. What I'm essentially trying to do is generate a 10x10 tile based map using arrays.
The idea is to create an object called Box with an 'x' and 'y' axis property and also an image object within the box. Then, each position in the 2d array is populated with a box object.
I then want to draw all these arrays out on a canvas. Each tile(or array element) is a 64x64 box.
const ROW = 10;
const COLS = 11;
const SIZE = 64;
var canvas = document.getElementById("canvas");
var surface = canvas.getContext("2d");
//creating tile
function box() {
this.xaxis = 56;
this.yaxis = 0;
this.img = new Image();
this.img.src = "box_image.png";
}
//creating map
var map =[];
function setMap() {
for (var i = 0; i < ROW; i++) {
for (var o = 0; o < COLS; o++) {
map[i][o] = new box();
}
}
}
//rendering map
function render() {
for (var i = 0; i < map.length; i++) {
for (var x = 0; x < map.length; x++) {
var tile = map[i][x];
tile.xaxis *= i;
tile.yaxis *= x;
surface.drawImage(tile.img, tile.xaxis, tile.yaxis, 64, 64);
}
}
}
setTimeout(render, 10);
Adding a few elements you forgot, here's how I would do it.
Fiddle
HTML
<canvas id="canvas" width="1000" height="1000"></canvas>
<!-- set canvas size -->
JS
const ROW = 10;
const COLS = 11;
const SIZE = 64;
var canvas = document.getElementById("canvas");
var surface = canvas.getContext("2d");
//creating tile
function box() {
this.xaxis = 56;
this.yaxis = 0;
this.src = "https://cdn4.iconfinder.com/data/icons/video-game-adicts/1024/videogame_icons-01-128.png"; //save path to image
}
//creating map
var map =[];
function setMap() {
for (var i = 0; i < ROW; i++) {
var arr = []; //make new row
map.push(arr); //push new row
for (var o = 0; o < COLS; o++) {
map[i].push(new box()); //make and push new column element in current row
}
}
}
//rendering map
function render() {
for (var i = 0; i < ROW; i++) { //For each row
for (var x = 0; x < COLS; x++) { //And each column in it
var tile = map[i][x];
tile.xaxis *= i;
tile.yaxis += (x*SIZE); //increment y value
var img = new Image();
img.onload = (function(x,y) { //draw when image is loaded
return function() {
surface.drawImage(this, x, y, 64, 64);
}
})(tile.xaxis, tile.yaxis);
img.src = tile.src;
}
}
}
setMap(); //create the grid
render(); //render the grid
There are a number of errors in your code.
First you are loading the same image 110 times. Load it once and that will save a lot of memory and time.
You create a single dimetioned array map
map = [];
Then attempt to access to as a two dim map. map[i][o] that will not work. You need to create a new array for each row.
You create the function to populate the map setMap() but you never call the function.
The Boxes you create have the yaxis value set to 0. When you call render and multiply it by the column index the result will be zero, so you will only see one column of images. You need to set the yaxis value to some value (64)
Below is your code fixed up with some comments. I left the zero yaxis value as maybe that is what you wanted. The image is created only once and the onload event is used to call render When setMap is called I add a new array for each row. I call setMap at the bottom but can be called anytime after you declare and define var map = [];
const ROW = 10;
const COLS = 11;
const SIZE = 64;
const canvas = document.getElementById("canvas");
const surface = canvas.getContext("2d");
const image = new Image();
image.src = "box_image.png";
// onload will not fire until all the immediate code has finished running
image.onload = function(){render()}; // call render when the image has loaded
//creating tile
function Box() { // any function you call with new should start with a capital
this.xaxis = 56;
this.yaxis = 0; // should this not be something other than zero
this.img = image;
}
//creating map
const map =[];
function setMap() {
for (var i = 0; i < ROW; i++) {
var row = []; // new array for this row
map[i] = row;
for (var o = 0; o < COLS; o++) {
row[o] = new box();
}
}
}
//rendering map
function render() {
for (var i = 0; i < map.length; i++) {
for (var x = 0; x < map[i].length; x++) { // you had map.length you needed the array for row i which is map[i]
var tile = map[i][x];
tile.xaxis *= i;
tile.yaxis *= x; // Note you have zero for yaxis?? 0 times anything is zero
surface.drawImage(tile.img, tile.xaxis, tile.yaxis, 64, 64);
}
}
}
setMap(); // create the map
I'm drawing buttons on createjs canvas that have gradient fill and stroke. The number of buttons are drawn inside a for loop. Each section, as you will see in the fiddle, is drawn separately via function. but only the first function run draws the correct fill. The subsequent calls only draws the gradient stroke Jsfiddle
for (i = 0; i < db.length; i++) {
var btn = db[i];
var sdb = btn.split("_");
var blabel = sdb[0];
var battrib = sdb[1];
var bval = sdb[2];
var sid = sdb[3];
var tick = sdb[4];
var cptn = sdb[5];
var imageType = sdb[6];
var buttonSize = 90 + 10;
var bttn = new c.Shape();
bttn.graphics.beginLinearGradientFill([grad1, grad2], [.2, 1], 0, 0,0,50 ).setStrokeStyle(3).beginLinearGradientStroke([grad2, grad1], [.2, 1], 0, 0,0,50 ).drawRoundRect(x, y, 85, 35,5);
var label = new c.Text(blabel);
label.font = font;
label.color = '#000';
label.x = x+8;
label.y = y+6;
m1.addChild(bttn, label);
x+= buttonSize;
}s.update();
It seems to be working to me. Is it perhaps that you forgot to offset your buttons, so you're only seeing the first one? bttn.y = i*40
https://jsfiddle.net/gskinner/wqu4nzdq/12/