Drawing easeljs radial gradient strokes - javascript

I'm trying to implement some more fancy canvas drawing, such as something that looks more like a brush stroke. I ran into and awesome resource (http://perfectionkills.com/exploring-canvas-drawing-techniques/), but I'm having some troubles converting it to syntax that works with easeljs.
Original Example: http://codepen.io/kangax/pen/FdlHC
My Attempt: http://codepen.io/mcfarljw/pen/Jifzk
var currentPoint = { x: event.stageX, y: event.stageY };
var dist = distanceBetween(lastPoint, currentPoint);
var angle = angleBetween(lastPoint, currentPoint);
drawingCanvas.graphics.setStrokeStyle(5, 'round', 'round');
for (var i = 0; i < dist; i += 5) {
x = lastPoint.x + (Math.sin(angle) * i);
y = lastPoint.y + (Math.cos(angle) * i);
drawingCanvas.graphics.beginRadialGradientStroke(["#F00","#00F"], [0, 1], x, y, 5, x, y, 10)
}
lastPoint = currentPoint;
stage.update();
It's not producing any errors, but it's also not drawing anything on the canvas. Any pointers on what I'm doing wrong? Thanks!

Your version is almost complete, expect that you missed the most important part of actually drawing the (current part of the) line:
drawingCanvas.graphics.moveTo(lastPoint.x, lastPoint.y);
drawingCanvas.graphics.lineTo(currentPoint.x, currentPoint.y);
If updated your code-pen here: http://codepen.io/anon/pen/fCcEJ

Related

Animating a "Wobbly Canvas" like in Discord's Login page?

