Direct the Image positioning used to as strokeStyle in canvas - javascript

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.

Related

HTML5 Canvas rotate gradient around centre with best fit

I want to make a gradient that covers the whole canvas whatever the angle of it.
So I used a method found on a Stack Overflow post which is finally incorrect. The solution is almost right but, in fact, the canvas is not totally covered by the gradient.
It is this answer: https://stackoverflow.com/a/45628098/5594331
(You have to look at the last point named "Example of best fit.")
In my code example below, the yellow part should not be visible because it should be covered by the black and white gradient. This is mostly the code written in Blindman67's answer with some adjustments to highlight the problem.
I have drawn in green the control points of the gradient. With the right calculations, these should be stretched to the edges of the canvas at any angle.
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function bestFitGradient(angle){
var dist = Math.sqrt(w * w + h * h) / 2; // get the diagonal length
var diagAngle = Math.asin((h / 2) / dist); // get the diagonal angle
// Do the symmetry on the angle (move to first quad
var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
if(a1 > Math.PI){ a1 -= Math.PI }
if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) }
// get angles from center to edges for along and right of gradient
var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
var ang2 = Math.abs(diagAngle - Math.abs(a1));
// get distance from center to horizontal and vertical edges
var dist1 = Math.cos(ang1) * h;
var dist2 = Math.cos(ang2) * w;
// get the max distance
var scale = Math.max(dist2, dist1) / 2;
// get the vector to the start and end of gradient
var dx = Math.cos(angle) * scale;
var dy = Math.sin(angle) * scale;
var x0 = w / 2 + dx;
var y0 = h / 2 + dy;
var x1 = w / 2 - dx;
var y1 = h / 2 - dy;
// create the gradient
const g = ctx.createLinearGradient(x0, y0, x1, y1);
// add colours
g.addColorStop(0, "yellow");
g.addColorStop(0, "white");
g.addColorStop(.5, "black");
g.addColorStop(1, "white");
g.addColorStop(1, "yellow");
return {
g: g,
x0: x0,
y0: y0,
x1: x1,
y1: y1
};
}
function update(timer){
var r = bestFitGradient(timer / 1000);
// draw gradient
ctx.fillStyle = r.g;
ctx.fillRect(0,0,w,h);
// draw points
ctx.lineWidth = 3;
ctx.fillStyle = '#00FF00';
ctx.strokeStyle = '#FF0000';
ctx.beginPath();
ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
border : 2px solid red;
}
<canvas id="canvas" width="300" height="200"></canvas>
In this fiddle there is a function that calculates the distance between a rotated line and a point:
function distanceToPoint(px, py, angle) {
const cx = width / 2;
const cy = height / 2;
return Math.abs((Math.cos(angle) * (px - cx)) - (Math.sin(angle) * (py - cy)));
}
Which is then used to find the maximum distance between the line and the corner points (only two points are considered, because the distances to the other two points are mirrored):
const dist = Math.max(
distanceToPoint(0, 0, angle),
distanceToPoint(0, height, angle)
);
Which can be used to calculate offset points for the end of the gradient:
const ox = Math.cos(angle) * dist;
const oy = Math.sin(angle) * dist;
const gradient = context.createLinearGradient(
width / 2 + ox,
height / 2 + oy,
width / 2 - ox,
height / 2 - oy
)

How to create a rounded hexagon with javascript andCANVAS

