Cursor wont align with HTML Canvas on longer and thinner images - javascript

I have a canvas that I am trying to draw on whenever I use square images with dimensions like such:
Width:2925
Height:2354
My cursor works correctly. When I use images with dimensions like Width: 585 Height: 2354 My cursor refuses to align correctly. Here is my JS I am using:
var canvas;
var ctx;
var canvasOffset;
var offsetX;
var offsetY;
var isDrawing = false;
var drag = false;
var rect = {};
var background = new Image();
//canvas.height = background.height;
//canvas.width = background.width;
var aspectRatio;
var parentOffsetWidth;
var parentOffsetHeight;
// Make sure the image is loaded first otherwise nothing will draw.
$(background).on("load", function () {
//ctx.drawImage(background,0,0);
//ctx.width = background.width;
//drawImageProp(ctx, background, 0, 0, background.width, background.height);.
//coverImg(background, 'cover');
//coverImg(background, 'contain');
//ctx.drawImage(background, 0, 0, background.width,background.height,0, 0, ctx.width, ctx.height);
console.log("--background img load--");
parentOffsetWidth = document.getElementById("imgGrid").offsetWidth;
parentOffsetHeight = document.getElementById("imgGrid").offsetHeight;
console.log("w:" + parentOffsetWidth);
console.log("h:" + parentOffsetHeight);
if (parentOffsetWidth < 500) {
//if img is to small to render properly
//parentOffsetWidth = 500;
}
if (background != null) {
canvas.height = (background.height / background.width) * parentOffsetWidth;
canvas.width = parentOffsetWidth;
}
//coverImg(background, 'cover');
drawImageProp(ctx, background, 0, 0, canvas.width, canvas.height, offsetX, offsetY);
})
$(document).ready(function () {
//ctx.drawImage(background,0,0);
})
function Load(backgroundImgSrc) {
console.log("--load--");
console.log(backgroundImgSrc);
background.src = backgroundImgSrc;
aspectRatio = background.height / background.width;
canvas = document.getElementById("canvas");
canvasOffset = $("#canvas").offset();
offsetX = canvasOffset.left;
offsetY = canvasOffset.top;
ctx = canvas.getContext("2d");
init();
}
var startX;
var startY;
const coverImg = (img, type) => {
const imgRatio = img.height / img.width
const winRatio = window.innerHeight / window.innerWidth
if ((imgRatio < winRatio && type === 'contain') || (imgRatio > winRatio && type === 'cover')) {
const h = window.innerWidth * imgRatio
ctx.drawImage(img, 0, (window.innerHeight - h) / 2, window.innerWidth, h)
}
if ((imgRatio > winRatio && type === 'contain') || (imgRatio < winRatio && type === 'cover')) {
const w = window.innerWidth * winRatio / imgRatio
ctx.drawImage(img, (window.w - w) / 2, 0, w, window.innerHeight)
}
}
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
////////////////////////////////////
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
function mouseDown(e) {
console.log(e);
rect.Left = e.pageX - offsetX;
rect.Top = e.pageY - offsetY;
rect.Left_Percentage = (rect.Left / canvas.width) * 100;
rect.Top_Percentage = (rect.Top / canvas.height) * 100;
drag = true;
}
function mouseUp() {
drag = false;
//ctx.clearRect(0,0,canvas.width,canvas.height);
canvas.style.cursor = "default";
window.dotnetInstance.invokeMethodAsync("ShowPopup", rect);
}
function mouseMove(e) {
if (drag) {
rect.Width = (e.pageX - offsetX) - rect.Left;
rect.Height = (e.pageY - offsetY) - rect.Top;
rect.Height_Percentage = (rect.Height/canvas.height) * 100;
rect.Width_Percentage = (rect.Width / canvas.width) * 100;
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw();
}
}
function draw() {
ctx.setLineDash([10]);
ctx.strokeRect(rect.Left, rect.Top, rect.Width, rect.Height);
}
window.SetDotNetInstance = (dotnetHelper) => {
console.log(dotnetHelper);
if (window.dotnetInstance == null) {
window.dotnetInstance = dotnetHelper;
}
}
function toggleDisplay(current) {
if ($(current).is(":checked")){
$(".clickRegionBlock").hide();
$(".blockNumberInfo").hide();
$("#imgGrid>.border").removeClass("border")
}
else {
$(".clickRegionBlock").show();
$(".blockNumberInfo").show();
$("#imgGrid>.box-border").addClass("border");
}
}
function Zoom() {
var gridSizes = $("#imgGrid").css("grid-template-columns");
var splitArrPx = gridSizes.split(" ");
var firstpx = parseInt(splitArrPx[0].replace("px", ""));
firstpx += 100;
var zoomedGridSizes = "";
splitArrPx.forEach(function (item, index, arr) {
var replacedItem = item.replace(/\d+/, firstpx);
zoomedGridSizes += replacedItem + " ";
})
console.log(zoomedGridSizes);
$("#imgGrid").css("grid-template-columns", zoomedGridSizes);
Load(background.src);
}
And here is my HTML code I am using .Net Blazor as well to achieve this that is why there are variables in my HTML it should not effect my javascript:
<canvas id="canvas" style="background-image:url('api/contentpreview/blocks/#b.PageId/#b.LocalIndex/#Resolution/#zone'); width:100%; height:auto; background-size:cover;" #onload='() => LoadImg("api/contentpreview/blocks/" + b.PageId + "/" + b.LocalIndex + "/" + Resolution + "/" + zone)' />
initCanvas = true;
canvasImg = "api/contentpreview/blocks/" + b.PageId + "/" + b.LocalIndex + "/" + Resolution + "/" + zone;