For reference, I'm talking about the dark-gray space in the upper left of Discord's Login Page. For anyone who can't access that link, here's a screenshot:
It has a number of effects that are really cool, the dots and (darker shadows) move with the mouse, but I'm more interested in the "wobbly edge" effect, and to a lesser extent the "fast wobble/scale in" on page load (scaling in the canvas on load would give a similar, if not "cheaper" effect).
Unfortunately, I can't produce much in the way of a MCVE, because I'm not really sure where to start. I tried digging through Discord's assets, but I'm not familiar enough to Webpack to be able to determine what's going on.
Everything I've been able to dig up on "animated wave/wobble" is CSS powered SVG or clip-path borders, I'd like to produce something a bit more organic.
Very interesting problem. I've scaled the blob down so it is visible in the preview below.
Here is a codepen as well at a larger size.
const SCALE = 0.25;
const TWO_PI = Math.PI * 2;
const HALF_PI = Math.PI / 2;
const canvas = document.createElement("canvas");
const c = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
class Blob {
constructor() {
this.wobbleIncrement = 0;
// use this to change the size of the blob
this.radius = 500;
// think of this as detail level
// number of conections in the `bezierSkin`
this.segments = 12;
this.step = HALF_PI / this.segments;
this.anchors = [];
this.radii = [];
this.thetaOff = [];
const bumpRadius = 100;
const halfBumpRadius = bumpRadius / 2;
for (let i = 0; i < this.segments + 2; i++) {
this.anchors.push(0, 0);
this.radii.push(Math.random() * bumpRadius - halfBumpRadius);
this.thetaOff.push(Math.random() * TWO_PI);
}
this.theta = 0;
this.thetaRamp = 0;
this.thetaRampDest = 12;
this.rampDamp = 25;
}
update() {
this.thetaRamp += (this.thetaRampDest - this.thetaRamp) / this.rampDamp;
this.theta += 0.03;
this.anchors = [0, this.radius];
for (let i = 0; i <= this.segments + 2; i++) {
const sine = Math.sin(this.thetaOff[i] + this.theta + this.thetaRamp);
const rad = this.radius + this.radii[i] * sine;
const theta = this.step * i;
const x = rad * Math.sin(theta);
const y = rad * Math.cos(theta);
this.anchors.push(x, y);
}
c.save();
c.translate(-10, -10);
c.scale(SCALE, SCALE);
c.fillStyle = "blue";
c.beginPath();
c.moveTo(0, 0);
bezierSkin(this.anchors, false);
c.lineTo(0, 0);
c.fill();
c.restore();
}
}
const blob = new Blob();
function loop() {
c.clearRect(0, 0, canvas.width, canvas.height);
blob.update();
window.requestAnimationFrame(loop);
}
loop();
// array of xy coords, closed boolean
function bezierSkin(bez, closed = true) {
const avg = calcAvgs(bez);
const leng = bez.length;
if (closed) {
c.moveTo(avg[0], avg[1]);
for (let i = 2; i < leng; i += 2) {
let n = i + 1;
c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
}
c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);
} else {
c.moveTo(bez[0], bez[1]);
c.lineTo(avg[0], avg[1]);
for (let i = 2; i < leng - 2; i += 2) {
let n = i + 1;
c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
}
c.lineTo(bez[leng - 2], bez[leng - 1]);
}
}
// create anchor points by averaging the control points
function calcAvgs(p) {
const avg = [];
const leng = p.length;
let prev;
for (let i = 2; i < leng; i++) {
prev = i - 2;
avg.push((p[prev] + p[i]) / 2);
}
// close
avg.push((p[0] + p[leng - 2]) / 2, (p[1] + p[leng - 1]) / 2);
return avg;
}
There are lots of things going on here. In order to create this effect you need a good working knowledge of how quadratic bezier curves are defined. Once you have that, there is an old trick that I've used many many times over the years. To generate smooth linked quadratic bezier curves, define a list of points and calculate their averages. Then use the points as control points and the new averaged points as anchor points. See the bezierSkin and calcAvgs functions.
With the ability to draw smooth bezier curves, the rest is about positioning the points in an arc and then animating them. For this we use a little math:
x = radius * sin(theta)
y = radius * cos(theta)
That converts polar to cartesian coordinates. Where theta is the angle on the circumference of a circle [0 - 2pi].
As for the animation, there is a good deal more going on here - I'll see if I have some more time this weekend to update the answer with more details and info, but hopefully this will be helpful.
The animation runs on a canvas and it is a simple bezier curve animation.
For organic feel, you should look at perlin noise, that was introduced when developing original Tron video FX.
You can find a good guide to understand perlin noise here.
In the example I've used https://github.com/josephg/noisejs
var c = $('canvas').get(0).getContext('2d');
var simplex = new SimplexNoise();
var t = 0;
function init() {
window.requestAnimationFrame(draw);
}
function draw() {
c.clearRect(0, 0, 600, 300);
c.strokeStyle="blue";
c.moveTo(100,100);
c.lineTo(300,100);
c.stroke();
// Draw a Bézier curve by using the same line cooridinates.
c.beginPath();
c.lineWidth="3";
c.strokeStyle="black";
c.moveTo(100,100);
c.bezierCurveTo((simplex.noise2D(t,t)+1)*200,(simplex.noise2D(t,t)+1)*200,(simplex.noise2D(t,t)+1)*200,0,300,100);
c.stroke();
// draw reference points
c.fillRect(100-5,100-5,10,10);
c.fillRect(200-5,200-5,10,10);
c.fillRect(200-5,0-5,10,10);
c.fillRect(300-5,100-5,10,10);
t+=0.001;
window.requestAnimationFrame(draw);
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.js"></script>
<canvas width="600" height="300"></canvas>
Note: further investigation on Discord source code, I've pointed out that's is using https://www.npm.red/~epistemex libraries. Epistemex NPM packages are still online, while GitHub repos and profile does not exists anymore.
Note 2: Another approach could be relying on physics libraries like this demo, but it can be an overkill, if you just need a single effect.

Advanced image skewing in JavaScript, fill polygon with image [duplicate]

I have an image which is a background containing a boxed area like this:
I know the exact positions of the corners of that shape, and I'd like to place another image within it. (So it appears to be inside the box).
I'm aware of the drawImage method for HTML5 canvas, but it seems to only support x, y, width, height parameters rather than exact coordinates. How might I draw an image onto a canvas at a specific set of coordinates, and ideally have the browser itself handle stretching the image.
Quadrilateral transform
One way to go about this is to use Quadrilateral transforms. They are different than 3D transforms and would allow you to draw to a canvas in case you want to export the result.
The example shown here is simplified and uses basic sub-divison and "cheats" on the rendering itself - that is, it draws in a small square instead of the shape of the sub-divided cell but because of the small size and the overlap we can get away with it in many non-extreme cases.
The proper way would be to split the shape into two triangles, then scan pixel wise in the destination bitmap, map the point from destination triangle to source triangle. If the position value was fractional you would use that to determine pixel interpolation (f.ex. bi-linear 2x2 or bi-cubic 4x4).
I do not intend to cover all this in this answer as it would quickly become out of scope for the SO format, but the method would probably be suitable in this case unless you need to animate it (it is not performant enough for that if you want high resolution).
Method
Lets start with an initial quadrilateral shape:
The first step is to interpolate the Y-positions on each bar C1-C4 and C2-C3. We're gonna need current position as well as next position. We'll use linear interpolation ("lerp") for this using a normalized value for t:
y1current = lerp( C1, C4, y / height)
y2current = lerp( C2, C3, y / height)
y1next = lerp(C1, C4, (y + step) / height)
y2next = lerp(C2, C3, (y + step) / height)
This gives us a new line between and along the outer vertical bars.
Next we need the X positions on that line, both current and next. This will give us four positions we will fill with current pixel, either as-is or interpolate it (not shown here):
p1 = lerp(y1current, y2current, x / width)
p2 = lerp(y1current, y2current, (x + step) / width)
p3 = lerp(y1next, y2next, (x + step) / width)
p4 = lerp(y1next, y2next, x / width)
x and y will be the position in the source image using integer values.
We can use this setup inside a loop that will iterate over each pixel in the source bitmap.
Demo
The demo can be found at the bottom of the answer. Move the circular handles around to transform and play with the step value to see its impact on performance and result.
The demo will have moire and other artifacts, but as mentioned earlier that would be a topic for another day.
Snapshot from demo:
Alternative methods
You can also use WebGL or Three.js to setup a 3D environment and render to canvas. Here is a link to the latter solution:
Three.js
and an example of how to use texture mapped surface:
Three.js texturing (instead of defining a cube, just define one place/face).
Using this approach will enable you to export the result to a canvas or an image as well, but for performance a GPU is required on the client.
If you don't need to export or manipulate the result I would suggest to use simple CSS 3D transform as shown in the other answers.
/* Quadrilateral Transform - (c) Ken Nilsen, CC3.0-Attr */
var img = new Image(); img.onload = go;
img.src = "https://i.imgur.com/EWoZkZm.jpg";
function go() {
var me = this,
stepEl = document.querySelector("input"),
stepTxt = document.querySelector("span"),
c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
corners = [
{x: 100, y: 20}, // ul
{x: 520, y: 20}, // ur
{x: 520, y: 380}, // br
{x: 100, y: 380} // bl
],
radius = 10, cPoint, timer, // for mouse handling
step = 4; // resolution
update();
// render image to quad using current settings
function render() {
var p1, p2, p3, p4, y1c, y2c, y1n, y2n,
w = img.width - 1, // -1 to give room for the "next" points
h = img.height - 1;
ctx.clearRect(0, 0, c.width, c.height);
for(y = 0; y < h; y += step) {
for(x = 0; x < w; x += step) {
y1c = lerp(corners[0], corners[3], y / h);
y2c = lerp(corners[1], corners[2], y / h);
y1n = lerp(corners[0], corners[3], (y + step) / h);
y2n = lerp(corners[1], corners[2], (y + step) / h);
// corners of the new sub-divided cell p1 (ul) -> p2 (ur) -> p3 (br) -> p4 (bl)
p1 = lerp(y1c, y2c, x / w);
p2 = lerp(y1c, y2c, (x + step) / w);
p3 = lerp(y1n, y2n, (x + step) / w);
p4 = lerp(y1n, y2n, x / w);
ctx.drawImage(img, x, y, step, step, p1.x, p1.y, // get most coverage for w/h:
Math.ceil(Math.max(step, Math.abs(p2.x - p1.x), Math.abs(p4.x - p3.x))) + 1,
Math.ceil(Math.max(step, Math.abs(p1.y - p4.y), Math.abs(p2.y - p3.y))) + 1)
}
}
}
function lerp(p1, p2, t) {
return {
x: p1.x + (p2.x - p1.x) * t,
y: p1.y + (p2.y - p1.y) * t}
}
/* Stuff for demo: -----------------*/
function drawCorners() {
ctx.strokeStyle = "#09f";
ctx.lineWidth = 2;
ctx.beginPath();
// border
for(var i = 0, p; p = corners[i++];) ctx[i ? "lineTo" : "moveTo"](p.x, p.y);
ctx.closePath();
// circular handles
for(i = 0; p = corners[i++];) {
ctx.moveTo(p.x + radius, p.y);
ctx.arc(p.x, p.y, radius, 0, 6.28);
}
ctx.stroke()
}
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
function inCircle(p, pos) {
var dx = pos.x - p.x,
dy = pos.y - p.y;
return dx*dx + dy*dy <= radius * radius
}
// handle mouse
c.onmousedown = function(e) {
var pos = getXY(e);
for(var i = 0, p; p = corners[i++];) {if (inCircle(p, pos)) {cPoint = p; break}}
}
window.onmousemove = function(e) {
if (cPoint) {
var pos = getXY(e);
cPoint.x = pos.x; cPoint.y = pos.y;
cancelAnimationFrame(timer);
timer = requestAnimationFrame(update.bind(me))
}
}
window.onmouseup = function() {cPoint = null}
stepEl.oninput = function() {
stepTxt.innerHTML = (step = Math.pow(2, +this.value));
update();
}
function update() {render(); drawCorners()}
}
body {margin:20px;font:16px sans-serif}
canvas {border:1px solid #000;margin-top:10px}
<label>Step: <input type=range min=0 max=5 value=2></label><span>4</span><br>
<canvas width=620 height=400></canvas>
You can use CSS Transforms to make your image look like that box. For example:
img {
margin: 50px;
transform: perspective(500px) rotateY(20deg) rotateX(20deg);
}
<img src="https://via.placeholder.com/400x200">
Read more about CSS Transforms on MDN.
This solution relies on the browser performing the compositing. You put the image that you want warped in a separate element, overlaying the background using position: absolute.
Then use CSS transform property to apply any perspective transform to the overlay element.
To find the transform matrix you can use the answer from: How to match 3D perspective of real photo and object in CSS3 3D transforms

Rotating Square in JavaScript (using p5 & triganometry)

I have created this code
var points = [
[100, 100],
[100, -100],
[-100, -100],
[-100, 100]
];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(255);
translate(200, 200);
fill(0);
beginShape();
for (var x = 0; x < points.length; x++) {
points[x] = rotatePoint(0, 0, points[x][0], points[x][1], 1);
vertex(points[x][0], points[x][1]);
console.log("vertex: "+String(points[x][0])+" "+String(points[x][1]));
}
endShape(CLOSE);
}
function getVectDist(p1X, p1Y, p2X, p2Y) {
var deltaX = p1X-p2X;
var deltaY= p1Y-p2Y;
var vect=[deltaX, deltaY];
return vect;
}
//Function to rotate a point around the origin or first point
function rotatePoint(originX, originY, pointX, pointY, angle) {
var dist = getVectDist(originX, originY, pointX, pointY);
//calculating the hypotenuse of dist
var hyp = Math.sqrt((dist[0]*dist[0])+(dist[1]*dist[1]));
console.log("hypotenuse"+String(hyp));
//calculating coords
var y = Math.cos(angle) * hyp;
var x = Math.sin(angle) * hyp;
return [x, y];
}
to try and create a rotating square. However, it just produces a weird projectile-like moving diagonal line that moves at 45 degrees before turning into a single pixel in the centre of the screen. Does anyone know why?
thanks.
I made your code into a codepen, you can check it out here: https://codepen.io/Awesomennjawarrior/pen/yXVbJZ?editors=0010
I used p5 vectors, you may want to check the p5 reference if you don't know what they are. The codepen is fully functional, with minor changes to your code. Also, in case you didn't already know, there is a rotate function in p5 that could have produced the same results with much less trouble. ;)