I created this codepen to show what I got.
I managed to generate a hexagon avatar with progressbar around it using the awesome open source Hexagon Progress jQuery Plugin from Max Lawrence.
He also helped me to improve his own code a little but I don't want to bother him again.
Maybe someone here can help me to round the corners of this hexagon.
I want it to looks something like this (from the awesome Vikinger Html template) but need to be open source because my software is all open source. I can't use the Vikinger code.
So far I read that I have to stop the line before the end and add a quadratic curve to the next line start but I could not managed to do that.
His code do something like this on line 505:
ctx.moveTo(this.coordBack[0].x + offset, this.coordBack[0].y + offset);
for(var i = 0; i < this.coordBack.length; i++) {
ctx.lineTo(this.coordBack[i].x + offset, this.coordBack[i].y + offset);
}
Unfortunatelly, I am not that good in javascript or math.
Two ways to do this. The easy way, and the long winded, lots of math way.
Easy rounded corners
To create simple rounded polygons you can use ctx.arcTo. It will do all the math for the corners.
To create the polygon the following functions create a point and a path (array of points)
const Point = (x,y) => ({x, y});
function polygon(sides, rad, rot = 0) {
var i = 0, step = Math.PI * 2 / sides, path = [];
while (i < sides) {
path.push(Point(Math.cos(i * step + rot) * rad, Math.sin((i++) * step + rot) * rad));
}
return path;
}
To create a hexagon. Note that the polygon is centered over its local origin 0,0
const hexagon = polygon(6, 100);
To render the rounded polygon you need to work from the line segment centers. The following function will stroke the path with the rounded corners.
function strokeRoundedPath(cx, cy, path, radius, style, width) {
ctx.setTransform(1,0,0,1,cx,cy);
var i = 0;
const len = path.length
var p1 = path[i++], p2 = path[i];
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.strokeStyle = style;
ctx.beginPath();
ctx.lineTo((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
while (i <= len) {
p1 = p2;
p2 = path[(++i) % len];
ctx.arcTo(p1.x, p1.y, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2, radius);
}
ctx.closePath();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0);
}
strokeRoundedPath(200, 200, hexagon, 20, "#000", 18);
Progress bar
Creating a progress bar is not as simple as the starting point can not be on a rounded corner, and moving over the rounded corners will need a lot of math to get the correct coordinates. This will negate the point of using easy arcToand need us to write the equivalent in JS (Way to slack for that today)
Using line dash for progress
There is however a hack that uses the line dash to create the effect you may be happy with. The snippet demonstrates this
const barWidth = 10;
const cornerRadius = barWidth * 2 + 8;
const polyRadius = 100;
const inset = 1;
const barRadius = polyRadius - barWidth * inset;
var progress = 0.0;
const approxLineLen = barRadius * Math.PI * 2;
const hexBar = polygon(6, barRadius);
const hexPoly = polygon(6, polyRadius);
const hexPolyInner = polygon(6, polyRadius - barWidth * 2 * inset);
const ctx = canvas.getContext("2d");
ctx.setLineDash([approxLineLen]);
loop()
function point(x,y) { return {x, y} }
function polygon(sides, radius, rot = 0) {
var i = 0;
const step = Math.PI * 2 / sides, path = [];
while (i < sides) {
path.push(point(Math.cos(i * step + rot) * radius, Math.sin((i++) * step + rot) * radius));
}
return path;
}
function roundedPath(path, radius) {
var i = 0, p1 = path[i++], p2 = path[i];
const len = path.length
ctx.moveTo((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
while (i <= len) {
p1 = p2;
p2 = path[(++i) % len];
ctx.arcTo(p1.x, p1.y, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2, radius);
}
}
function strokeRoundedPath(cx, cy, path, radius, style, width) {
ctx.setTransform(1,0,0,1,cx,cy);
ctx.lineWidth = width;
ctx.lineCap = "round";
ctx.strokeStyle = style;
ctx.beginPath();
roundedPath(path, radius);
ctx.closePath();
ctx.stroke();
}
function fillRoundedPath(cx, cy, path, radius, style) {
ctx.setTransform(1,0,0,1,cx,cy);
ctx.fillStyle = style;
ctx.beginPath();
roundedPath(path, radius);
ctx.fill();
}
function loop() {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
fillRoundedPath(polyRadius, polyRadius, hexPoly, cornerRadius, "#000");
fillRoundedPath(polyRadius, polyRadius, hexPolyInner, cornerRadius - barWidth * inset * 2, "#F80");
ctx.lineDashOffset = approxLineLen - (progress % 1) * approxLineLen;
strokeRoundedPath(polyRadius, polyRadius, hexBar, cornerRadius - barWidth * inset, "#09C", barWidth);
progress += 0.005;
requestAnimationFrame(loop);
}
<canvas id="canvas" width = "210" height="210"></canvas>

Rotating Rectangles Around Circle Perimeter on Canvas

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>

angled direction sine wave

I have been able to draw the sin wave in horizontal direction as shows the image(image link: https://i.stack.imgur.com/RTpDY.png) and in the vertical direction.
now I need to draw it in an angled 45° direction
could any one help me pleese!
the script code:
var c =document.getElementById("c");
var ctx=c.getContext('2d');
var x=0,y=250,vx=0.05,vy=0.05,a=1;
for(var i=0; i<501;i++){
x += a;
y = Math.floor(500 * (0.5 - 0.15 * Math.sin(vy)));
vy += vx;
// this.ctx.clearRect(0, 0, 500,500);
this.ctx.beginPath();
this.ctx.arc(x, y, 2, 0, Math.PI * 2, true);
this.ctx.closePath();
this.ctx.fillStyle = 'red';
this.ctx.fill();
console.log("x:"+x+"y:"+y+"vy:"+vy);
}
Draw a sin wave along a line
The following will draw a sin wave aligned to a line. The line can be in any direction.
The standard settings
The wave length will be in pixels. For a sin wave to make a complete cycle you need to rotate its input angle by Math.PI * 2 so you will need to convert it to value matching pixel wavelength.
const waveLen = 400; // pixels
The phase of the sin wave is at what part of the wave it starts at, as the wave length is in pixels, the phase is also in pixels, and represents the distance along the wave that denotes the starting angle.
const phase = 200; // mid way
The amplitude of the wave is how far above and below the center line the wave max and min points are. This is again in pixels
const amplitude = 100;
A wave also has an offset, though in this case not really important I will added it as well. In pixels as well
const offset = 0;
The line that marks the center of the wave has a start and end coordinate
const x1 = 20;
const y1 = 20;
const x2 = 400;
const y2 = 400;
And some context settings
const lineWidth = 3;
const lineCap = "round";
const lineJoin = "round";
const strokeStyle = "blue";
Example code
And so to the code that does the rendering, I have expanded the code with comments so you can read what is going on. Below that is a more useable version.
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
window.addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
y2 = x2 = innerWidth; // at 45 deg
drawSinWave();
})
const waveLen = 120; // pixels
const phase = 50; // mid way
const amplitude = 25;
const offset = 0;
const x1 = 20;
const y1 = 20;
var x2 = 400; // as vars to let it change to fit resize
var y2 = 400;
function drawSinWave() {
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.strokeStyle = "blue";
// get the vector form of the line
const vx = x2 - x1;
const vy = y2 - y1;
// Get the length of the line in pixels
const dist = Math.sqrt(vx * vx + vy * vy);
// Make the vector one pixel long to move along the line
const px = vx / dist;
const py = vy / dist;
// We also need a vector to move out from the line (at 90 deg to the ine)
// So rotate the pixel vector 90deg CW
const ax = -py; // a for amplitude vector
const ay = px;
// Begin the path
ctx.beginPath();
// Now loop along every pixel in the line
// We go past the end a bit as floating point errors can cause it to end
// a pixels too early
for (var i = 0; i <= dist + 0.5; i++) {
// fix i if past end
if (i > dist) {
i = dist
} // Carefull dont mess with this ot it will block the page
// Use the distance to get the current angle of the wave
// based on the wave length and phase
const ang = ((i + phase) / waveLen) * Math.PI * 2;
// and at this position get sin
const val = Math.sin(ang);
// Scale to match the amplitude and move to offset
// as the distance from the center of the line
const amp = val * amplitude + offset;
// Get line ceneter at distance i using the pixel vector
var x = x1 + px * i;
var y = y1 + py * i;
// Use the amp vector to move away from the line at 90 degree
x += ax * amp;
y += ay * amp;
// Now add the point
ctx.lineTo(x, y);
}
ctx.stroke();
}
drawSinWave();
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id=canvas width=4 00 height=4 00></canvas>
As a more useable function with a few shortcuts
const ctx = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
window.addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight;
waveExample.y2 = waveExample.x2 = innerWidth; // at 45 deg
drawSinWave(waveExample);
})
const waveExample = {
waveLen: 100, // pixels
phase: 50, // mid way
amplitude: 35,
offset: 0,
x1: 20,
y1: 20,
x2: 400, // as vars to let it change to fit resize
y2: 400,
lineWidth : 5,
lineCap : "round",
lineJoin : "round",
strokeStyle : "Red",
}
function drawSinWave(wave) {
ctx.lineWidth = wave.lineWidth;
ctx.lineCap = wave.lineCap;
ctx.lineJoin = wave.lineJoin;
ctx.strokeStyle = wave.strokeStyle;
var vx = wave.x2 - wave.x1;
var vy = wave.y2 - wave.y1;
const dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
ctx.beginPath();
for (var i = 0; i <= dist + 0.5; i++) {
if (i > dist) { i = dist }
const pos = Math.sin(((i + wave.phase) / wave.waveLen) * Math.PI * 2) * wave.amplitude + wave.offset;
ctx.lineTo(
wave.x1 + vx * i - vy * pos,
wave.y1 + vy * i + vx * pos
);
}
ctx.stroke();
}
drawSinWave(waveExample);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id=canvas width=4 00 height=4 00></canvas>
The easiest solution is rotating the canvas:
ctx.rotate(45*Math.PI/180);
Although I'm presuming you need the canvas orientation fixed and you need to mathematically alter the way you draw? In which case here's a whole bunch of math about how to plot sine waves at a rotated counterclockwise:
http://mathman.biz/html/rotatingsine.html

How can I generate a rainbow circle using HTML5 canvas?

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.

Categories