I'm currently doing a project regarding p5.js and I need to do the following:
Input: given image and rectangle;
Algorithm: move the image inside the rectangle. By saying moving, I mean when the mouse is clicked, move the whole image to match the size of the rectangle by showing only portion of the image (size of the rectangle).
Output: dragging image inside the rectangle.
Update: When I import an image with React input form, it does not change the current image in P5 even I call the p5.redraw() function.
My current code is moving the image, but I need an image to be inside the rectangle and show only part of the image that is this size of the rectangle. The code is written in React:
const [selectedFile, setSelectedFile] = useState();
let backgroundImage;
let dragging = false; // Is the object being dragged?
let rollover = false; // Is the mouse over the ellipse?
let x, y, w, h; // Location and size
let offsetX, offsetY; // Mouseclick offset
const setup = (p5, parentRef) => {
p5.createCanvas(1000, 500).parent(parentRef);
// Starting location
x = 350;
y = 50;
const url =
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
backgroundImage = p5.loadImage(url);
// Dimensions
w = 700;
h = 700;
};
const draw = (p5) => {
p5.background(233);
if (
p5.mouseX > x &&
p5.mouseX < x + w &&
p5.mouseY > y &&
p5.mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
// Adjust location if being dragged
if (dragging) {
x = p5.mouseX + offsetX;
y = p5.mouseY + offsetY;
}
if (selectedFile != null) {
const url = URL.createObjectURL(selectedFile);
backgroundImage = p5.loadImage(url);
// p5.image(backgroundImage, x, y, w, h);
// p5.redraw();
// backgroundImage = p5.loadImage(url, () => {
// p5.image(backgroundImage, x, y, w, h);
// p5.redraw();
// });
} else {
}
p5.image(backgroundImage, x, y);
drawMaskOverlay(p5);
};
const drawMaskOverlay = (p5) => {
p5.fill(255);
p5.noStroke();
p5.beginShape();
// CW
p5.vertex(0, 0);
p5.vertex(p5.width, 0);
p5.vertex(p5.width, p5.height);
p5.vertex(0, p5.height);
// cutout contour CCW
p5.beginContour();
p5.vertex(400, 100);
p5.vertex(400, 400);
p5.vertex(600, 400);
p5.vertex(600, 100);
p5.endContour();
p5.endShape();
};
const mousePressed = (p5) => {
if (
p5.mouseX > x &&
p5.mouseX < x + w &&
p5.mouseY > y &&
p5.mouseY < y + h
) {
dragging = true;
offsetX = x - p5.mouseX;
offsetY = y - p5.mouseY;
}
};
const mouseReleased = (p5) => {
// Quit dragging
dragging = false;
};
to display it, I use the following code:
return (
<div className="App">
<div>
<h1>Select an image</h1>
<input
type="file"
name="file"
id="file"
// onChange={(e) => setSelectedFile(e.target.files[0])}
onChange={(e) => setSelectedFile(e.target.files[0])}
/>
<Sketch
// preload={preload}
setup={setup}
draw={draw}
mouseReleased={mouseReleased}
mousePressed={mousePressed}
/>
</div>
</div>
);
I use react-p5 library.
You can see and test the code in StackBlitz.
One quick workaround is to simply overlay a cutout shape using beginContour() / endContour(), thus only showing a portion of the image:
let backgroundImage;
let dragging = false;
let rollover = false;
let x, y, w, h; // Location and size
let offsetX, offsetY;
preload = () => {
const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
backgroundImage = loadImage(url);
};
setup = () => {
createCanvas(1000, 500);
// Starting location
x = 100;
y = 100;
// Dimensions
w = 300;
h = 400;
}
draw = () => {
background(233);
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
if (dragging) {
x = mouseX + offsetX;
y = mouseY + offsetY;
}
image(backgroundImage, x, y, w, h);
// noFill();
// stroke(0);
// rect(100, 100, 200, 300);
drawMaskOverlay();
};
function drawMaskOverlay(){
fill(0);
noStroke();
beginShape();
// CW
vertex(0, 0);
vertex(width, 0);
vertex(width, height);
vertex(0, height);
// cutout contour CCW
beginContour();
vertex(100, 100);
vertex(100, 400);
vertex(300, 400);
vertex(300, 100);
endContour();
endShape();
}
mousePressed = () => {
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
dragging = true;
offsetX = x - mouseX;
offsetY = y - mouseY;
}
};
mouseReleased = () => {
dragging = false;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
If the project is a school project it's unclear whether the above will pass if it doesn't cover the materials taught. If it's a personal project, the above should be a simple enough workaround.
Other alternatives ways of showing parts of the image could be:
working with p5.Image which has a mask() method (potentially in conjuction with p5.Graphics which would make it easy draw into and covert to a p5.Image via get())
with with p5.Image and instead of masking, clearing a blank (buffer) image and only copying a portion of backgroundImage (depending on it's dragged location) via copy() which would make it appear like backgroundImage is masked.
Update based on your file select comment here's a tweaked version of the above snippet:
let backgroundImage;
let dragging = false;
let rollover = false;
let x, y, w, h; // Location and size
let offsetX, offsetY;
preload = () => {
const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
backgroundImage = loadImage(url);
};
setup = () => {
createCanvas(1000, 500);
// Starting location
x = 100;
y = 100;
// Dimensions
w = 300;
h = 400;
createFileInput(handleFile);
}
handleFile = (file) => {
if (file.type === 'image') {
backgroundImage = createImg(file.data, '');
backgroundImage.hide();
} else {
backgroundImage = null;
}
}
draw = () => {
background(233);
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
rollover = true;
} else {
rollover = false;
}
if (dragging) {
x = mouseX + offsetX;
y = mouseY + offsetY;
}
// only render the image if it exists
if(backgroundImage)
image(backgroundImage, x, y, w, h);
// noFill();
// stroke(0);
// rect(100, 100, 200, 300);
drawMaskOverlay();
};
function drawMaskOverlay(){
fill(0);
noStroke();
beginShape();
// CW
vertex(0, 0);
vertex(width, 0);
vertex(width, height);
vertex(0, height);
// cutout contour CCW
beginContour();
vertex(100, 100);
vertex(100, 400);
vertex(300, 400);
vertex(300, 100);
endContour();
endShape();
}
mousePressed = () => {
if (
mouseX > x &&
mouseX < x + w &&
mouseY > y &&
mouseY < y + h
) {
dragging = true;
offsetX = x - mouseX;
offsetY = y - mouseY;
}
};
mouseReleased = () => {
dragging = false;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
Related
Here is the link to the codepen: https://codepen.io/Jsbbvk/pen/RwGBwOO
const edgePadding = 80;
const panSpeed = 5;
const expandCanvasEdge = (x, y) => {
let pan = {
x: 0,
y: 0,
};
const width = canvas.getWidth(),
height = canvas.getHeight();
if (x <= edgePadding) {
//left
const speedRatio = 1 - Math.max(0, x) / edgePadding;
pan.x = panSpeed * speedRatio;
} else if (x >= width - edgePadding) {
//right
const speedRatio =
1 - (width - Math.min(width, x)) / edgePadding;
pan.x = -panSpeed * speedRatio;
}
if (y <= edgePadding) {
//top
const speedRatio = 1 - Math.max(0, y) / edgePadding;
pan.y = panSpeed * speedRatio;
} else if (y >= height - edgePadding) {
//bottom
const speedRatio =
1 - (height - Math.min(height, y)) / edgePadding;
pan.y = -panSpeed * speedRatio;
}
if (pan.x || pan.y) {
canvas.relativePan(new fabric.Point(pan.x, pan.y));
}
}
canvas.on('mouse:move', function(opt) {
if (this.isMouseDown && this.isDrawingMode) {
let {x, y} = canvas.getPointer(opt.e, true);
expandCanvasEdge(x, y);
}
if (!this.isDrawingMode && this.isDragging) {
//panning
var e = opt.e;
var vpt = this.viewportTransform;
vpt[4] += e.clientX - this.lastPosX;
vpt[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
});
In the demo, when you draw close to the edge of the canvas, the canvas should pan to allow more drawing space.
However, while the panning is happening, the drawing (path) is static on the canvas; it doesn't stretch as the canvas pans.
Is there a way to fix this issue?
I did some deep research for you and found a few examples.
You can overcome this situation by using the relativePan function.
One of the examples I have found:
function startPan(event) {
if (event.button != 2) {
return;
}
var x0 = event.screenX,
y0 = event.screenY;
function continuePan(event) {
var x = event.screenX,
y = event.screenY;
fc.relativePan({ x: x - x0, y: y - y0 });
x0 = x;
y0 = y;
}
function stopPan(event) {
$(window).off('mousemove', continuePan);
$(window).off('mouseup', stopPan);
};
$(window).mousemove(continuePan);
$(window).mouseup(stopPan);
$(window).contextmenu(cancelMenu);
};
function cancelMenu() {
$(window).off('contextmenu', cancelMenu);
return false;
}
$(canvasWrapper).mousedown(startPan);
You can determine a roadmap by examining the resources and demos here.
JSFiddle demo https://jsfiddle.net/tornado1979/up48rxLs/
I need help with a pretty difficult problem. I am currently making a game with React and Redux. In this game I use a canvas to create a map data from my redux store. Currently the map is just a matrix of black and white squares. Let's say that I wanted to change the color of a square when you click on it, but also maintain the ability to drag the element. The problem is that it is difficult to pinpoint exactly where the mouse is clicking given that the element can be dragged anywhere on the page. As far as I can tell, none of the mouseclick event object properties seem to be able to tell me. I thought offsetX and offsetY would tell me, but they don't seem to stay the same when the canvas object moves for some reason.
I am using React and Redux for this project, and the CanvasMap element is wrapped in a Draggable from this library: https://www.npmjs.com/package/react-draggable#draggablecore
import React from 'react'
import { connect } from 'react-redux'
import './CanvasMap.css'
class CanvasMap extends React.Component{
componentDidMount() {
const map = this.props.map //Data representing the map
const canvas = this.refs.canvas
const context = canvas.getContext('2d')
//Building the map on the canvas
for(let i = 0; i < map.length; i++){
for(let j = 0; j < map[i].length; j++){
const x=i*100
const y=j*100
const isBlackSquare= map[i][j] === 'black' ? true : false
if(isBlackSquare) context.fillRect(x, y, 100, 100)
else context.strokeRect(x, y, 100, 100)
}
}
function handleClick(e){
//None of the event properties seem to stay the same when the canvas is moved
console.log(e)
}
canvas.addEventListener('click', handleClick)
}
render(){
return (
<div className='Map'>
<canvas ref='canvas' width={8000} height={8000} />
</div>
)
}
}
function mapStateToProps(state){
return {
map: [...state.map]
}
}
const connectedComponent = connect(mapStateToProps)(CanvasMap)
export { connectedComponent as CanvasMap }
In most cases when you click on an HTML element you can use the rectangleBounding Box and get coordinators from that like
domRect = element.getBoundingClientRect();
in a canvas click position it is a little more difficult
Here is a script I did a while ago to draw while dragging the mouse the on the canvas .Maybe you can apply this method
<html>
<head>
<style>
* { margin:0; padding:0; } /* to remove the top and left whitespace */
html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
canvas { display:block; } /* To remove the scrollbars */
</style>
</head>
<body>
<canvas id="canvas" ></canvas>
<script>
////////////////////////////////////////
(function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var elemLeft = canvas.offsetLeft;
var elemTop = canvas.offsetTop;
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
// resize the canvas to fill browser window dynamically
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/**
* Your drawings need to be inside this function otherwise they will be reset when
* you resize the browser window and the canvas goes will be cleared.
*/
drawStuff();
}
resizeCanvas();
function drawStuff() {
// do your drawing stuff here
var img = new Image();
img.src = 'images/3PkBe.gif';
img.onload = function()
{
//var canvas = document.getElementById('canvas');
// create pattern
var ptrn = ctx.createPattern(img, 'repeat'); // Create a pattern with this image, and set it to "repeat".
ctx.fillStyle = ptrn;
ctx.fillRect(0, 0, canvas.width, canvas.height); // context.fillRect(x, y, width, height);
ctx.shadowBlur=20;
//ctx.shadowColor="black";
//ctx.fillStyle="green";
//ctx.fillRect(20,160,100,80);
ctx.strokeStyle = "lightgray";
//var canvasOffset = canvas.offset();
//var offsetX = canvasOffset.left;
//var offsetY = canvasOffset.top;
var mouseIsDown = false;
var lastX = 0;
var lastY = 0;
var elements = [];
makeShip( 30 , 30,120, 120, '#119' , "romea");
makeShip( 30, 160,120, 120, '#393', "fomar");
makeShip( 30, 290,120, 120, '#955', "ojab");
makeShip( 30, 420,120, 120, '#6ff', "eliot");
// Add event listener for `click` events.
canvas.addEventListener('click', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.info(x, y);
elements.forEach(function(element) {
if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.log(element.name);
}
});
}, false);
canvas.addEventListener('mousedown', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.info(x, y);
elements.forEach(function(element) {
if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.info(element.name);
handleMouseDown(element);
}
});
}, false);
canvas.addEventListener('mousemove', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.info(x, y);
elements.forEach(function(element) {
if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.info(element.name);
handleMouseMove(element,x,y);
}
});
}, false);
canvas.addEventListener('mouseup', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
//console.info(x, y);
elements.forEach(function(element) {
//if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.info(element.name + "mouse up evenr=========");
handleMouseUp(element);
//}
});
}, false);
function makeShip(x, y, width, height, colour,ShipName) {
var ship = {
name: ShipName,
colour: colour,
width: width,
height: height,
x: x,
y: y
}
elements.push(ship);
return (ship);
}
function drawShip(ship) {
//ctx.fillStyle = ship.colour;
//ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
//ctx.fillRect(element.x, element.y, element.width, element.height);
}
function drawAllShips() {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < elements.length; i++) {
var ship = elements[i]
//drawShip(ship);
ctx.fillStyle = ship.colour;
ctx.fillRect(ship.x , ship.y, ship.width, ship.height);
// ctx.fillStyle = ship.fill;
// ctx.fill();
// ctx.stroke();
}
}
// Add element.
//elements.push({
//colour: '#05EFFF',
//width: 150,
//height: 100,
//x: 20,
//y: 15
//});
// Render elements.
// elements.forEach(function(element) {
// ctx.fillStyle = element.colour;
// ctx.fillRect(element.x, element.y, element.width, element.height);
// });
drawAllShips();
function handleMouseDown(e) {
mouseX = e.x ;
mouseY = e.y ;
//mouseX = parseInt(e.x - offsetX);
//mouseY = parseInt(e.y - offsetY);
console.log("===========Problem "+mouseX);
// mousedown stuff here
lastX = mouseX;
lastY = mouseY;
mouseIsDown = true;
//alert("mouse Handle");
}
function handleMouseUp(e) {
//mouseX = parseInt(e.clientX - offsetX);
//mouseY = parseInt(e.clientY - offsetY);
ctx.onmousemove = null;
// mouseup stuff here
mouseIsDown = false;
return
}
function handleMouseMove(e,x,y) {
if (mouseIsDown) {
//console.log(' no fuck');
mouseX = e.x ;
mouseY = e.y ;
console.log(e.name+"is truing to drag");
// mousemove stuff here
//for (var i = 0; i < elements.length; i++) {
//if (ctx.isPointInPath(mouseX, mouseY)) {
//console.log('============== no fuck');
var ship =e;// elements[i];
ship.x = x-15;//(mouseX - lastX);
ship.y = y-20;//(mouseY -lastY);
// ship.right = ship.x + ship.width;
// ship.bottom = ship.y + ship.height;
//drawShip(ship);
//}
//}
lastX = mouseX;
lastY = mouseY;
drawAllShips();
}
}
<!-- ctx.mousedown(function (e) { -->
<!-- handleMouseDown(e); -->
<!-- }); -->
<!-- ctx.mousemove(function (e) { -->
<!-- handleMouseMove(e); -->
<!-- }); -->
<!-- ctx.mouseup(function (e) { -->
<!-- handleMouseUp(e); -->
<!-- }); -->
}
}
})();
</script>
</body>
</html>
SOLVED IT!
I got lost in the slew of data around elements and click events, I was trying to figure out the right combination of pageX, clientX, offsetLeft, screenX, etc. However, the final solution is incredibly simple once you know exactly what to do. Here it is:
function handleClick(e){
const rect = e.target.getBoundingClientRect()
const x = e.pageX - rect.left
const y = e.pageY - rect.top
}
This should get you the exact x and y coordinate of your mouse in relation to the element, no matter where you drag and reposition the element.
I'm making a game (called Flow in android and app store) but i have a problem in Js that if i draw a line through cells then I end the function and call 'mousedown' again the lines will connect.
I feel pretty stupid. Can you help me why do they connect even tho I use context.beginPath().Here's the jsfiddle too.
https://jsfiddle.net/vj7f3qb0/
function inGame(map,usedColor) {
let x = 0;
y = 0;
canvas = document.querySelector('canvas')
const position = canvas.getBoundingClientRect();
canvas.addEventListener('mousedown', e => {
x = e.clientX - position.left;
y = e.clientY - position.top;
let b = parseInt(Math.round(x) / 50)
let a = parseInt(Math.round(y) / 50)
if(map[a][b] != 0) {
isValidClick = true
isDrawing = true
// drawLine(map,b,a,c,usedColor)
drawLine(map,a,b,canvas,usedColor)
}
})
}
function drawLine(map,a,b,canvas,usedColor) {
let savedMap = map;
let c = canvas.getContext('2d');
// console.log(c)
const position = canvas.getBoundingClientRect();
canvas.addEventListener("mousemove", e => {
let y = parseInt(Math.round(e.clientX - position.left) / 50)
let x = parseInt(Math.round(e.clientY - position.top) / 50)
if(x != a || y != b){
if(isDrawing) {
c.beginPath()
c.moveTo((b*50)+25,(a*50)+25);
c.lineTo((y*50)+25,(x*50)+25);
c.lineWidth = 5;
c.strokeStyle = "black"
c.stroke()
console.log("x,y")
console.log(x,y)
a = x
b = y
}}
// console.log(x,y)
// console.log(a,b)
canvas.addEventListener("mouseup", e => {
if(isDrawing) {
isDrawing = false;
}
})
})
}
I'm working on a certain layout where I need to draw a hexagon which needs to be clickable. I'm using the Path2D construct and isPointInPath function. I'm constructing an animation where a number of hexagons is created and then each moved to a certain position. After the movement is done, I am attaching onclick event handlers to certain hexagons. However there is weird behaviour.
Some initialized variables
const COLOR_DARK = "#73b6c6";
const COLOR_LIGHT = "#c3dadd";
const COLOR_PRIMARY = "#39a4c9";
const TYPE_PRIMARY = 'primary';
let hexagons = [];
Below is the function which draws the hexagons.
function drawHex(ctx, x, y, hexProps, stroke, color) {
let myPath = new Path2D();
myPath.moveTo(x + hexProps.width*0.5, y);
myPath.lineTo(x, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width*0.5, y + hexProps.height);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x + hexProps.width*0.5, y);
myPath.closePath();
if (stroke){
ctx.strokeStyle = color;
ctx.stroke(myPath);
} else {
ctx.fillStyle = color;
ctx.fill(myPath);
}
return myPath;
}
This function populates the hexagon array
function populateLeftHex(canvasWidth, canvasHeight, hexProps) {
const startX = canvasWidth / 2;
const startY = canvasHeight / 2;
const baseLeft = canvasWidth * 0.05;
for(let i = 0; i < 5; i++){
let hexNumber = (i % 4 == 0)? 2: 1;
for(let j = 0; j < hexNumber; j++){
hexagons.push({
startX: startX,
startY: startY,
endX: baseLeft + (2 * j) + ((i % 2 == 0)? (hexProps.width * j) : (hexProps.width/2)),
endY: ((i + 1) * hexProps.height) - ((i) * hexProps.height * hexProps.facShort) + (i* 2),
stroke: true,
color: ( i % 2 == 0 && j % 2 == 0)? COLOR_DARK : COLOR_LIGHT,
type: TYPE_PRIMARY
});
}
}
}
And here is where Im calling the isPointInPath function.
window.onload = function (){
const c = document.getElementById('canvas');
const canvasWidth = c.width = window.innerWidth,
canvasHeight = c.height = window.innerHeight,
ctx = c.getContext('2d');
window.requestAnimFrame = (function (callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
console.log(canvasWidth);
let hexProps = {
width: canvasWidth * 0.075,
get height () {
return this.width/Math.sqrt(3) + (1.5)*(this.width/Math.sqrt(2)/2);
} ,
facShort: 0.225,
get facLong () {
return 1 - this.facShort;
}
};
populateLeftHex(canvasWidth, canvasHeight, hexProps);
let pct = 0;
const fps = 200;
animate();
function animate () {
setTimeout(function () {
// increment pct towards 100%
pct += .03;
// if we're not done, request another animation frame
if (pct < 1.00) {
requestAnimFrame(animate);
} else { //if pct is no longer less than 1.00, then the movement animation is over.
hexagons.forEach(function (hex) {
if(hex.type === TYPE_PRIMARY) {
console.info(hex.path);
c.onclick = function(e) {
let x = e.clientX - c.offsetLeft,
y = e.clientY - c.offsetTop;
console.info(ctx.isPointInPath(hex.path, (e.clientX - c.offsetLeft), (e.clientY - c.offsetTop) ));
};
}
})
}
ctx.clearRect(0, 0, c.width, c.height);
// draw all hexagons
for ( let i = 0; i < hexagons.length; i++) {
// get reference to next shape
let hex = hexagons[i];
// note: dx/dy are fixed values
// they could be put in the shape object for efficiency
let dx = hex.endX - hex.startX;
let dy = hex.endY - hex.startY;
let nextX = hex.startX + dx * pct;
let nextY = hex.startY + dy * pct;
hex = hexagons[i];
ctx.fillStyle = hex.color;
hex.path = drawHex(ctx, nextX, nextY, hexProps, hex.stroke, hex.color);
}
}, 1000 / fps);
}
Can you help me figure out what I'm doing wrong? Maybe I misunderstood how Path2D works? Thanks in advance.
Had to do a bit of work to build a test page as your example is incomplete, but this is working for me - though my hexagon is concave...
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var hexProps = {width:100, height:100, facShort:-2, facLong:10};
var hexagons = [];
function drawHex(ctx, x, y, hexProps, stroke, color) {
let myPath = new Path2D();
myPath.moveTo(x + hexProps.width*0.5, y);
myPath.lineTo(x, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width*0.5, y + hexProps.height);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x + hexProps.width*0.5, y);
myPath.closePath();
if (stroke){
ctx.strokeStyle = color;
ctx.stroke(myPath);
} else {
ctx.fillStyle = color;
ctx.fill(myPath);
}
return myPath;
}
hexagons.push({type:0, path:drawHex(ctx,100,100,hexProps,false,"#0f0")});
hexagons.forEach(function (hex) {
if(hex.type === 0) {
console.info(hex.path);
myCanvas.onclick = function(e) {
let x = e.clientX - myCanvas.offsetLeft,
y = e.clientY - myCanvas.offsetTop;
console.info(x,y);
console.info(ctx.isPointInPath(hex.path, (e.clientX -
myCanvas.offsetLeft), (e.clientY - myCanvas.offsetTop) ));
};
}
})
<canvas width=500 height=500 id=myCanvas style='border:1px solid red'></canvas>
Test clicks give true and false where expected:
test.htm:48 165 168
test.htm:49 true
test.htm:48 151 336
test.htm:49 false
test.htm:48 124 314
test.htm:49 true
test.htm:48 87 311
test.htm:49 false
I am creating a smudging tool with HTML5 canvas. Now I have to shift the pixel color at the point of mouse pointer to the next position where mouse pointer moves. Is it possible to do with javascript?
<canvas id="canvas"><canvas>
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
var url = 'download.jpg';
var imgObj = new Image();
imgObj.src = url;
imgObj.onload = function(e) {
context.drawImage(imgObj, 0, 0);
}
function findPos(obj) {
var curleft = 0,
curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return {
x: curleft,
y: curtop
};
}
return undefined;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
$('#canvas').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
console.log(x, y);
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
console.log(hex)
});
I am very short on time ATM so code only.
Uses an offscreen canvas brush to get a copy of the background canvas background where the mouse was last frame. Then use a radial gradient to feather the brush using ctx.globalCompositeOperation = "destination-in". Then draw the updated brush at the next mouse position.
The main canvas is use just to display, the canvas being smeared is called background You can put whatever content you want on that canvas (eg image) and it can be any size, and you can zoom, pan, rotate the background though you will have to convert the mouse coordinates to match the background coordinates
Click drag mouse to smear colours.
const ctx = canvas.getContext("2d");
const background = createCanvas(canvas.width,canvas.height);
const brushSize = 64;
const bs = brushSize;
const bsh = bs / 2;
const smudgeAmount = 0.25; // values from 0 none to 1 full
// helpers
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
// simple mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// brush gradient for feather
const grad = ctx.createRadialGradient(bsh,bsh,0,bsh,bsh,bsh);
grad.addColorStop(0,"black");
grad.addColorStop(1,"rgba(0,0,0,0)");
const brush = createCanvas(brushSize)
// creates an offscreen canvas
function createCanvas(w,h = w){
var c = document.createElement("canvas");
c.width = w;
c.height = h;
c.ctx = c.getContext("2d");
return c;
}
// get the brush from source ctx at x,y
function brushFrom(ctx,x,y){
brush.ctx.globalCompositeOperation = "source-over";
brush.ctx.globalAlpha = 1;
brush.ctx.drawImage(ctx.canvas,-(x - bsh),-(y - bsh));
brush.ctx.globalCompositeOperation = "destination-in";
brush.ctx.globalAlpha = 1;
brush.ctx.fillStyle = grad;
brush.ctx.fillRect(0,0,bs,bs);
}
// short cut vars
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
var lastX;
var lastY;
// update background is size changed
function createBackground(){
background.width = w;
background.height = h;
background.ctx.fillStyle = "white";
background.ctx.fillRect(0,0,w,h);
doFor(64,()=>{
background.ctx.fillStyle = `rgb(${randI(255)},${randI(255)},${randI(255)}`;
background.ctx.fillRect(randI(w),randI(h),randI(10,100),randI(10,100));
});
}
// main update function
function update(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
createBackground();
}else{
ctx.clearRect(0,0,w,h);
}
ctx.drawImage(background,0,0);
// if mouse down then do the smudge for all pixels between last mouse and mouse now
if(mouse.button){
brush.ctx.globalAlpha = smudgeAmount;
var dx = mouse.x - lastX;
var dy = mouse.y - lastY;
var dist = Math.sqrt(dx*dx+dy*dy);
for(var i = 0;i < dist; i += 1){
var ni = i / dist;
brushFrom(background.ctx,lastX + dx * ni,lastY + dy * ni);
ni = (i+1) / dist;
background.ctx.drawImage(brush,lastX + dx * ni - bsh,lastY + dy * ni - bsh);
}
}else{
brush.ctx.clearRect(0,0,bs,bs); /// clear brush if not used
}
lastX = mouse.x;
lastY = mouse.y;
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>