Calculate x, y coordinates of rotated line segments to draw on a Canvas

This is less an HTML, CANVAS question and more of a general math question. I posted it here because it's prototyped using CANVAS and is still kind of a general programming question that I thought someone could answer. Here is the basic idea: I want to draw a line 10 pixels thick, but I don't want to use the standard lineTo and set a stroke width. I want to actually draw the border of the line using beginPath and lineTo. The reason being is this is actually for AS3 project and using this method allows us to have a line stroke and fill. So rotating the canvas and things of that nature are out of the question. I just need to figure out how to calculate the proper x, y coordinates for the line.
In the code below is the coordinates for the top of a line. I basically want to take this coordinates, add 10 to the y axis for each one and that will give me the return coordinates for the bottom of the line. Of course, each segment of the line is rotated, so calculating the coordinates for the bottom of the line has proved tricky. I'm hoping someone can help.
Once you run the example code, the issue should be obvious. The line isn't drawn properly. For relatively mild rotations of line segments it seems to work, but as the angle of rotation gets more severe the x, y coordinates are calculated incorrectly.
<!doctype html>
<html>
<body>
<canvas id="canvas" width="800" height="600">
</canvas>
<script type="text/javascript">
var coords = [
{x:78,y:183},
{x:130,y:183},
{x:237,y:212},
{x:450,y:213},
{x:517,y:25},
{x:664,y:212},
{x:716,y:212}
];
var coordsBck = [];
for( i = 0; i < coords.length; i++ ) {
var c1, c2, r;
c1 = coords[i];
if( i < coords.length - 1 ) {
c2 = coords[i + 1];
r = Math.atan2((c2.y - c1.y),(c2.x - c1.x));
console.log( c1.x, c1.y, c2.x, c2.y, ( r * 180/Math.PI ));
}
else
{
r = 00;
}
var d = r * 180/Math.PI;
var cos = Math.cos( r );
var sin = Math.sin( r );
var x = cos * 0 - sin * 10;
var y = sin * 0 + cos * 10;
coordsBck.push({x: c1.x + x, y: c1.y + y});
}
while(coordsBck.length > 0 )
{
coords.push( coordsBck.pop() );
}
var ctx = document.getElementById("canvas").getContext("2d");
ctx.beginPath();
for( i = 0; i < coords.length; i++ ) {
var line = coords[i];
console.log( i, line.x, line.y );
if( i == 0 )
{
ctx.moveTo( line.x, line.y );
}
else
{
ctx.lineTo( line.x, line.y );
}
}
ctx.fill();
function t(o) {
return "x: " + o.x + ", y: " + o.y;
}
</script>
</body>
</html>
If you don't need end caps. http://jsfiddle.net/xA6kB/1/
<doctype !html>
<html>
<body>
<canvas id="canvas" width="800" height="600">
</canvas>
<script type="text/javascript">
var points =
[
{x: 78, y: 183},
{x: 130, y: 183},
{x: 237, y: 212},
{x: 450, y: 213},
{x: 517, y: 25},
{x: 664, y: 212},
{x: 716, y: 212}
];
var quads = [];
var lineThickness = 10;
// Remove the -1 to create a loop
for (var i = 0; i < points.length - 1; ++i)
{
var point = points[i];
var nextPoint = points[(i + 1) % points.length];
// Right hand normal with x positive to the right and y positive down
var normal = {x: -(nextPoint.y - point.y), y: nextPoint.x - point.x};
// Normalize normal
var normalLength = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= normalLength;
normal.y /= normalLength;
// A quad has 4 points
quads.push({x: point.x - lineThickness / 2 * normal.x, y: point.y - lineThickness / 2 * normal.y});
quads.push({x: nextPoint.x - lineThickness / 2 * normal.x, y: nextPoint.y - lineThickness / 2 * normal.y});
quads.push({x: nextPoint.x + lineThickness / 2 * normal.x, y: nextPoint.y + lineThickness / 2 * normal.y});
quads.push({x: point.x + lineThickness / 2 * normal.x, y: point.y + lineThickness / 2 * normal.y});
}
var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
for(var i = 0; i < quads.length; i += 4)
{
context.moveTo(quads[i].x, quads[i].y);
context.lineTo(quads[i + 1].x, quads[i + 1].y);
context.lineTo(quads[i + 2].x, quads[i + 2].y);
context.lineTo(quads[i + 3].x, quads[i + 3].y);
}
context.fill();
</script>
</body>
</html>
When i have such issues, i usually compute normalized vectors, and 'play' with them.
Say you draw a line from A to B, compute AB vector (ABx=Bx-Ax ; ABy=By-Ay) then i normalize it (...) to get ABN.
Then i compute ABNR, the 90 degree rotation of ABN ( ABNR.x = -ABN.y ; ABNR.y = ABN.x )
Then in your example, say A' and A'' are the points surrounding A, we have the simple A'=A+5*ABNR and A''= A-5*ABNR , and as well B'=B+5*ABNR and B''=B-5*ABNR.
The rectangle you want to draw is the A'A''B''B' rectangle.
There must be much more optimized way to do this (after all, one can draw a line with only additions), this one is simple and works, it depends on your speed rquirements. You may also optimze the code once you have your formulas working.
I ended up sorting this out after Vincent and Sirisian's answers gave me some ideas. I really appreciate the input guys! Basically, both of those answers made me realized I should be treating the segments like rectangles and that I needed some additional coordinates. I put together a jsfiddle if anyone was in interested.
http://jsfiddle.net/WesleyJohnson/sAaM9/1/

