The idea of the program is to have an image of a map and to overlay a black canvas on that map. Then the user will click on some part of the canvas and, similar to a spray paint tool, the pixels near the mouse will become transparent on the canvas. Thus, the map will be shown (like a fog of war type feature). When I click near the top left of the canvas the spray paint works sort of as intended but as I click further right and down the canvas the pixels that get turned transparent are way further right and down... Any idea what is wrong here? Here is the code:
// On document ready function.
$(document).ready(function() {
canvas = document.getElementById("myImageDisplayerCanvas");
drawingContext = canvas.getContext("2d");
drawingContext.fillStyle = "#000000";
drawingContext.fillRect(0, 0, 800, 554);
});
// Spray paint logic.
var _intervalId, // used to track the current interval ID
_center, // the current center to spray
density = 10,
radius = 10,
drawingCxt,
leftClickPressed,
drawingContext,
canvas;
function getRandomOffset() {
var randomAngle = Math.random() * 360;
var randomRadius = Math.random() * radius;
return {
x: Math.cos(randomAngle) * randomRadius,
y: Math.sin(randomAngle) * randomRadius
};
}
this.startDrawing = function(position) {
_center = position;
leftClickPressed = true;
// spray once every 10 milliseconds
_intervalId = setInterval(this.spray, 10);
};
this.moveDrawing = function(position) {
if (leftClickPressed) {
clearInterval(_intervalId);
_center = position;
// spray once every 10 milliseconds
_intervalId = setInterval(this.spray, 10);
}
}
this.finishDrawing = function(position) {
clearInterval(_intervalId);
leftClickPressed = false;
};
this.spray = function() {
var centerX = parseInt(_center.offsetX),
centerY =
parseInt(_center.offsetY),
i;
for (i = 0; i < density; i++) {
var offset = getRandomOffset();
var x = centerX + parseInt(offset.x) - 1,
y = centerY +
parseInt(offset.y) - 1;
var dy = y * 800 * 4;
var pos = dy + x * 4;
var imageData = drawingContext.getImageData(0, 0, 800, 554);
imageData.data[pos++] = 0;
imageData.data[pos++] = 0;
imageData.data[pos++] = 0;
imageData.data[pos++] = 0;
drawingContext.putImageData(imageData, 0, 0);
}
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<div id="myImageDisplayerDiv" style="position:relative; width:800px; height:554px">
<img src="~/images/RedLarch.jpg" style="width:800px; height:554px; top: 0; left: 0; position: absolute; z-index: 0" />
<canvas id="myImageDisplayerCanvas" onmousedown="startDrawing(event)" onmousemove="moveDrawing(event)" onmouseup="finishDrawing(event)" style="width:800px; height:554px; top: 0; left: 0; position: absolute; z-index: 1; opacity: 1; fill: black" />
</div>
The reason the spray is offset is because you have not set the canvas resolution. You set the resolution via the canvas element width and height properties
<canvas id="canvas" width="800" height="554"></canvas>
Setting the style width and height sets the canvas display size not the resolution. If you don't set the canvas resolution it defaults to 300 by 150.
There were many other problems with your code.
Getting the whole canvas image data just to set a single pixel is way overkill. Just create a single pixel imageData object and place that pixel where needed
Use requestAnimationFrame
to sync with the display, never use setInterval
or setTimeout
as they are not synced to the display hardware and will cause you endless problems.
Create a single mouse event function to handle all the mouse events and just record the mouse state. Use the main loop called by requestAnimationFrame to handle the drawing, never draw from a mouse event.
You don't need to define functions with this.functionName = function(){}
if the function is not part of an object.
Trig function use radians not degrees. 360 deg in radians is 2 * Math.PI
Below is a quick rewrite of you code using the above notes.
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop); // start main loop when code below has run
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, 800, 554);
// create a pixel buffer for one pixel
const imageData = ctx.getImageData(0, 0, 1, 1);
const pixel32 = new Uint32Array(imageData.data.buffer);
pixel32[0] = 0;
// Spray paint logic.
const density = 10;
const radius = 10;
// mouse handler
const mouse = {x : 0, y : 0, button : false};
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
function getRandomOffset() {
const angle = Math.random() * Math.PI * 2;
const rad = Math.random() * radius;
return { x: Math.cos(angle) * rad, y: Math.sin(angle) * rad};
}
function spray(pos) {
var i;
for (i = 0; i < density; i++) {
const offset = getRandomOffset();
ctx.putImageData(imageData, pos.x + offset.x, pos.y + offset.y);
}
}
// main loop called 60 times a second
function mainLoop(){
if (mouse.button) { spray(mouse) }
requestAnimationFrame(mainLoop);
}
<canvas id="canvas" width="800" height="554"></canvas>