Check if point is inside of shape drawn by a complex Path2D - javascript

I want to check if a given point is inside of the space drawn inside of a irregular path - inside of the brain (the path is below).
I don't seem to be able to use CanvasRenderingContext2D.isPointInPath(path, x, y) because it only returns true if the point is literally inside the path (the white outline).
I also don't think I can use the odd polygon rule, given that it is possible for a point not to be in the edge and its line still hit the shape wall an even number of times...

As you're working with a SVG, here's a workaround which doesn't involve any abstract calculations.
(1)
Make the inside of your shape, thus the area you want to detect a color completely different from the rest of the shape or any other visuals. In your case for example it would be nothing (or black) to red.
This is controlled by the svg's fill attribute, which takes a hex #ff0000 or a rgb value rgb(255,0,0). Well for reasons that will be important later we make it a rgba(255,0,0,1) value though it ignores the alpha value.
As we don't want the fill to be visible, we also need to set the fill-opacity value to 0.005. This is the lowest value possible and equals the CanvasRenderingContext2D value of 1 in a range from 0-255.
(2)
Now we need to turn the svg into an Image object that can be drawn onto a canvas. This can be done using the following lines:
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let img = new Image();
img.onload = function(e) {
ctx.drawImage(e.currentTarget, canvas.width / 2 - 50, canvas.height / 2 - 50, 100, 100);
}
img.src = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svg);
svg is just a string representation of your svg's data.
(3)
The final step involves getting the pixel color at a specific position on the canvas. For this we utilize the .getImageData(x, y, width, height) method, which returns an object consisting of a Uint8ClampedArray which holds four values per pixel. If we set both width and height to 1 we get exactly the four color components for a single pixel - red, green, blue and alpha.
Now we simply compare the color with the red we've used in step (1) and if it's equal, we know it's inside the shape.
Here's a working example (hover your mouse over the image):
let hitColor = 'rgba(255,0,0,1)';
let svg = `<?xml version="1.0" encoding="iso-8859-1"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 511.989 511.989" style="enable-background:new 0 0 511.989 511.989;" xml:space="preserve">
<path style="fill:${hitColor};fill-opacity:0.005" d="M489.333,255.088l-21.344-17.625l8-34.671l4-18.812l-30.656-42.141l6.656-22.53l-8-38.483
l-30.655-30.077h-39.999c0,0-22.655,12.312-16,0c6.672-12.328-33.326-36.577-33.326-36.577h-17.328l-40.663,15.483l-9.602,7.828
l-31.062-23.702h-30.672l-23.999,0.391l-20.929,23.312L150.41,50.75h-22.383l-23.998,10.654L66.694,73.295l-6.664,29.358
l-5.336,42.577l-19.999,22.891v26.516l12.397,37.623l-12.397,16.547l-18.664,22.672v41.326l18.664,29.984v36.67l23.999,55.999
c0,0,35.999,18.672,41.335,21.327c5.328,2.672,14.664,0,14.664,0l29.327,32l27.999,9.328h21.336l42.663-18.656l24.397-18.672
l10.664,24l42.257,13.328h21.344l31.998-6.656l21.719-38.671l11.609,3.999l38.67-7.999l28-28.327l10.656-46.327v-31.343
l17.718-20.156l10.281-35.171L489.333,255.088z"/>
<path style="fill:#ffffff;" d="M511.973,294.009c0-24.546-12.891-46.093-32.28-58.218c7.984-11.203,12.672-24.905,12.672-39.701
c0-22.203-10.562-41.938-26.938-54.453c2.625-7.655,4.078-15.858,4.078-24.42c0-41.281-33.484-74.749-74.764-74.749
c-8.344,0-16.375,1.375-23.875,3.906C363.21,19.594,338.554,0,309.321,0c-22.266,0-41.858,11.359-53.327,28.608
C244.525,11.359,224.925,0,202.667,0c-29.233,0-53.889,19.594-61.537,46.374c-7.5-2.531-15.531-3.906-23.89-3.906
c-41.288,0-74.757,33.468-74.757,74.749c0,8.562,1.445,16.765,4.086,24.42c-16.382,12.516-26.952,32.25-26.952,54.453
c0,14.796,4.695,28.498,12.672,39.701c-19.383,12.125-32.273,33.672-32.273,58.218c0,21.327,9.727,40.374,24.984,52.952
c-2.375,8.375-3.656,17.202-3.656,26.343c0,52.53,42.194,95.185,94.537,95.966c13.405,25.406,40.069,42.719,70.787,42.719
c29.632,0,55.506-16.125,69.326-40.062c13.82,23.938,39.687,40.062,69.327,40.062c30.718,0,57.389-17.312,70.795-42.719
c52.342-0.781,94.529-43.436,94.529-95.966c0-9.141-1.281-17.968-3.656-26.343C502.238,334.383,511.973,315.336,511.973,294.009z
M186.668,490.644c-32.351,0-58.663-26.312-58.663-58.654c0-16.406,7.195-31.688,18.078-42.344c2.008-1.938,3.25-4.641,3.25-7.656
c0-5.891-4.773-10.655-10.664-10.655c-2.906,0-5.547,1.156-7.469,3.047c-15.569,14.593-24.53,34.577-24.53,57.608
c0,5.265,0.516,10.421,1.492,15.405c-16.469-2-31.702-9.391-43.616-21.312c-14.102-14.094-21.867-32.844-21.867-52.78
c0-19.952,7.766-38.702,21.867-52.796c14.102-14.109,32.851-21.875,52.796-21.875c6.351,0,12.585,0.781,18.585,2.312
c0.883,0.234,1.797,0.375,2.742,0.375c5.891,0,10.664-4.78,10.664-10.671c0-5.203-3.727-9.531-8.656-10.469
c-7.477-1.875-15.289-2.875-23.335-2.875c-35.805,0-67.03,19.608-83.529,48.655c-7.734-8.422-12.469-19.641-12.469-31.952
c0-16.406,8.32-31.405,22.265-40.14l9.789-6.125c2.898-1.906,4.812-5.188,4.812-8.922c0-2.266-0.719-4.391-1.938-6.109l-6.609-9.297
c-5.695-7.999-8.711-17.452-8.711-27.326c0-13.922,6.07-26.453,15.695-35.094c13.578,18.766,35.655,30.984,60.593,30.984
c5.89,0,10.765-4.781,10.765-10.672s-4.844-10.656-10.733-10.656c-29.453,0-53.452-23.969-53.452-53.436
c0-29.453,23.968-53.422,53.421-53.422c7.679,0,14.976,1.641,21.585,4.562c2.242,33.312,29.968,59.624,63.842,59.624
c5.891,0,10.664-4.766,10.664-10.671c0-5.891-4.773-10.657-10.664-10.657c-23.522,0-42.663-19.14-42.663-42.671
c0-23.515,19.133-42.655,42.663-42.655c23.523,0,42.663,19.141,42.663,42.655v106.67h-0.016
c-0.156,5.734-4.867,10.359-10.655,10.359c-5.891,0-10.664,4.781-10.664,10.672s4.773,10.671,10.664,10.671
c3.741,0,7.335-0.656,10.671-1.828v87.451c0,17.64-14.358,31.983-31.999,31.983c-5.891,0-10.664,4.781-10.664,10.672
s4.773,10.672,10.664,10.672c12.008,0,23.086-3.969,31.999-10.672v101.357C245.33,464.332,219.011,490.644,186.668,490.644z
M478.177,325.961c-16.5-29.047-47.718-48.655-83.529-48.655c-8.047,0-15.859,1-23.344,2.875c-4.922,0.938-8.656,5.266-8.656,10.469
c0,5.891,4.781,10.671,10.672,10.671c0.953,0,1.859-0.141,2.734-0.375c6-1.531,12.25-2.312,18.594-2.312
c19.938,0,38.687,7.766,52.795,21.875c14.109,14.094,21.859,32.844,21.859,52.796c0,19.937-7.75,38.687-21.859,52.78
c-11.922,11.921-27.14,19.312-43.607,21.312c0.969-4.984,1.484-10.141,1.484-15.405c0-23.031-8.953-43.016-24.531-57.608
c-1.922-1.891-4.562-3.047-7.469-3.047c-5.891,0-10.672,4.765-10.672,10.655c0,3.016,1.25,5.719,3.25,7.656
c10.891,10.656,18.094,25.938,18.094,42.344c0,32.342-26.312,58.654-58.67,58.654c-32.344,0-58.663-26.312-58.663-58.654V330.633
c8.914,6.703,19.991,10.672,31.991,10.672c5.906,0,10.672-4.781,10.672-10.672s-4.766-10.672-10.672-10.672
c-17.625,0-31.991-14.344-31.991-31.983v-87.451c3.336,1.172,6.93,1.828,10.663,1.828c5.891,0,10.672-4.78,10.672-10.671
c0-5.891-4.781-10.672-10.672-10.672c-5.78,0-10.491-4.625-10.647-10.359h-0.016V63.982c0-23.515,19.147-42.655,42.663-42.655
s42.671,19.141,42.671,42.655c0,23.531-19.155,42.671-42.671,42.671c-5.891,0-10.672,4.767-10.672,10.657
c0,5.905,4.781,10.671,10.672,10.671c33.874,0,61.607-26.312,63.842-59.624c6.609-2.922,13.906-4.562,21.578-4.562
c29.468,0,53.436,23.969,53.436,53.422c0,29.467-23.999,53.436-53.467,53.436c-5.891,0-10.719,4.766-10.719,10.656
c0,5.89,4.875,10.672,10.75,10.672c24.937,0,47.029-12.219,60.592-30.984c9.625,8.641,15.703,21.172,15.703,35.094
c0,9.874-3.016,19.327-8.719,27.326l-6.609,9.297c-1.219,1.719-1.938,3.844-1.938,6.109c0,3.734,1.922,7.016,4.812,8.922
l9.797,6.125c13.938,8.734,22.266,23.733,22.266,40.14C490.645,306.32,485.911,317.539,478.177,325.961z"/>
</svg>
`;
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let rect = canvas.getBoundingClientRect();
let img = new Image();
img.onload = function(e) {
ctx.drawImage(e.target, canvas.width / 2 - 50, canvas.height / 2 - 50, 100, 100);
canvas.addEventListener('mousemove', (e) => {
let data = ctx.getImageData(e.clientX - rect.left, e.clientY - rect.top, 1, 1).data;
let hit = hitColor == `rgba(${data[0]},${data[1]},${data[2]},${data[3]})`;
document.getElementById('message').innerText = `inside path: ${hit}`;
});
}
img.src = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svg);
<canvas id="canvas" style="background:black;"></canvas><br>
<span id="message">inside path:</span>