How can i draw polygons on an HTML5 canvas through a JavaScript function

i want the user to be able to click somewhere on the canvas and the polygon will appear on it
<DIV id="canvasarea" class="rounded">
<CANVAS id="canvas" width="800px" height="800px"></CANVAS>
</DIV>
Use a javascript library, I'd try processingjs first - keep in mind IE has to be tricked into supporting canvas.
I didn't see a sample that matched your request exactly but these two should give you a good starting point
http://processingjs.org/learning/basic/shapeprimitives
same-domain-as-above/learning/topic/continuouslines
there is also a great primer on canvas here - google "dive into html5 canvas"
Here is a function I included in an object using mootools library. You could implement it in plain javascript too. ctx is equal to canvas.getContext('2d') object and n var defines the number of sides for the polygon we want... I hope you get the idea, the solution is requires only basic math.
polygonPath: function(ctx, n) {
var eq = 360 / n;
var radius = 50;
this.points = {xy: []};
ctx.beginPath();
ctx.moveTo(50,0);
for (var i = 0 ; i <= n; i++){
var deg = i * eq;
var rad = this.radConst * deg;
var x1 = radius * Math.cos(rad);
var y1 = radius * Math.sin(rad);
console.log('x: ' + x1 + ', y: ' + y1 + ', deg: ' + deg);
ctx.lineTo(x1,y1);
this.points.xy.push(x1 + ',' + y1 + ',' + rad);
}
ctx.stroke();
ctx.closePath();
/* Pentagon:
point 1 : 50,0
point 2: 15.45, 47.55
point 3: -40.45, 29.38
point 4: -40.45, -29.38
point 5: 15.45, -47.55
point 6 : 50, -1.22e-14*/
},
KineticJS is a great way to get started quickly. Here's an example that shows you how to draw a polygon on the canvas:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-polygon-tutorial/
Detect your click and then run the logic provided in the example.
From here: http://www.authorcode.com/draw-and-fill-a-polygon-and-triangle-in-html5/
You can use the lineTo() method:
var objctx = canvas.getContext('2d');
objctx.beginPath();
objctx.moveTo(75, 50);
objctx.lineTo(175, 50);
objctx.lineTo(200, 75);
objctx.lineTo(175, 100);
objctx.lineTo(75, 100);
objctx.lineTo(50, 75);
objctx.closePath();
objctx.fillStyle = "rgb(200,0,0)";
objctx.fill();
If you not want to fill the polygon use the stroke() method in the place of fill()
Thanks.

Categories

Resources