I am trying to implement a circle distortion function in Processing / p5js. Here are two examples of what I want to achieve:
https://www.shadertoy.com/view/MsSSDz and
http://jsfiddle.net/hto1s6fy
Both examples implement the same functionality - an original image is distorted into a 2d circle with an inner and outer radius.
I have been trying to translate the code from the jsfiddle example into my Processing / p5js sketch, but I have had limited success.
Here is my code and the resulting image:
let img;
let cx;
let cy;
let innerRadius;
let outerRadius;
let startX;
let startY;
let endX;
let endY;
function preload() {
img = loadImage('image.jpg');
}
function setup() {
createCanvas(img.width, img.height);
image(img, 0, 0);
noLoop();
cx = width / 2;
cy = height / 2;
innerRadius = 0;
outerRadius = 320;
startX = 0;
startY = 0;
endX = img.width;
endY = img.height;
}
function draw() {
let angle = 0;
let step = 1 * atan2(1, outerRadius);
let limit = 2 * PI;
push();
translate(cx, cy);
while (angle < limit) {
push();
rotate(angle);
translate(innerRadius, 0);
rotate(-PI / 2);
let ratio = angle / limit;
let x = startX + ratio * (endX - startX);
image(img, x, startY, 1, (endY - startY), 0, 0, 1, (outerRadius - innerRadius));
pop();
angle += step;
}
pop();
}
Original image:
and result of code
Please is anyone able to tell me where I am going wrong?
I'm not very familiar with processing, but it seems like the people in your examples are using a different library than processing. So even though the function seem to be the same, it could be that there are small differences, which causes a different result.
Related
I am trying to use an image as stroke style, but I have a problem on how to direct how the pattern (arrow image) is placed.
For example I used an arrow as pattern for the strokeStyle. I want the arrow to be pointing forward at the top rectangle, point down at the right side, point backwards at the bottom and then pointing up at the right hand side of the rectangle.
More like the image should follow the shape of the rectangle
Using normal stroke just places the arrow in a straight pattern as shown in the picture.
enter image description here
This is my code
function drawPattern(img, size) {
const canvas = document.getElementById("canvas");
const tempCanvas = document.createElement("canvas");
const tCtx = tempCanvas.getContext("2d");
tempCanvas.width = size;
tempCanvas.height = size;
tCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, size, size);
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
const pat = ctx.createPattern(tempCanvas, "repeat");
ctx.strokeStyle = pat;
ctx.lineWidth = 100;
ctx.strokeRect(0, 0, canvas.width, canvas.height);
}
const img = new Image();
img.src = "http://freundbild.com/arrow.png";
img.onload = function () {
drawPattern(this, 100);
};
You can use a matrix DOMMatrix or DOMMatrixReadOnly to orientate the pattern using the patterns setTransform function.
UPDATE
See code for how.
Pattern created in code, but can be an image. Just create the pattern and pass to the following function.
Note that the second function is the image the pattern was created from. The example uses a square pattern so the second argument is just the size
Note that the pattern is scaled to fit the lineWidth so will not maintain its aspect.
function orientPattern(ctx, image, pattern, dist, lineWidth, p1, p2) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const d = (dx * dx + dy * dy) ** 0.5;
const nx = dx / d;
const ny = dy / d;
const w = image.width;
const h = image.height;
const yScale = h / lineWidth;
const mat = new DOMMatrixReadOnly([
nx, ny,
-ny / yScale, nx / yScale,
p1.x - ((ny * h * 0.5) % h) / yScale - (nx * (dist % w)) ,
p1.y + ((nx * h * 0.5) % h) / yScale - (ny * (dist % w))
]);
pattern.setTransform(mat);
return pattern;
}
Note You need to draw each line segment of the shape you want to draw one at a time. You can not fit it to a rect arc or a path that is not a straight line.
Note Line joins will have holes or overlay (depending on the line cap setting) There is no easy way to overcome this problem without writing a full replacement of the 2D stroke function. That would incur a serious performance hit on rendering this type of path.
const ctx = canvas.getContext("2d");
function createArrowPattern(size, bgCol, col) {
const c = document.createElement("canvas");
c.width = c.height = size;
const ctx = c.getContext("2d");
ctx.fillStyle = bgCol;
ctx.fillRect(0,0, size, size);
ctx.fillStyle = col;
const u = size / 5;
ctx.setTransform(size, 0, 0, size, size / 2, size / 2);
ctx.beginPath();
ctx.lineTo(-0.4, -0.2);
ctx.lineTo( 0.1, -0.2);
ctx.lineTo( 0.1, -0.5);
ctx.lineTo( 0.4, 0);
ctx.lineTo( 0.1, 0.5);
ctx.lineTo( 0.1, 0.2);
ctx.lineTo(-0.4, 0.2);
ctx.fill();
return ctx.createPattern(c, "repeat");
}
function orientPattern(ctx, size, pattern, dist, lineWidth, p1, p2) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const d = (dx * dx + dy * dy) ** 0.5;
const nx = dx / d;
const ny = dy / d;
const yScale = size / lineWidth;
const mat = new DOMMatrixReadOnly([
nx, ny,
-ny / yScale, nx / yScale,
p1.x - ((ny * size * 0.5) % size) / yScale - (nx * (dist % size)) ,
p1.y + ((nx * size * 0.5) % size) / yScale - (ny * (dist % size))
]);
pattern.setTransform(mat);
return pattern;
}
function drawPatternPath(ctx, size, pattern, lineWidth, start, ...points) {
var i = 0;
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
var dist = 0;
var p1 = points[i++]
while (i < points.length) {
const p2 = points[i++];
ctx.strokeStyle = orientPattern(ctx, size, pattern, -start + dist, lineWidth, p1, p2);
ctx.beginPath();
ctx.lineTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
// dist += 10
dist += ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
p1 = p2;
}
}
const P = (x, y) => ({x,y});
const ARROW_SIZE = 64;
const LINE_WIDTH = 22;
const arrow = createArrowPattern(ARROW_SIZE, "white", "red");
var pos = 0;
animate()
function animate() {
ctx.clearRect(0,0,400,300);
drawPatternPath(
ctx, ARROW_SIZE, arrow, LINE_WIDTH, pos,
P(30,30),
P(370,30),
P(370, 200),
P(350, 250),
P(300, 270),
P(30,270),
P(30,30)
);
pos += 1;
requestAnimationFrame(animate);
}
<canvas id="canvas" width ="400" height="300"></canvas>
I consider this approach a hack and that there is a need to map images, patterns, gradients, and text, to strokes (as paths) in the 2D API as at the moment there is no acceptable workaround in both quality and performance.
Alternative option is WebGL which is easily able to draw such patterned paths with excellent speed and exceptional quality however to integrate it with the 2D API is non trivial, and once along that path why bother with the 2D API at all.
I'm building a basic game using plain javascript and I am trying to rotate my object to follow my mouse.
I've tried getting the client's mouse X and Y then subtracting the canvas width and height divided by two. Then taking those values and inputing it into Math.atan2(). However, I feel the issue may be in my transform and rotate. The code bellow is what I've tried.
WIDTH = c.height;
HEIGHT = c.width;
document.onmousemove = function(ve){
let cX = -c.width / 2;
let cY = -c.height / 2;
let x = ve.offsetX;
let y = ve.offsetY;
var rX = cX + x - 8;
var rY = cY + y - 8;
player.angle = Math.atan2(rX, rY) / Math.PI * 180;
}
function update(){
var now = Date.now();
dt = now - lastUpdate;
ctx.clearRect(0, 0, WIDTH, HEIGHT);
ctx.setTransform(1, 0, 0, 1, WIDTH / 2, HEIGHT / 2);
ctx.rotate(player.angle + 10);
drawCircle(player.x, player.y, 20, 0, 180, "red");
tx.setTransform(1, 0, 0, 1, 0, 0);
}
setInterval(update, dt/10000);
The player spins around my mouse in wide circles with no apparent pattern.
Here's a gif showing what's happening.
https://gyazo.com/006c99879ecf219791d059de14d98b74
In order to rotate the object to follow the mouse you need to get the angle between the previous position of the mouse and the actual position of the mouse and use this angle to rotate the object. Also the object is drawn with the tip in the origin of the canvas {x:0,y:0} so you'll need to translate the player to the position of the mouse.
I hope this is what you need.
const ctx = c.getContext("2d")
const HEIGHT = c.height = window.innerHeight;
const WIDTH = c.width = window.innerWidth;
let m = {x:0,y:0}
let prev = {x:0,y:0}
let angle = 0;
c.addEventListener("mousemove",(evt)=>{
ctx.clearRect(-WIDTH, -HEIGHT, 2*WIDTH, 2*HEIGHT);
// the previous position of the mouse
prev.x = m.x;
prev.y = m.y;
//the actual position of the mouse
m = oMousePos(c, evt);
// if the mpuse is moving get the angle between the previoue position and the actual position of the mouse
if(m.x != prev.x && m.y != prev.y){
angle = Math.atan2(m.y-prev.y, m.x-prev.x)
}
ctx.restore();
ctx.save();
ctx.translate(m.x, m.y);
ctx.rotate(angle);
drawPlayer();
})
function drawPlayer(){
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-20,-5);
ctx.lineTo(-20,5);
ctx.lineTo(0,0);
ctx.closePath();
ctx.fill()
}
// a function to detect the mouse position
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
<canvas id="c"></canvas>
As an observation: in your code you have Math.atan2(rX, rY) The first argument has to be y.
I'm trying to create a little circular "equalizer" effect using JavaScript and HTML canvas for a little project I'm working on, and it works great, except one little thing. It's just a series of rectangular bars moving in time to an mp3 - nothing overly fancy, but at the moment all the bars point in one direction (i.e. 0 radians, or 90 degrees).
I want each respective rectangle around the edge of the circle to point directly away from the center point, rather than to the right. I have 360 bars, so naturally, each one should be 1 degree more rotated than the previous.
I thought that doing angle = i*Math.PI/180 would fix that, but it doesn't seem to matter what I do with the rotate function - they always end up pointing in weird and wonderful directions, and being translated a million miles from where they were. And I can't see why. Can anyone see where I'm going wrong?
My frame code, for reference, is as follows:
function frames() {
// Clear the canvas and get the mp3 array
window.webkitRequestAnimationFrame(frames);
musicArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(musicArray);
ctx.clearRect(0, 0, canvas.width, canvas.height);
bars = 360;
for (var i = 0; i < bars; i++) {
// Find the rectangle's position on circle edge
distance = 100;
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * distance + (canvas.width / 2);
var y = Math.sin(angle) * distance + (canvas.height / 2);
barWidth = 5;
barHeight = (musicArray[i] / 4);
// Fill with a blue-green gradient
var grd = ctx.createLinearGradient(x, 0, x + 40, 0);
grd.addColorStop(0, "#00CCFF");
grd.addColorStop(1, "#00FF7F");
ctx.fillStyle = grd;
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.fillRect(x, y, barHeight, barWidth);
}
For clarity I've removed part of your code. I'm using rotate as you intended. Also I'm using barHeight = (Math.random()* 50); instead your (musicArray[i]/4); because I wanted to have something to show.
Also I've changed your bars to 180. It's very probable that you won't have 360 bars but 32 or 64 or 128 or 256 . . . Now you can change the numbers of bare to one of these numbers to see the result.
I'm drawing everything around the origin of the canvas and translating the context in the center.
I hope it helps.
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400;
let ch = canvas.height = 400;
let bars = 180;
let r = 100;
ctx.translate(cw / 2, ch / 2)
for (var i = 0; i < 360; i += (360 / bars)) {
// Find the rectangle's position on circle edge
var angle = i * ((Math.PI * 2) / bars);
//var x = Math.cos(angle)*r+(canvas.width/2);
//var y = Math.sin(angle)*r+(canvas.height/2);
barWidth = 2 * Math.PI * r / bars;
barHeight = (Math.random() * 50);
ctx.fillStyle = "green";
// Rotate the rectangle according to position
// ctx.rotate(i*Math.PI/180); - DOESN'T WORK
// Draw the rectangle
ctx.save();
ctx.rotate(i * Math.PI / 180);
ctx.fillRect(r, -barWidth / 2, barHeight, barWidth);
//ctx.fillRect(r ,0, barHeight, barWidth);
ctx.restore();
}
canvas {
border: 1px solid
}
<canvas id="c"></canvas>
Here is another solution, I'm preserving your initial trigonometry approach.
But instead of rectangles I used lines, I don't think it makes a difference for you, if what you need is bars moving in time to an mp3 all you need to do is change the var v = Math.random() + 1; to a reading from the Amplitude, and those bars will be dancing.
const canvas = document.getElementById("c");
canvas.width = canvas.height = 170;
const ctx = canvas.getContext("2d");
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.lineWidth = 2;
let r = 40;
let bars = 180;
function draw() {
ctx.clearRect(-100, -100, 200, 200)
for (var i = 0; i < 360; i += (360 / bars)) {
var angle = i * ((Math.PI * 2) / bars);
var x = Math.cos(angle) * r;
var y = Math.sin(angle) * r;
ctx.beginPath();
var v = Math.random() + 1;
ctx.moveTo(x, y);
ctx.lineTo(x * v, y * v)
grd = ctx.createLinearGradient(x, y, x*2, y*2);
grd.addColorStop(0, "blue");
grd.addColorStop(1, "red");
ctx.strokeStyle = grd;
ctx.stroke();
}
}
setInterval(draw, 100)
<canvas id="c"></canvas>
I'm trying to make a ball bounce inside a canvas and it doesn't work. The ball is getting stuck against the "walls" and I don't understand why. Anyone know how I can fix this?
var can = document.querySelector("canvas");
var ctx = can.getContext("2d");
var canvasWidth = 500;
var canvasHeight = 400;
var radius = 30;
var pX = 60;
var pY = 50;
function draw() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.beginPath();
ctx.fillStyle = "red";
ctx.arc(pX, pY, radius, 0, Math.PI * 2, false);
ctx.fill();
ctx.closePath();
}
function animate() {
var vX = 3;
var vY = 3;
if (pY >= canvasHeight - radius) {
vY = -vY;
}
if (pX >= canvasWidth - radius) {
vX = -vX;
}
pX += vX;
pY += vY;
draw();
requestAnimationFrame(animate);
}
animate();
Your conditions for collision detection only consider the bottom wall and the right-side wall. You need to add conditions for the top wall and the left-side wall. Something like
if (pY >= canvasHeight - radius || pY <= radius) {
vY = -vY;
}
if (pX >= canvasWidth - radius || pX <= radius) {
vX = -vX;
}
You are seeing the strange behaviour with your collision detection because vX and vY are locally declared and initialized in animate. This means, every time animate is called, it will be re-initialized.
Simply move vX and vY declarations out of the animate function.
EDIT: JsFiddle if you want to see it in action: http://jsfiddle.net/y45fhko7/
I would like to generate a canvas image using gradients in some clever way. I would like the image to looks something like this:
I just can't get my head around it. I need to generate lines in the form and arc - or use gradients with color stops in some clever way. Maybe it would be a lot easier if I converted to HSL and just go through the HUE values?
For example in a rectangle format I could
for (var i = 0; i < h; ++i) {
var ratio = i/h;
var hue = Math.floor(360*ratio);
var sat = 100;
var lum = 50;
line(dc, hslColor(hue,sat,lum), left_margin, top_margin+i, left_margin+w, top_margin+i);
}
Does anybody have any clever tips on how to produce this image using canvas?
This is not perfect (due to drawing steps ...), but it can help you :
http://jsfiddle.net/afkLY/2/
HTML:
<canvas id="colors" width="200" height="200"></canvas>
Javascript:
var canvas = document.getElementById("colors");
var graphics = canvas.getContext("2d");
var CX = canvas.width / 2,
CY = canvas.height/ 2,
sx = CX,
sy = CY;
for(var i = 0; i < 360; i+=0.1){
var rad = i * (2*Math.PI) / 360;
graphics.strokeStyle = "hsla("+i+", 100%, 50%, 1.0)";
graphics.beginPath();
graphics.moveTo(CX, CY);
graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
graphics.stroke();
}
The idea is to draw the disc line by line with a hue value corresponding to the line direction.
You can change the color base rotation by adding a radius angle to rad variable (adding -pi/2 to rad would make the gradient look like your figure).
EDIT:
I made a new demo that generalizes the concept a bit and renders a rainbow polygon. Here is the CodePen.
To get rid of the small voids beteween the colors, I used quads that overflow to the next color part, except for the last one.
Small adjustment to make it have a white center
var canvas = document.getElementById('colorPicker');
var graphics = canvas.getContext("2d");
var CX = canvas.width / 2,
CY = canvas.height / 2,
sx = CX,
sy = CY;
for (var i = 0; i < 360; i += 0.1) {
var rad = i * (2 * Math.PI) / 360;
var grad = graphics.createLinearGradient(CX, CY, CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
grad.addColorStop(0, "white");
grad.addColorStop(0.01, "white");
grad.addColorStop(0.99, "hsla(" + i + ", 100%, 50%, 1.0)");
grad.addColorStop(1, "hsla(" + i + ", 100%, 50%, 1.0)");
graphics.strokeStyle = grad;
graphics.beginPath();
graphics.moveTo(CX, CY);
graphics.lineTo(CX + sx * Math.cos(rad), CY + sy * Math.sin(rad));
graphics.stroke();
}
Here is an alternate approach that takes a slightly more functional approach:
var canvas = document.getElementById("radial"),
ctx = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
center = { x: width/2, y: height/2 },
diameter = Math.min(width, height);
var distanceBetween = function(x1,y1,x2,y2) {
// Get deltas
var deltaX = x2 - x1,
deltaY = y2 - y1;
// Calculate distance from center
return Math.sqrt(deltaX*deltaX+deltaY*deltaY);
}
var angleBetween = function(x1,y1,x2,y2) {
// Get deltas
var deltaX = x2 - x1,
deltaY = y2 - y1;
// Calculate angle
return Math.atan2(deltaY, deltaX);
}
var radiansToDegrees = _.memoize(function(radians) {
// Put in range of [0,2PI)
if (radians < 0) radians += Math.PI * 2;
// convert to degrees
return radians * 180 / Math.PI;
})
// Partial application of center (x,y)
var distanceFromCenter = _.bind(distanceBetween, undefined, center.x, center.y)
var angleFromCenter = _.bind(angleBetween, undefined, center.x, center.y)
// Color formatters
var hslFormatter = function(h,s,l) { return "hsl("+h+","+s+"%,"+l+"%)"; },
fromHue = function(h) { return hslFormatter(h,100,50); };
// (x,y) => color
var getColor = function(x,y) {
// If distance is greater than radius, return black
return (distanceFromCenter(x,y) > diameter/2)
// Return black
? "#000"
// Determine color
: fromHue(radiansToDegrees(angleFromCenter(x,y)));
};
for(var y=0;y<height;y++) {
for(var x=0;x<width;x++) {
ctx.fillStyle = getColor(x,y);
ctx.fillRect( x, y, 1, 1 );
}
}
It uses a function to calculate the color at each pixel – not the most efficient implementation, but perhaps you'll glean something useful from it.
Note it uses underscore for some helper functions like bind() – for partial applications – and memoize.
Codepen for experimentation.