Related

javascript loaded image is blurry (PNG)

I'm having a problem drawing sprites on canvas for a school project. My code:
treeImage = new Image();
treeImage.src = "sprites/treeSprites.png";
function rocks() { //to create the rock
this.x = 1920 * Math.random(); //random location on the width of the field
this.y = ground[Math.round(this.x/3)]; //ground is an array that stores the height of the ground
this.draw = function() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(Math.tan((ground[Math.floor(this.x/3)]-ground[Math.floor(this.x/3)+1])/-3));
//^rotating based on its position on the ground^
ctx.drawImage(treeImage, 200, 50, 50, 50, -25, -50, 50, 50);
ctx.restore();
}
}
len = rockArray.length; //every frame
for (var i = 0;i<len;i++) {
rockArray[i].draw();
}
I only request 50×50px from the image. Exactly outside of the 50×50 there are black lines (which shouldn't interfere because I only request the square within the black lines) but when I draw the rock, the black outlines are visible. (For other reasons, I can't remove the black lines.)
I'm guessing the image JavaScript stores when I load the image is made blurry, and then when I request that part from the image, the lines around are visible too, as the blur "spreads" the lines into the square I request.
Is there a way I can prevent this?
Use ctx.imageSmoothingEnabled = false.
This will make the image sharp instead of smoothed (blurry).
(documentation)
If you draw a vertical line at x=5 and width = 1, the canvas actually draws the line from 4.5 to 5.5 this results in aliasing and a fuzzy line. A quick way to remedy that so it is a solid line is to offset the entire canvas by half a pixel before doing anthing else.
ctx.translate(-0.5, -0.5);
(documentation)

