how to calculate the area of painted region? - javascript

I paint freehand strokes on my canvas with a code like below. I need to check how much of the canvas is covered with strokes. What is a good way to check that? The only thing I can think of is to count the number of pixels that have the specific color on mouse up event. But it is lame because it is slow...
Any help?
$(document).ready(function(){
var draw = false;
var x_prev = null, y_prev = null;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.mousedown(function(e){
draw = true;
x_prev = e.pageX - this.offsetLeft;
y_prev = e.pageY - this.offsetTop;
});
window.mouseup(function(){draw=false});
canvas.mousemove(function(e){
if(draw){
context.beginPath();
context.moveTo(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
context.lineTo(x_prev, y_prev);
context.stroke();
context.closePath();
x_prev = e.pageX - this.offsetLeft;
y_prev = e.pageY - this.offsetTop;
}
});

Computers are fast. It seems plenty fast to me to re-count the number of pixels over a particular alpha each frame when drawing. Test it yourself here: http://jsfiddle.net/ZC8cB/3/
Relevant code:
var w = canvas.attr('width'),
h = canvas.attr('height'),
area = w * h;
function updateArea() {
var data = context.getImageData(0, 0, w, h).data;
for (var ct=0, i=3, len=data.length; i<len; i+=4) if (data[i]>50) ct++;
$fill.html(ct);
$pct.html((100 * ct / area).toFixed(2));
}
If this is really too slow, you could choose to update the area every other mousemove, every third mousemove, etc. or on an interval timer. For example, here's a very-slightly-modified version that only updates every tenth mousemove: http://jsfiddle.net/ZC8cB/4/
And if a single frame of counting is too slow—because you have a slow computer or huge canvas or both—then you can fetch the ImageData in one frame and each update frame count a particular portion of the pixels.

Quantify the area to line width sized squares and count the number of unique squares encountered during draw.
var thickness = 4
var height = ..
var width = ..
var drawn = []
var covered = 0;
canvas.mousemove(function(e) {
var x = e.pageX - this.ofsetLeft;
var y = e.pageY - this.offsetTop;
x = parseInt( x / width ) * ( width / thickness )
y = parseInt( y / height ) * ( height / thickness )
id = x + y * parseInt(thickness / width)
if ( !drawn[ id ] ) {
drawn[ id ] = 1;
covered++;
}
}
You can get drawn area in percents by dividing the covered squares by number of total squares
var a = covered / ((width / thickness) * (height / thickness))

Related

How to make my drawing of an exponential function to grow within the canvas?

I have an exponential curve made using p5.js that draws itself over time as follow :
However I am trying to have it responsive in such a way that as the curve grows, it would always be fully visible inside the canvas.
Here are screenshots of what I mean to achieve :
Working examples found on a website
As you can see on this example, once it reaches the edges of the canvas, it kind of "zooms out" in order for the canvas to fit the whole curve, as a result since it never leaves the canvas the curve bends the more it grows.
To try and achieve this, I explored using scale scale(x / 100, y / 100) to be triggered once the limits of the canvas are reached. That way the canvas starts scaling proportionally to the curve's expansion.
However, using this method does not solve my problem because it seems that reducing the scaling while adding points to the curve does not make the curve grow anymore.
Here is my current (updated) code :
var y = 49;
var x = 0;
var inc = 0.02;
var translateY;
let createTopY = false;
let createTopX = false;
var topY;
var percentageIncY = 100;
var perecntageIncX = 100;
// Scaling
var scaleX = 1
var scaleY = 1
function setup() {
createCanvas(400, 200);
background(20, 25, 29)
}
function draw() {
frameRate(20)
// Set curve history for continuous lines
let prev_x = x
let prev_y = y
// Recreate canvas position (origin) based on Scale Y (zoom)
translateY = height * scaleY
translate(0, (height - translateY) + 49 ) // 49 is the initial y value
scale(scaleX, scaleY)
// Exponential curve values
x = x + 5 // Approximate
y = y + y * inc
// Draw curve
strokeWeight(3)
stroke(229, 34, 71);
line(prev_x, height - prev_y, x, height - y);
// Create topY when top at scale(1) is reached
if (createTopY !== true) checkInt(y)
if (createTopX !== true) checkInt(x)
//-------------- SCALING ---------------//
// From TopX, decrease scale exponentialy
if (x > width - 20) { // Temporarily set to 20 (50-30) to better visualize
// The increased value of x in % starting at topX
percentageIncX = (x * 100) / (width - 20)
// Decrease scaleX exponentialy
scaleX = 100 / percentageIncX
print(
"% increase X: " +
percentageIncX
)
}
// From topY, decrease scale exponentialy
if (y > height + 20) { // Temporarily set to 20 (50-30) to visualize
// The increased value of y in % starting at topY
percentageIncY = (y * 100) / (height + 20) // Temporarily set to 20 (50-30) to better visualize
// Decrease scaleY exponentialy
scaleY = 100 / percentageIncY
print(
"% increase Y: " +
percentageIncY
)
}
//-------------------------------------//
}
const checkInt = (prop) => {
const parsed = int(prop)
if (parsed > height + 20) { // Temporarily set to 20 (50-30) to better visualize
createTopY = true
createTopX = true
topY = y
print('createTopY is: ' + createTopY)
print('createTopX is: ' + createTopX)
print("---START SCALING---")
print('starting at ' + y)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
Use frameRate() to control to the number of frames to be displayed every second. Stitch the curve with line segments (line()) instead of drawing with single dots (ellipse()) to draw a curve without interruptions.
var y = 1;
var x = 0;
var inc = 0.01;
function setup() {
createCanvas(400, 400);
background(100)
frameRate(100)
}
function draw() {
let prev_x = x;
let prev_y = y;
x = x + 0.5
y = y + y * inc;
noFill();
stroke(255, 0, 0, 255);
strokeWeight(3);
line(prev_x, height-prev_y, x, height-y);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Highlight a square on a checker board when mouse is over

ive drawn a checker board on a canvas and i want to highlight the square which the mouse is over. I have given it a go but the furthest i can get is with it half a square out of sync.
Here is my code:
canvas.addEventListener('mousemove', function(evt)
{
const position = getGridPoint(evt);
drawBoard(); //Clears the last highlight
context.lineWidth='3'; //Draws the new highlight
context.strokeStyle = 'yellow';
context.rect(position.x * board.squareW, position.y * board.squareH, board.squareW, board.squareH);
context.stroke();
})
function getGridPoint(evt)
{
const rect = canvas.getBoundingClientRect();
//board.w = width of the board
//board.squareW = width of each tile on the board
const x = Math.round((evt.clientX - rect.left) / (rect.right - 2 - rect.left) * board.w);
const y = Math.round((evt.clientY - rect.top) / (rect.bottom - 2 - rect.top) * board.h);
const roundX = Math.round(x / board.squareW);
const roundY = Math.round(y / board.squareH);
return {
x: roundX,
y: roundY
};
}
Its something in the 2nd function where im using math.round
So it will work if i manually subtract half a tile's width from x and y but it seems a hacky way and id rather do it properly in the first place
JSfiddle: http://jsfiddle.net/5toudex0/3/
try this for getTile
function getTile(evt)
{
const rect = canvas.getBoundingClientRect();
//board.w = width of the board
//board.squareW = width of each tile on the board
const x = Math.floor((evt.clientX - rect.left) / board.squareW);
const y = Math.floor((evt.clientY - rect.top) / board.squareH);
return {
x: x,
y: y
};
}
With a little change made to the mousemove handler for the canvas, you can (0) directly get the client position of the mouse and then (1) compute the col/row indices for the tile to highlight.
Consider the following change to your code:
canvas.addEventListener('mousemove', function(evt)
{
const position = getTile(evt);
var xPos = evt.clientX, yPos = evt.clientY;
var xTileIndex = (xPos / board.squareW)>>0;
var yTileIndex = (yPos / board.squareH)>>0;
console.log(`x,y = ${xPos},${yPos}`);
console.log(`x,y = ${xTileIndex},${yTileIndex}`);

Calculating evenly spaced points on the perimeter of a circle

The math behind this question has been asked numerous times, so that's not specifically what I'm after. Rather, I'm trying to program the equation for determining these points into a loop in JavaScript, so that I can display points the evenly around the circle.
So with the equations for the X and Y positions of the points:
pointX = r * cos(theta) + centerX
pointY = r * sin(theta) + centerY
I should be able to calculate it with this:
var centerX = 300;
var centerY = 175;
var radius = 100;
var numberOfPoints = 8;
var theta = 360/numberOfPoints;
for ( var i = 1; i <= numberOfPoints; i++ ) {
pointX = ( radius * Math.cos(theta * i) + centerX );
pointY = ( radius * Math.sin(theta * i) + centerY );
// Draw point ( pointX , pointY )
}
And it should give me the x,y coordinates along the perimeter for 8 points, spread 45° from each other. But this doesn't work, and I'm not understanding why.
This is the output that I get (using the HTML5 Canvas element). The points should reside on the innermost red circle, as that one has a
Incorrect:
When it "should" look like this (although this is with just 1 point, placed manually):
Correct:
Could someone help me out? It's been years since I took trig, but even with looking at other examples (from various languages), I don't see why this isn't working.
Update: Figured it out!
I didn't need to add the centerX and centerY to each calculation, because in my code, those points were already relative to the center of the circle. So, while the canvas center was at point (300, 175), all points were relative to the circle that I created (the stroke line that they need to be placed on), and so the center for them was at (0, 0). I removed this from the code, and split the theta and angle calculations into two variables for better readability, and voila!
totalPoints = 8;
for (var i = 1; i <= totalPoints ; i++) {
drawPoint(100, i, totalPoints);
}
function drawPoint(r, currentPoint, totalPoints) {
var theta = ((Math.PI*2) / totalPoints);
var angle = (theta * currentPoint);
electron.pivot.x = (r * Math.cos(angle));
electron.pivot.y = (r * Math.sin(angle));
return electron;
}
Correct:
cos and sin in Javascript accept an argument in radians, not degrees. You can change your theta calculation to
var theta = (Math.PI*2)/numberOfPoints;
See the Math.cos documentation for details
#Emmett J. Butler's solution should work. The following is a complete working example
// canvas and mousedown related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// save canvas size to vars b/ they're used often
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var centerX = 150;
var centerY = 175;
var radius = 100;
var numberOfPoints = 8;
var theta = 2.0*Math.PI/numberOfPoints;
ctx.beginPath();
for ( var i = 1; i <= numberOfPoints; i++ ) {
pointX = ( radius * Math.cos(theta * i) + centerX );
pointY = ( radius * Math.sin(theta * i) + centerY );
ctx.fillStyle = "Red";
ctx.fillRect(pointX-5,pointY-5,10,10);
ctx.fillStyle = "Green";
}
ctx.stroke();

How can canvas coordinates be converted into tile coordinates?

So I have a canvas with an isometric tile map drawn on it, which looks perfect.
In the event listener at the bottom of the script, I grab the cursor's coordinates inside the canvas. How could I find out which tile the cursor is hovering over?
var cs = document.getElementById('board');
var c = cs.getContext("2d")
var gridWidth=100
var gridHeight=50
var tilesX = 12, tilesY = 12;
var spriteWidth=gridWidth
var spriteHeight=img.height/img.width*gridWidth
cs.width = window.innerWidth //spriteWidth*10
cs.height = window.innerHeight //spriteHeight*10
var ox = cs.width/2-spriteWidth/2
var oy = (tilesY * gridHeight) / 2
window.onresize=function(){
cs.width = window.innerWidth //spriteWidth*10
cs.height = window.innerHeight //spriteHeight*10
ox = cs.width/2-spriteWidth/2
oy = (tilesY * gridHeight) / 2
draw()
}
draw();
function renderImage(x, y) {
c.drawImage(img, ox + (x - y) * spriteWidth/2, oy + (y + x) * gridHeight/2-(spriteHeight-gridHeight),spriteWidth,spriteHeight)
}
function draw(){
for(var x = 0; x < tilesX; x++) {
for(var y = 0; y < tilesY; y++) {
renderImage(x,y)
}
}
}
cs.addEventListener('mousemove', function(evt) {
var x = evt.clientX,
y = evt.clientY;
console.log('Mouse position: ' + x + ',' + y);
}, false);
Sorry for pasting such lengthy code, but all of it is there just to lay the isometric grid.
EDIT: Also, how could I get the top left coordinates of the tile image to relay it?
Assuming you've arranged your tiles where the leftmost column and topmost row are zero:
var column = parseInt(mouseX / tileWidth);
var row = parseInt(mouseY / tileHeight);
BTW, if you eventually move your canvas off the top-left of the page then you must adjust your mouse coordinates by the canvas offset.
Here's an example of how to calculate mouse position:
// references to the canvas element and its context
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// get the offset position of the canvas on the web page
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
// listen for mousedown events
canvas.onmousedown=handleMousedown;
function handleMousedown(e){
// tell the browser we will handle this event
e.preventDefault();
e.stopPropagation();
// calculate the mouse position
var mouseX=e.clientX-offsetX;
var mouseY=e.clientY-offsetY;
}

Faking a 3d sphere in canvas

I have a question about Faking 3d in HTML5/Canvas/Javascript...
Basically, I have a 2d image drawn on a <canvas> using drawImage().
What I would like to do, is draw the image, then displace the texture on the sphere to look... sphere-like...
see image below for clarity:
Any ideas?
I have googled myself to near-death over this, also it cannot be WebGL as it has to work on Mobiles... is there a way to do this?
You can check a working prototype here: http://jsbin.com/ipaliq/edit
I bet there's ton's of room for optimization and better/faster algorithms, but I hope this proof of concepts will point you in the right direction.
The basic algorithm goes through each pixel of the original image and calculates its new position if it was subject to an spherical deformation.
Here's the result:
Code:
var w = 150, h = 150;
var canvas=document.getElementById("myCanvas");
var ctx=canvas.getContext("2d");
ctx.fillStyle="red";
ctx.fillRect(0,0,w,h);
//-- Init Canvas
var initCanvas = function()
{
var imgData=ctx.getImageData(0,0,w,h);
for (i=0; i<imgData.width*imgData.height*4;i+=4)
{
imgData.data[i]=i%150*1.5;
imgData.data[i+1]=i%150*1.5;
imgData.data[i+2]=(i/imgData.width)%150*1.5;
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,0,0);
};
initCanvas();
var doSpherize = function()
{
var refractionIndex = 0.5; // [0..1]
//refraction index of the sphere
var radius = 75;
var radius2 = radius * radius;
var centerX = 75;
var centerY = 75;
//center of the sphere
var origX = 0;
var origY = 0;
for (x=0; x<w;x+=1)
for (y=0; y<h;y+=1)
{
var distX = x - centerX;
var distY = y - centerY;
var r2 = distX * distX + distY * distY;
origX = x;
origY = y;
if ( r2 > 0.0 && r2 < radius2 )
{
// distance
var z2 = radius2 - r2;
var z = Math.sqrt(z2);
// refraction
var xa = Math.asin( distX / Math.sqrt( distX * distX + z2 ) );
var xb = xa - xa * refractionIndex;
var ya = Math.asin( distY / Math.sqrt( distY * distY + z2 ) );
var yb = ya - ya * refractionIndex;
// displacement
origX = origX - z * Math.tan( xb );
origY = origY - z * Math.tan( yb );
}
// read
var imgData=ctx.getImageData(origX,origY,1,1);
// write
ctx.putImageData(imgData,x+w,y+h);
}
};
doSpherize();
Note
I would advice agains such an intense operation to be handed by the frontend without webGL pixel shaders. Those are very optimized and will be able to take advantage of GPU accelaration.
You can definitely do something like this. I'm not sure if the quality and speed will be good enough though, particularly on mobile.
You're going to want to getImageData the pixels, perform some mathematical transformation on them I can't think of at the moment, and then putImageData them back.
It's probably going to look kind of blurry or pixelated (you could try interpolating but that will only get you so far). And you may have to optimize your javascript considerably to get it done in a reasonable time on a mobile device.
The transformation is probably something like an inverse azimuthal projection.

Categories