Related

How to make image fit on canvas without deforming

How to fit img to canvas, each time I put the //commented code the canvas disapears, I don't know how to get around this one. So i gave it a few tries but the frame goes away and I would like the canvas to take the size of the image so no stretching or deforming. There must be a simple line to add just for the format. Currently the image goes beyonf the canvas format from the left top up.
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1e3 / 60)
}
let curCanvas = document.getElementById('canvas0')
let curCtx = curCanvas.getContext('2d')
let imgCanvas = document.getElementById('canvas-ref')
let imgCtx = imgCanvas.getContext('2d')
curCanvas.width = parent.innerWidth
curCanvas.height = parent.innerHeight
// imgCanvas.width = window.innerWidth
// imgCanvas.height = window.innerHeight
// Create a variable for the canvas and it's context
// var canvas = document.getElementById("canvas-ref");
// var ctx = canvas.getContext("2d");
// // Initialise an image object
// var image = new Image();
// // When it loads an image
// image.onload = function() {
// // Get the canvas' current style
// var canvasStyle = getComputedStyle(canvas);
// // Get it's current width, minus the px at the end
// var canvasWidth = canvasStyle.width.replace("px", "");
// // Work out the images ratio
// var imageRatio = this.width/this.height;
// // Work out the new height of the canvas, keeping in ratio with the image
// var canvasHeight = canvasWidth/imageRatio;
// // Set the canvas' height in the style tag to be correct
// canvas.style.height = canvasHeight+"px";
// // Set the width/height attributes to be correct (as this is what drawImage uses)
// canvas.width = canvasWidth;
// canvas.height = canvasHeight;
// // Draw the image at the right width/height
// ctx.drawImage(this, 0, 0, canvasWidth, canvasHeight);
// };
// Reference image
const img = new Image();
img.onload = () => {
imgCanvas.width = img.naturalWidth
imgCanvas.height = img.naturalHeight
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
imgCtx.drawImage(img, 0, 0);
}
// const img = new Image();
// image.src = "imgURL";
// image.onload = function(){
// scaleToFit(this);
// }
// function scaleToFit(img){
// // get the scale
// var scale = Math.min(canvas.width / img.width, canvas.height / img.height);
// // get the top left position of the image
// var x = (canvas.width / 2) - (img.width / 2) * scale;
// var y = (canvas.height / 2) - (img.height / 2) * scale;
// ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
// }
// img.onload = () => {
// imgCanvas.width = img.naturalWidth
// imgCanvas.height = img.naturalHeight
// imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
// imgCtx.drawImage(img, 0, 0);
// }
curCtx.strokeStyle = '#555'
let mouse = {
cut: 8,
influence: 50,
down: false,
button: 1,
x: 0,
y: 0,
px: 0,
py: 0
}
class Point {
constructor(x, y) {
this.x = x
this.y = y
this.px = x
this.py = y
this.vx = 0
this.vy = 0
this.pinX = null
this.pinY = null
this.origx = x;
this.origy = y;
canvas1
this.constraints = []
}
update(delta) {
if (this.pinX && this.pinY) return this
if (mouse.down) {
let dx = this.x - mouse.x
let dy = this.y - mouse.y
let dist = Math.sqrt(dx * dx + dy * dy)
if (mouse.button === 1 && dist < mouse.influence) {
this.px = this.x - (mouse.x - mouse.px)
this.py = this.y - (mouse.y - mouse.py)
} else if (dist < mouse.cut) {
this.constraints = []
}
}
this.addForce(0, gravity)
let nx = this.x + (this.x - this.px) * friction + this.vx * delta
let ny = this.y + (this.y - this.py) * friction + this.vy * delta
this.px = this.x
this.py = this.y
this.x = nx
this.y = ny
this.vy = this.vx = 0
if (this.x >= curCanvas.width) {
this.px = curCanvas.width + (curCanvas.width - this.px) * bounce
this.x = curCanvas.width
} else if (this.x <= 0) {
this.px *= -1 * bounce
this.x = 0
}
if (this.y >= curCanvas.height) {
this.py = curCanvas.height + (curCanvas.height - this.py) * bounce
this.y = curCanvas.height
} else if (this.y <= 0) {
this.py *= -1 * bounce
this.y = 0
}
return this
}
draw() {
let i = this.constraints.length
while (i--) this.constraints[i].draw()
}
resolve() {
if (this.pinX && this.pinY) {
this.x = this.pinX
this.y = this.pinY
return
}
this.constraints.forEach((constraint) => constraint.resolve())
}
attach(point) {
this.constraints.push(new Constraint(this, point))
}
free(constraint) {
this.constraints.splice(this.constraints.indexOf(constraint), 1)
}
addForce(x, y) {
this.vx += x
this.vy += y
}
pin(pinx, piny) {
this.pinX = pinx
this.pinY = piny
}
}
class Constraint {
constructor(p1, p2) {
this.p1 = p1
this.p2 = p2
this.length = spacing
}
resolve() {
let dx = this.p1.x - this.p2.x
let dy = this.p1.y - this.p2.y
let dist = Math.sqrt(dx * dx + dy * dy)
if (dist < this.length) return
let diff = (this.length - dist) / dist
if (dist > tearDist) this.p1.free(this)
let mul = diff * 0.5 * (1 - this.length / dist)
let px = dx * mul
let py = dy * mul
!this.p1.pinX && (this.p1.x += px)
!this.p1.pinY && (this.p1.y += py)
!this.p2.pinX && (this.p2.x -= px)
!this.p2.pinY && (this.p2.y -= py)
return this
}
draw() {
curCtx.drawImage(imgCanvas, this.p1.origx, this.p1.origy,
spacing, spacing, this.p1.x, this.p1.y, spacing + 1, spacing + 1);
}
}
class Cloth {
constructor() {
this.points = []
let startX = curCanvas.width / 2 - (clothWidth * spacing / 2)
console.log("Start x", startX);
for (let y = 0; y <= clothHeight; y++) {
for (let x = 0; x <= clothWidth; x++) {
let point = new Point(startX + x * spacing, 5 + y * spacing)
y === 0 && point.pin(point.x, point.y)
x !== 0 && point.attach(this.points[this.points.length - 1])
y !== 0 && point.attach(this.points[x + (y - 1) * (clothWidth + 1)])
this.points.push(point)
}
}
}
update(delta) {
let i = accuracy
while (i--) {
this.points.forEach((point) => {
point.resolve()
})
}
curCtx.beginPath()
this.points.forEach((point) => {
point.update(delta * delta).draw()
})
curCtx.stroke()
}
}
function setMouse(e) {
if (curCanvas === null) return;
let rect = curCanvas.getBoundingClientRect()
mouse.px = mouse.x
mouse.py = mouse.y
mouse.x = e.clientX - rect.left * 0.5
mouse.y = e.clientY - rect.top
}
function bindEvents(c) {
if (curCanvas === null) return;
c.onmousedown = (e) => {
mouse.button = e.which
mouse.down = true
setMouse(e)
}
c.onmousemove = setMouse
c.onmouseup = () => (mouse.down = false)
c.oncontextmenu = (e) => e.preventDefault()
}
function load_canvas(id) {
console.log("Loading canvas ", id);
switch (id) {
case 0:
canvas0();
break;
case 1:
canvas1();
break;
case 2:
canvas2();
break;
case 3:
canvas3();
break;
case 4:
canvas4();
break;
case 5:
canvas5();
break;
case 6:
canvas6();
break;
case 7:
canvas7();
break;
case 8:
canvas8();
break;
case 9:
canvas9();
break;
case 10:
canvas10();
break;
case 11:
canvas11();
break;
default:
break;
}
console.log("Binding mouse events");
bindEvents(curCanvas)
}
window.onload = function () {
load_canvas(0) // Ne pas modifier
}
I don't know exactly if I understood your question right.
Because every image has a different aspect ratio, I'd suggest to fit the canvas to the size of the image.
Here, I hide the image and set width and height of the canvas accordingly and render the image into the canvas.
Maybe this helps.
I do this on every change of windows size.
You can set the image width and height via css.
window.onload = drawFullImage;
window.onresize = drawFullImage;
function drawFullImage() {
var c = document.getElementById("myCanvas");
var imageDiv = document.getElementById("imagetorender");
c.width = imageDiv.getBoundingClientRect().width;
c.height = imageDiv.getBoundingClientRect().height;
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
var img = document.getElementById("imagetorender");
ctx.drawImage(img, 0, 0, c.width, c.height);
}
body {
position: relative;
margin: 0;
}
#imagetorender {
visibility: hidden;
overflow: auto;
width: 400px;
}
#myCanvas {
position: absolute;
top: 0;
left: 0;
}
<div id="wrapper">
<img id="imagetorender" src="http://farm5.static.flickr.com/4005/4706825697_c0367e6dee_b.jpg" />
<canvas id="myCanvas"></canvas>
</div>