Animation: Making an image gradiently appear from side to side (in opacity)(so called "soft-wipe" effect)

Sorry if the title is confusing. I've tried my best to compose it, if you are going to understand what I am asking, feel free to suggest better title in comment.
The animation I am trying to make can easily be done by video editors, but looks to me not so easy with CSS/JS: First of all, I am not talking about sliding in the image, the image is not moving at all. I want it to appear from side to side, but in a gradient transparent manner. Imagine there is a gradient opacity mask on the image, making the opacity of its left end to be 1, and that of its right end to be 0. When this mask moves from left right, it is the animation I wanna achieve.
I can split the image to several pieces, and manipulate the opacity of each one, there has to be a certain amount pieces to make the whole animation smooth and appeal.
The other way I am thinking is to use canvas, where you can manipulate the image by pixel, as this page suggests, I can do
// get the image data object
var image = ctx.getImageData(0, 0, 500, 200);
// get the image data values
var imageData = image.data,
length = imageData.length;
// set every fourth value to 50
for(var i=3; i < length; i+=4){
imageData[i] = 50;
}
// after the manipulation, reset the data
image.data = imageData;
// and put the imagedata back to the canvas
ctx.putImageData(image, 0, 0);
However the page is only taking about a static image but not animation. I am wondering whether it would undermine performance if I use this approach. Also, this approach involve a lot of ugly calculations.
I think what I want to achieve is not very strange, so is there any javascript plugin to achieve this?
The animation I am trying to make can easily be done by video editors,
[...] the image is not moving at all. I want it to appear from side to side
In the video industry we call this a soft-wipe (AKA soft edged wipe) and it's not too complicated to make.
All you need is an alpha mask that you can make using a linear gradient. Then use translate properties with the context combined with xor composite mode to animate it.
What xor mode does is to invert the alpha channel based on the alpha channel drawn to it. The advantage of this is that the canvas element get transparent as well, so any background can show through. You can keep the default comp. mode as well which will make the background black instead.
The gradient is made like this (the color values does not matter with xor mode, just the alpha channel values):
var g = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);
g.addColorStop(0, "rgba(0,0,0,0)");
g.addColorStop(1, "rgba(0,0,0,1)");
ctx.fillStyle = g;
(see this answer for how to avoid "bright line" artifacts by creating a smoothed gradient).
Now create a function that draws a complete frame based on position t which is a normalized value combined with canvas width - have in mind we need the double width to work with: gradient + room for the gradient to exit -
function render(t) {
var w = t * ctx.canvas.width; // width based on t
ctx.drawImage(img, 0, 0); // render bg. image
ctx.translate(-ctx.canvas.width + w, 0); // translate on x-axis
ctx.fillRect(0, 0, ctx.canvas.width * 2, ctx.canvas.height); // render gradient mask
}
Call this in an animation loop until t=2, but optionally set globalCompositeOperation to xor and we're good to go. The animation loop itself will reset transformation for us:
Demo
var ctx = c.getContext("2d"),
img = new Image,
t = 0, step = 0.02
// alpha mask
var g = ctx.createLinearGradient(0, 0, ctx.canvas.width, 0);
g.addColorStop(0, "rgba(0,0,0,0)");
g.addColorStop(1, "rgba(0,0,0,1)");
ctx.fillStyle = g;
ctx.globalCompositeOperation = "xor";
// load bg image
img.onload = animate;
img.src = "http://i.imgur.com/d0tZU7n.png";
function animate() {
ctx.setTransform(1,0,0,1,0,0); // reset any transformations
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
render(t);
t += step;
if (t <= 2) requestAnimationFrame(animate); // 2 since we need double width
else {t=0; setTimeout(animate, 2000)}; // just to repeat anim. for demo
}
function render(t) {
var w = t * ctx.canvas.width; // width based on t
ctx.drawImage(img, 0, 0);
ctx.translate(-ctx.canvas.width + w, 0); // translate on x-axis
ctx.fillRect(0, 0, ctx.canvas.width*2, ctx.canvas.height);
}
body {background:url(http://i.imgur.com/OT99vSA.jpg) repeat}
<canvas width=658 height=325 id=c></canvas>
Use the context composite property to do the masking via a gradient. Create an offscreen canvas the same size as the image or the size of the display canvas whichever is smallest.
For every frame create the gradient with the appropriate colour stops (CSS color format rgba(red, green, blue, alpha)) to set the alpha values.
Clear the off screen canvas
ctxOffScreen.clearRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
Then set the composite value for the of screen canvas to
ctxOffScreen.globalCompositeOperation = "source-over";
Render the image onto it
ctxOffScreen.drawImage(image, 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
Then set the comp to
ctxOffScreen.globalCompositeOperation = "destination-in";
This will match the pixels already drawn with the same alpha value that is in what every you draw next (the gradient mask)
Then set the fill style to the gradient you created and draw a rectangle over the top
ctxOffScreen.fillStyle = gradient;
ctxOffScreen.fillRect( 0, 0, ctxOffScreen.canvas.width, ctxOffScreen.canvas.height);
Then just render the offscreen canvas to the onscreen canvas
ctx.drawImage(ctxOffScreen.canvas, 0, 0);
If you use
ctxOffScreen.globalCompositeOperation = "destination-out";
instead of "destination-in" you will invert the mask you created with the gradient.
As an alternative to canvas, you can use svg. CSS gradients are not animateable, however other properties of svg are, so you can find some creative ways to animate gradients after all.
#Mask rect {
x: 400px;
transition: 1s;
}
svg:hover #Mask rect {
x: -400px;
}
svg {
border: 2px solid black;
background-color: #ee3377;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400">
<defs>
<linearGradient id="Gradient">
<stop offset="0" stop-color="white" stop-opacity="0" />
<stop offset=".5" stop-color="white" stop-opacity="1" />
</linearGradient>
<mask id="Mask">
<rect width="800" height="400" fill="url(#Gradient)" />
</mask>
</defs>
<image xlink:href="http://i.imgur.com/g3D5jNz.jpg" width="400" height="400" mask="url(#Mask)"></image>
</svg>
You could probably animate the offset property directly, I haven't tested yet.

Jquery: convert SVG to PNG with new size

I´m using the following function to transform an SVG into a PNG and offer it as a download:
<svg id="chart">
...some contenthere
</svg>
function() {
var svg = $("#chart")[0],
bBox = $('#chart')[0].getBBox();
var width = bBox.width*2,
height = bBox.height*2;
var canvas = $("canvas")[0],
serializer = new XMLSerializer(),
svgString = serializer.serializeToString(svg);
canvas.width = width;
canvas.height = height;
canvg(canvas, svgString);
var dataURL = canvas.toDataURL('image/png'),
data = atob(dataURL.substring('data:image/png;base64,'.length));
asArray = new Uint8Array(data.length);
for (var i = 0; i < data.length; ++i) {
asArray[i] = data.charCodeAt(i);
}
var blob = new Blob([asArray.buffer], {type: 'image/png'});
saveAs(blob, 'climatechart.png');
}
It actually works fine, despite the fact that the output image is the same size as the SVG in the browser. How can i set a new size for the image? I also tried to get the size directly from the svg not the BBox, but it was the same.
This solution is based on a remote formatting server and not local code. It uses XSL FO server-side to render SVG to bitmap directly, allowing you to set the resolution for high quality output.
Documentation: http://www.cloudformatter.com/CSS2Pdf.APIDoc.Usage
Example: http://jsfiddle.net/g75t4oyq/
Code Implementation for JPG format # 300dpi from SVG in div:
click="return xepOnline.Formatter.Format('JSFiddle', {render:'newwin', mimeType:'image/jpg', resolution:'300', srctype:'svg'})";
jQuery('#buttons').append('<button onclick="'+ click +'">JPG #300dpi</button>');
You can easily resize a canvas as you can draw it as a canvas. The following example ignores any of the SVG rendering, which is working fine according to your question, but it does show how you can easily resize a canvas using another canvas:
var myCanvas = document.createElement('canvas');
var myCtx = myCanvas.getContext('2d');
myCanvas.width = 200;
myCanvas.height = 200;
myCtx.arc(100, 100, 100, 0, Math.PI * 2, true);
myCtx.fill();
var seCanvas = document.createElement('canvas');
var seCtx = seCanvas.getContext('2d');
seCanvas.width = 200;
seCanvas.height = 200;
seCtx.drawImage(myCanvas, 0, 0, 100, 100);
document.body.appendChild(myCanvas);
document.body.appendChild(seCanvas);
After that, you can simply continue the process using this new canvas.
SVG documents can be resized via height and width attributes if the svg element defines a viewBox attribute depending on its size; e.g.
<svg width="500" height="200" viewBox="0 0 50 20" >
Once the viewBox has been set, the scaling of svg in canvas works as you coded.
Ok, thanks for the suggestions, i finally found a solution that perfectly fits (according to Cédric Hartlands hint). The viewbox attribute was the part that was completely missing in my code. The viewbox is defined as the following:
viewbox = "x y width height"
So to scale up the <svg> and ALL the elements in it, it is necessary to make sure that width and height of the viewbox exactly match the new width and height of the <svg> element:
//original version
<svg> width="100" height="100" viewbox="0 0 100 100" preserveAspectRatio="xMinYMin meet"</svg>
//scaled up version
<svg> width="200" height="200" viewbox="0 0 200 200" preserveAspectRatio="xMinYMin meet"</svg>
If i convert the scaled up svg into a canvas, it fits the whole image without loosing any quality (which is the biggest benefit of vector graphics anyway). Cannot believe it took my so long to get that.

Plot x and y coordinates on an image using javascript

I want to plot x and y coordinates on an image by drawing a circle around the point.
I am getting the image from the server as an array buffer. After the image is displayed i need to mark the corners in the image using coordinates sent as a json from a service.
How can i do this using javascript jquery?
I was thinking of doing the same by overlaying the image with a canvas layer.
How can i implement this?
I have tried the below method but the points were getting plotted outside the image
jQuery('#plotCoordinates').on('click',function(){
jQuery.getJSON( "plot.json", function( response ) {
console.log("response >> " ,response);
var imageCanvas = $('#imageCan');
jQuery.each(response,function(i,obj){
console.log('obj >> ',obj);
point = $('<div class="plot-point"></div>');
x = obj.x,
y = obj.y;
point.css({
left: x + "px",
top: y + "px"
});
point.appendTo(imageCanvas);
});
});
});
You can certainly do it using a canvas.Do the following steps-->
Draw image on canvas as image size=canvas size.
Now, overlay another canvas with a higher z-index exactly on the previous canvas(canvas is transparent by default).
Now use the coordinates of the points you fetched using json to mark the corners in the image by plotting them on this overlayed canvas.
Then you can draw circles around these points on the upper canvas using simple context functions.
As canvas size is same as image size these coordinates will exactly coincide and you can achieve want you want :).In short you draw your circles and markings on the upper canvas and simply draw image on the lower canvas.Easy huhh??:)
Note:Both the canvases and the image itself should be of equal dimensions to exactly coincide.
Have a look at this example https://jsfiddle.net/rbrv949d/
<canvas id="c" style="z-index: 1;"></canvas>
<canvas id="cover" style="z-index: 2;"></canvas>
JS onload
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var canover=document.getElementById('cover');
var ctxover = canvas.getContext('2d');
// Create an image element
var img = new Image();
// When the image is loaded, draw it
img.onload = function () {
ctx.drawImage(img, 0, 0);
ctxover.fillRect(0,0,10,10);
ctxover.fillRect(0,20,10,10);
}
// Specify the src to load the image
img.src = "http://www.experts-exchange.com/images/experts-exchange/experts-exchange-logo.png";
canvas.width=img.width;
canvas.height=img.height;
canover.width=img.width;
canover.height=img.height;
Here , ctxover.fillRect(xposition,yposition,widthinpixels,heightinpixels)
In your case,the xposition and yposition are the one fetched from json.
You can also draw those markings on the same canvas without using the overlayed canvas.Its upto you.Provided if you clear this canvas those markings will also get cleared unlike the other case :)

Canvas pixels to coordinates

I have a canvas with width = 900, height = 574. I have all the pixels of the canvas. Inside the canvas there are some rectangles, knowing all the pixels of a rectangle what I want is to find the coordinates of the 4 points of a rectangle and then find the width and height of the rectangle.
So what I did is:
pMinX = (_.min(points)/ 4) % highlightCanvas.width
pMinY = Math.floor((_.min(points) / 4) / highlightCanvas.width)
pMaxX = (_.max(points) / 4) % highlightCanvas.width
pMaxY = Math.floor((_.max(points) / 4) / highlightCanvas.width)
Points is the array of the pixels (4 channels rgba) of the rectangle of which the coordinates I want to find.
pMinY and pMaxY seem to work well while pMinX, pMaX sometimes seem correct while others are wrong.
To test, I created a floating div and resize it according to:
{
width: pMaxX - pMinX
height: pMaxY - pMinY
}
The height of the div is always correct. But the width there are cases that fails.
Any idea why sometimes the calculation fails?
Here's annotated code showing how to calculate the bounding box (x, y, width, height) of the salmon colored rectangle in your image.
It works like this:
Get the r,g,b,a values for each pixel on the canvas using .getImageData.
Set up test(s) that a pixel's rgba must meet to be considered as "inside the desired rectangle" In your example, the salmon rectangle is made up of 2 colors so this test will capture all pixels inside your rectangle:
// create an array to hold tests that are used to
// find your desired pixels. The tests should be functions
// that return true for your desired rgba pixel values
// (substitue whatever algorithm test that are necessary for your design)
var tests=[];
// add a test into the tests[] array
tests.push(function(r,g,b,a){
return(
(r==251 && g==115 && b==119)||
(r==249 && g==100 && b==107)
);
});
Determine the minX, minY, maxX & maxY of pixels meeting the tests
Calculate the bounding box of the rectangle pixels from the determined minimums and maximums:
var bounds={
x:minX,
y:minY,
width:maxX-minX,
height:maxY-minY
};
Important Note: For .getImageData to be allowed, you must satisfy security restrictions. The usual way is to serve the image on the same domain as your webpage. Alternatively, you can set up the server hosting the image to serve that image to any anonymous requestor.
Example code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// load the image
var img=new Image();
// the image must satisfy cross-origin restrictions
// or else we can't use .getImageData
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/findRect.png";
function start(){
// resize the canvas to the image size
// and draw the image onto the canvas
cw=canvas.width=img.width;
ch=canvas.height=img.height;
ctx.drawImage(img,0,0);
// create an array to hold tests that are used to
// find your desired pixels. The tests should be functions
// that return true for your desired rgba pixel values
// (substitue whatever algorithm test that are necessary for
// your design)
var tests=[];
// sample test returns true if matching the 2 colors making up the rect
tests.push(function(r,g,b,a){
return(
(r==251 && g==115 && b==119)||
(r==249 && g==100 && b==107)
);
});
// find the bounds of all pixels meeting the prescribed test(s)
var bounds=findImageBounds(tests);
// testing...draw just the discovered rect to a second canvas
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
document.body.appendChild(c);
c.width=cw;
c.height=ch;
cctx.drawImage(canvas,
bounds.x,bounds.y,bounds.w,bounds.h,
bounds.x,bounds.y,bounds.w,bounds.h
);
}
function findImageBounds(tests){
// get the rgba color values for all pixels on the canvas
var d=ctx.getImageData(0,0,cw,ch).data;
// iterate over each pixel
// find the min/max X,Y of pixels where all tests are true
var minX=1000000;
var minY=1000000;
var maxX=-1000000;
var maxY=-1000000;
var hits=0;
for(var y=0;y<ch;y++){
for(var x=0;x<cw;x++){
// n==the position in the rgba array for canvas position x,y
n=(y*cw+x)*4;
// the rgba values at this pixel
r=d[n];
g=d[n+1];
b=d[n+2];
a=d[n+3];
// run all tests on this pixel
var testsTrue=true;
for(var i=0;i<tests.length;i++){
testsTrue=testsTrue && tests[i](r,g,b,a);
}
// if this pixel meets all tests
// see if it influences our boundary
if(testsTrue){
hits++;
if(x<minX){minX=x;}
if(y<minY){minY=y;}
if(x>minX){maxX=x;}
if(y>maxY){maxY=y;}
}
}}
// return the x,y,width,height of the bounding box
// of pixels meeting all the supplied tests
return({x:minX,y:minY,w:maxX-minX,h:maxY-minY,pixelCount:hits});
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4>The original canvas</h4>
<canvas id="canvas" width=300 height=300></canvas>
<h4>Just the rect from the canvas</h4>

Categories