arrow with modification point

Hi I'm trying to reproduce a draw.io effect. When you draw an arrow it display a blue point in the middle of the arrow that allows you to create angle between the two lines and it display two blue point that allow you to do the same with the two new line. I have added image below. It'll be easier to understand.
I wonder how to code dynamically these blue points that allow to "break" the line
var ctx = tempcanvas.getContext('2d'),
mainctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
x1,
y1,
isDown = false;
ctx.translate(0.5, 0.5);
tempcanvas.onmousedown = function(e) {
var rect = canvas.getBoundingClientRect();
x1 = e.clientX - rect.left;
y1 = e.clientY - rect.top;
isDown = true;
}
tempcanvas.onmouseup = function() {
isDown = false;
mainctx.drawImage(tempcanvas, 0, 0);
ctx.clearRect(0, 0, w, h);
}
tempcanvas.onmousemove = function(e) {
if (!isDown) return;
var rect = canvas.getBoundingClientRect(),
x2 = e.clientX - rect.left,
y2 = e.clientY - rect.top;
var p0={x1,y1};
var p1={x2,y2};
ctx.clearRect(0, 0, w, h);
drawLineWithArrowhead(p0,p1,25);
}
function drawLineWithArrowhead(p0,p1,headLength){
var PI=Math.PI;
var degreesInRadians225=225*PI/180;
var degreesInRadians135=135*PI/180;
var dx=p1.x2-p0.x1;
var dy=p1.y2-p0.y1;
var angle=Math.atan2(dy,dx);
// calc arrowhead points
var x225=p1.x2+headLength*Math.cos(angle+degreesInRadians225);
var y225=p1.y2+headLength*Math.sin(angle+degreesInRadians225);
var x135=p1.x2+headLength*Math.cos(angle+degreesInRadians135);
var y135=p1.y2+headLength*Math.sin(angle+degreesInRadians135);
ctx.beginPath();
// draw the line from p0 to p1
ctx.moveTo(p0.x1,p0.y1);
ctx.lineTo(p1.x2,p1.y2);
// draw partial arrowhead at 225 degrees
ctx.moveTo(p1.x2,p1.y2);
ctx.lineTo(x225,y225);
// draw partial arrowhead at 135 degrees
ctx.moveTo(p1.x1,p1.y1);
ctx.lineTo(x135,y135);
// stroke the line and arrowhead
ctx.stroke();
}
canvas {position:absolute;left:0;top:0}
#canvas {background:#eef}
<canvas id="canvas" width=400 height=400></canvas>
<canvas id="tempcanvas" width=400 height=400></canvas>
Example snippet
Sorry out of time (Weekend and all) to write a detailed explanation and no point wasting the code, so hope it helps.
const ctx = canvas.getContext("2d");
ctx.bounds = canvas.getBoundingClientRect();
const P2 = (x = 0, y = 0) => ({x, y});
const points = [];
const lineStyle = "#000";
const nearLineStyle = "#0AF";
const lineWidth = 2;
const nearLineWidth = 3;
const pointStyle = "#000";
const nearPointStyle = "#0AF";
const pointLineWidth = 1;
const nearPointLineWidth = 2;
const arrowSize = 18;
const pointSize = 5;
const nearPointSize = 15;
const checkerSize = 256; // power of two
const checkerCol1 = "#CCC";
const checkerCol2 = "#EEE";
const MIN_SELECT_DIST = 20; // in pixels;
var w = canvas.width, h = canvas.height;
var cw = w / 2, ch = h / 2;
var cursor = "default";
var toolTip = "";
const mouse = { x: 0, y: 0, button: 0 };
const drag = {dragging: false};
requestAnimationFrame(update);
function mouseEvents(e) {
mouse.x = e.pageX - ctx.bounds.left - scrollX;
mouse.y = e.pageY - ctx.bounds.top - scrollY;
if (e.type === "mousedown") { mouse.button |= 1 << (e.which - 1) }
else if (e.type === "mouseup") { mouse.button &= ~(1 << (e.which - 1)) }
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
const checkerboard = (()=> {
const s = checkerSize, s2 = s / 2;
const c = document.createElement("canvas");
c.height = c.width = checkerSize;
const ctx = c.getContext("2d", {alpha: false});
ctx.fillStyle = checkerCol1;
ctx.fillRect(0,0,s, s);
ctx.fillStyle = checkerCol2;
ctx.fillRect(0,0,s2,s2);
ctx.fillRect(s2,s2,s2,s2);
ctx.globalAlpha = 0.25;
var ss = s2;
while(ss > 8) {
ctx.fillStyle = ctx.createPattern(c, "repeat");
ctx.setTransform(1/8,0,0,1/8,0,0);
ctx.fillRect(0,0,s * 8,s * 8);
ss /= 2;
}
return ctx.createPattern(c, "repeat");
})();
function nearestPointLine(points, point, minDist){ // fills nearest object with nearest point and line to point if within minDist.
var i = 0, p1, dist;
nearest.reset(minDist);
const v1 = P2();
const v2 = P2();
const v3 = P2();
for (const p of points) {
v2.x = point.x - p.x;
v2.y = point.y - p.y;
dist = (v2.x * v2.x + v2.y * v2.y) ** 0.5;
if(dist < nearest.point.dist) {
nearest.point.dist = dist;
nearest.point.p = p;
nearest.point.idx = i;
}
if (p1) {
v1.x = p1.x - p.x;
v1.y = p1.y - p.y;
v2.x = point.x - p.x;
v2.y = point.y - p.y;
const u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
if (u >= 0 && u <= 1) { // is closest poin on line segment
v3.x = p.x + v1.x * u;
v3.y = p.y + v1.y * u;
//ctx.fillRect(v3.x, v3.y, 5, 5)
dist = ((v3.y - point.y) ** 2 + (v3.x - point.x) ** 2) ** 0.5;
if(dist < nearest.line.dist) {
nearest.line.dist = dist;
nearest.line.p1 = p1;
nearest.line.p2 = p;
nearest.line.idx = i;
nearest.line.onLine.x = v3.x;
nearest.line.onLine.y = v3.y;
}
}
}
p1 = p;
i ++;
}
if (nearest.point.idx > -1 && nearest.point.dist / 2 <= nearest.line.dist) {
nearest.active = nearest.point;
nearest.near = true;
} else if (nearest.line.idx > -1) {
nearest.active = nearest.line;
nearest.near = true;
}
}
function drawLine(p1, p2) {
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
}
function drawLineArrow(p1, p2) {
var nx = p1.x - p2.x;
var ny = p1.y - p2.y;
const d =( nx * nx + ny * ny) ** 0.5;
if(d > arrowSize) {
nx /= d;
ny /= d;
ctx.setTransform(-nx, -ny, ny, -nx, p2.x, p2.y);
ctx.beginPath()
ctx.fillStyle = ctx.strokeStyle;
ctx.moveTo(0, 0);
ctx.lineTo(-arrowSize, arrowSize / 2);
ctx.lineTo(-arrowSize, -arrowSize / 2);
ctx.fill();
ctx.setTransform(1,0,0,1,0,0);
}
}
function drawPoint(p, size = pointSize) {
ctx.rect(p.x - size / 2, p.y - size / 2, size, size);
}
function drawLines(points) {
var p1;
ctx.strokeStyle = lineStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath()
for(const p of points) {
if (p1) { drawLine(p1 ,p) }
p1 = p;
}
ctx.stroke();
if(points.length > 1) {
drawLineArrow(points[points.length - 2], p1);
}
}
function drawPoints(points) {
ctx.strokeStyle = pointStyle;
ctx.lineWidth = pointLineWidth;
ctx.beginPath()
for(const p of points) { drawPoint(p) }
ctx.stroke();
}
function sizeCanvas() {
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
ctx.bounds = canvas.getBoundingClientRect();
}
}
const nearest = {
point: { isPoint: true },
line: { onLine: P2() },
reset(minDist) {
nearest.point.dist = minDist;
nearest.point.idx = -1;
nearest.line.dist = minDist;
nearest.line.idx = -1;
nearest.active = null;
nearest.near = false;
},
draw() {
const a = nearest.active;
if (a) {
if (a.isPoint) {
ctx.strokeStyle = nearPointStyle;
ctx.lineWidth = nearPointLineWidth;
ctx.beginPath()
drawPoint(a.p, nearPointSize);
ctx.stroke();
} else {
ctx.strokeStyle = nearLineStyle;
ctx.lineWidth = nearLineWidth;
ctx.beginPath()
drawLine(a.p1, a.p2);
ctx.stroke();
ctx.strokeStyle = nearPointStyle;
ctx.lineWidth = nearPointLineWidth;
ctx.beginPath()
drawPoint(a.onLine, nearPointSize);
ctx.stroke();
}
}
}
}
function update() {
cursor = "crosshair";
toolTip = "";
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
sizeCanvas();
ctx.fillStyle = checkerboard;
ctx.fillRect(0, 0, w, h);
if (!drag.dragging) {
nearestPointLine(points, mouse, MIN_SELECT_DIST);
if (nearest.near && nearest.active.isPoint) { cursor = "move"; toolTip = "Drag to move point"}
else if (nearest.near) { cursor = "crosshair"; toolTip = "Click/drag to cut and drag new point" }
else {
if (points.length < 2) {
cursor = "crosshair";
toolTip ="Click to add point";
} else {
cursor = "default";
toolTip = "";
}
}
}
drawLines(points);
drawPoints(points);
nearest.draw();
if((mouse.button & 1) === 1) {
if (!drag.dragging) {
if(points.length < 2 && !nearest.near) {
points.push(P2(mouse.x, mouse.y));
mouse.button = 0;
} else if (nearest.near) {
if (nearest.active.isPoint) {
drag.point = nearest.active.p;
} else {
drag.point = P2(nearest.active.onLine.x, nearest.active.onLine.y);
points.splice(nearest.active.idx, 0, drag.point);
nearestPointLine(points, drag.point, 20);
}
drag.offX = drag.point.x - mouse.x;
drag.offY = drag.point.y - mouse.y;
drag.dragging = true;
}
}
if(drag.dragging) {
drag.point.x = drag.offX + mouse.x;
drag.point.y = drag.offY + mouse.y;
drag.point.x = drag.point.x < 1 ? 1 : drag.point.x > w - 2 ? w - 2 : drag.point.x;
drag.point.y = drag.point.y < 1 ? 1 : drag.point.y > h - 2 ? h - 2 : drag.point.y;
cursor = "none";
}
} else if((mouse.button & 1) === 0) {
drag.dragging = false;
drag.point = null;
}
canvas.title = toolTip;
canvas.style.cursor = cursor;
requestAnimationFrame(update);
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

Why this pixelation animation is jolty

I have the following multi-step pixelation animation. It animates from low-to-high pixelation very slowly to show you that it jolts in between some of the steps, as if the image is slightly moved between renderings. I can't figure out why this is happening or how to make it appear as if the image is staying in one place.
var c = document.createElement('canvas')
c.style.display = 'flex'
c.style.width = '100vw'
c.style.height = '100vh'
c.style['image-rendering'] = 'pixelated'
document.body.appendChild(c)
var x = c.getContext('2d')
x.webkitImageSmoothingEnabled = false
x.mozImageSmoothingEnabled = false
x.msImageSmoothingEnabled = false
x.imageSmoothingEnabled = false
var src = c.getAttribute('data-draw')
var small = `https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/M101_hires_STScI-PRC2006-10a.jpg/307px-M101_hires_STScI-PRC2006-10a.jpg`
var large = `https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/M101_hires_STScI-PRC2006-10a.jpg/1280px-M101_hires_STScI-PRC2006-10a.jpg`
// c.width = c.clientWidth
// c.height = c.clientHeight
var stack = []
var start = false
var place = 0
function queue(image, ratio, width, height) {
stack.push({ image, ratio, width, height })
if (start) return
start = true
setTimeout(proceed, 0)
}
function proceed() {
let point = stack.shift()
let w
let h
if (point.ratio) {
w = c.width = c.clientWidth * point.ratio
h = c.height = c.clientHeight * point.ratio
} else {
w = point.width
h = point.height
}
if (!stack.length) {
x.webkitImageSmoothingEnabled = true
x.mozImageSmoothingEnabled = true
x.msImageSmoothingEnabled = true
x.imageSmoothingEnabled = true
c.classList.remove('px')
}
drawImageProp(x, point.image, 0, 0, w, h)
if (stack.length) {
setTimeout(proceed, 1000)
}
}
var s = new Image()
s.onload = function(){
queue(s, 0.01)
queue(s, 0.03)
var i = new Image()
i.onload = function(){
queue(i, 0.03)
queue(i, 0.11)
queue(i, 1)
}
i.src = large
}
s.src = small
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
Even in between the last two steps it shifts slightly. Wondering what's going wrong and how to fix it.
FYI, there are two images, a small one and a big one, both the same thing. It first loads the small image at low resolution, then loads the large one. While the large one is loading, it does a few animation steps using the small image to start the animation. Then once the large one is done, it picks up where the small one left off and does a few more animation steps using the large image.

Why is camera's position not correctly calculated (canvas)

I am making a clone of agar.io and I am stuck in my code. I can't understand why my camera's position is not correctly calculated. I want my camera's position to half the vector between the farthest blob and the closest blob.
Below is a picture and my code:
<html>
<head>
<title>Play Agario Clone</title>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<canvas id="game">
kindly update your browser.
</canvas>
<script>
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
// camera
update: function(obj) {
var farthestBlobX = Math.max.apply(0, obj.blobs.map(function(cell) { return cell.x }));
var farthestBlobY = Math.max.apply(0, obj.blobs.map(function(cell) { return cell.y }));
var closestBlobX = Math.min.apply(0, obj.blobs.map(function(cell) { return cell.x }));
var closestBlobY = Math.min.apply(0, obj.blobs.map(function(cell) { return cell.y }));
var x = farthestBlobX - closestBlobX;
var y = farthestBlobY - closestBlobY;
var length = Math.sqrt(x * x + y * y);
this.x = length/2 - width/2;
this.y = length/2 - height/2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function () {
for (var i = 0; i < this.blobs.length; i ++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54/this.blobs[i].mass;
this.blobs[i].velX = x/length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y/length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j ++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var x = blob2.x - blob1.x;
var y = blob2.y - blob1.y;
var dist = Math.sqrt(x * x + y * y);
if (dist < blob1.mass + blob2.mass) {
x /= dist;
y /= dist;
blob1.x = blob2.x - x * (blob1.mass + blob2.mass);
blob1.y = blob2.y - y * (blob1.mass + blob2.mass);
}
}
}
}
this.x += (mouseX - width/2)/(width/2) * 1;
this.y += (mouseY - height/2)/(height/2) * 1
},
split: function (cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function () {
for (var i = 0; i < this.blobs.length; i ++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove (e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup () {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass/2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass*2
});
var loop = function () {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update () {
camera.update(player);
player.update();
}
function draw () {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
</script>
</body>
</html>
Instead of computing everything relative to your camera, use your camera to set the global transformation matrix of your canvas, and only for this.
This way, your blobs' updates will be cleaner, and your camera easier to manage.
Now to get the middle position between two points, do (pt1 + pt2) / 2.
You were not clear in your question, if fartherstX and fartherstY should represent the same blob. In your code it wasn't, so I didn't change it.
Also, I didn't gone into all your logics, but beware NaN values, I got some while doing the edit.
function draw() {
var cw = ctx.canvas.width / 2;
var ch = ctx.canvas.height / 2;
// reset transform to clear the canvas
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
// here we really set the camera position
ctx.setTransform(1, 0, 0, 1, -camera.x + cw, -camera.y + ch);
ctx.strokeRect(0, 0, width, height); // just to show the original area
player.draw();
}
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var camera = {
x: 0,
y: 0,
// camera
update: function(obj) {
var farthestBlobX = Math.max.apply(0, obj.blobs.map(function(cell) {
return cell.x
}));
var farthestBlobY = Math.max.apply(0, obj.blobs.map(function(cell) {
return cell.y
}));
var closestBlobX = Math.min.apply(0, obj.blobs.map(function(cell) {
return cell.x
}));
var closestBlobY = Math.min.apply(0, obj.blobs.map(function(cell) {
return cell.y
}));
this.x = (closestBlobX + farthestBlobX) / 2 || 0;
this.y = (closestBlobY + farthestBlobY) / 2 || 0;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX - this.blobs[i].x || 0;
var y = mouseY - this.blobs[i].y || 0;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var x = blob2.x - blob1.x;
var y = blob2.y - blob1.y;
var dist = Math.sqrt(x * x + y * y);
if (dist < blob1.mass + blob2.mass) {
x /= dist;
y /= dist;
blob1.x = blob2.x - x * (blob1.mass + blob2.mass);
blob1.y = blob2.y - y * (blob1.mass + blob2.mass);
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1;
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.blobs[i].x, this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 10,
y: 10,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player);
player.update();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
I see there is an answer already..
var canvas;
var ctx;
var width = innerWidth;
var height = innerHeight;
var mouseX = 0;
var mouseY = 0;
const camera = {
x : 0,
y : 0,
update(obj) { // camera
this.x = (obj.blobsExtent.minx + obj.blobsExtent.maxx) / 2;
this.y = (obj.blobsExtent.miny + obj.blobsExtent.maxy) / 2;
this.x -= width / 2;
this.y -= height / 2;
}
};
const player = {
defaultMass : 54,
blobs : [],
blobsExtent : { // get the extent while updating the blobs save you having to iterate all the objects a second time to get extent
minx :0,
miny : 0,
maxx : 0,
maxy : 0,
},
update () {
var be = this.blobsExtent; // be for Blob Extent alias to save typing and make code easier to read
for (var i = 0; i < this.blobs.length; i++) {
var blob1 = this.blobs[i];
var x = mouseX - blob1.x;
var y = mouseY - blob1.y;
// to stop the divide by zero propigating NaN set length to 1 if less than 1
var length = Math.max(1,Math.sqrt(x * x + y * y)); // x * x is quicker than Math.pow(x,2)
var speed = 54 / blob1.mass;
blob1.velX = x / length * speed * Math.min(1, Math.pow(x / blob1.mass, 2));
blob1.velY = y / length * speed * Math.min(1, Math.pow(x / blob1.mass, 2));
blob1.x += blob1.velX;
blob1.y += blob1.velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i) {
var blob2 = this.blobs[j];
var x = blob2.x - blob1.x;
var y = blob2.y - blob1.y;
var dist = Math.sqrt(x * x + y * y);
var radTotal = blob1.mass + blob2.mass;
if (dist < radTotal) {
x /= dist;
y /= dist;
blob1.x = blob2.x - x * radTotal;
blob1.y = blob2.y - y * radTotal;
}
}
}
if(i === 0){ // use first blob to setup min max
be.maxx = be.minx = blob1.x;
be.maxy = be.miny = blob1.y;
}else{
be.maxx = Math.max(be.maxx, blob1.x);
be.maxy = Math.max(be.maxy, blob1.y);
be.minx = Math.min(be.minx, blob1.x);
be.miny = Math.min(be.miny, blob1.y);
}
}
},
split (cell) {
cell.mass /= 2;
this.blobs.push(createBlob(cell.x, cell.y, cell.mass));
},
draw () {
var b; // alias for blob
ctx.fillStyle = "red"; // all the same colour then can render as one path
ctx.setTransform(1,0,0,1,-camera.x,-camera.y);
ctx.beginPath();
for (var i = 0; i < this.blobs.length; i++) {
b = this.blobs[i];
ctx.arc( b.x, b.y, b.mass, 0, Math.PI * 2);
ctx.closePath();
}
ctx.fill();
ctx.setTransform(1,0,0,1,0,0); // restore default transform
}
};
function handleMouseMove(e) {
mouseX = e.clientX + camera.x;
mouseY = e.clientY + camera.y;
}
function createBlob(x,y,mass){ return {x,y,mass} }
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push(createBlob(0,0,player.defaultMass));
player.blobs.push(createBlob(100,100,player.defaultMass / 2));
player.blobs.push(createBlob(100,100,player.defaultMass * 2));
}
function update() {
camera.update(player);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
setup();
requestAnimationFrame(loop);
body {
margin: 0;
padding: 0;
}
<canvas id="game"></canvas>

Getting mouse position within canvas

I am trying to modified this effect to work within my canvas. However, I can't seem to get the mouse position to work properly. The hover area isn't contained to my canvas.
Here's a CSSdeck of how i tried to implement it: http://cssdeck.com/labs/ukktjtis
Effect:
function hoverText(){
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
keyword = "MacroPlay Games",
imageData,
density = 3,
mouse = {},
hovered = false,
colors = ["0,120,232", "8,200,255", "30,140,255"],
minDist = 20,
bounceFactor = 0.7;
var W = window.innerWidth, H = window.innerHeight;
canvas.width = W;
canvas.height = H;
document.addEventListener("mousemove", function(e) {
mouse.x = e.pageX-50;
mouse.y = e.pageY+200;
}, false);
// Particle Object
var Particle = function() {
this.w = Math.random() * 10.5;
this.h = Math.random() * 10.5;
this.x = -W;
this.y = -H;
this.free = false;
this.vy = -5 + parseInt(Math.random() * 10) / 2;
this.vx = -4 + parseInt(Math.random() * 8);
// Color
this.a = Math.random();
this.color = colors[parseInt(Math.random()*colors.length)];
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
this.draw = function() {
ctx.fillStyle = "rgba("+this.color+","+this.a+")";
ctx.fillRect(this.x, this.y, this.w, this.h);
}
};
var particles = [];
// Draw the text
function drawText() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "#000000";
ctx.font = "100px 'Arial', sans-serif";
ctx.textAlign = "center";
ctx.fillText(keyword, W/2, H/2);
}
// Clear the canvas
function clear() {
ctx.clearRect(0, 0, W, H);
}
// Get pixel positions
function positionParticles() {
// Get the data
imageData = ctx.getImageData(0, 0, W, W);
data = imageData.data;
// Iterate each row and column
for (var i = 0; i < imageData.height; i += density) {
for (var j = 0; j < imageData.width; j += density) {
// Get the color of the pixel
var color = data[((j * ( imageData.width * 4)) + (i * 4)) - 1];
// If the color is black, draw pixels
if (color == 255) {
particles.push(new Particle());
particles[particles.length - 1].setPosition(i, j);
}
}
}
}
drawText();
positionParticles();
// Update
function update() {
clear();
for(i = 0; i < particles.length; i++) {
var p = particles[i];
if(mouse.x > p.x && mouse.x < p.x + p.w && mouse.y > p.y && mouse.y < p.y + p.h)
hovered = true;
if(hovered == true) {
var dist = Math.sqrt((p.x - mouse.x)*(p.x - mouse.x) + (p.y - mouse.y)*(p.y - mouse.y));
if(dist <= minDist)
p.free = true;
if(p.free == true) {
p.y += p.vy;
p.vy += 0.15;
p.x += p.vx;
// Collision Detection
if(p.y + p.h > H) {
p.y = H - p.h;
p.vy *= -bounceFactor;
// Friction applied when on the floor
if(p.vx > 0)
p.vx -= 0.1;
else
p.vx += 0.1;
}
if(p.x + p.w > W) {
p.x = W - p.w;
p.vx *= -bounceFactor;
}
if(p.x < 0) {
p.x = 0;
p.vx *= -0.5;
}
}
}
ctx.globalCompositeOperation = "lighter";
p.draw();
}
}
(function animloop(){
requestAnimFrame(animloop);
update();
})();
}
It's highly advised you use jquery (or some js lib) to avoid cross-browser issues like getting the mouse position.
You can easily get the mouse position in any browser using jquery like this:
// get the position of the canvas relative to the web page
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// then in the mouse handler, get the exact mouse position like this:
function handleMouseDown(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
}
// tell the browser to send mousedown events to the handleMouseDown function
$("#canvas").mousedown(function(e){handleMouseDown(e);});
I personally prefer a library like hammer.js. I've use it for all my projects - check it out, it's pretty good.

Categories

Resources