Basically, I am trying to make an interactive background where wherever the mouse hovers or goes to, the lines in this background flows or follows the mouse in that certain direction. Is there anyway I could do this by using HTML/CSS/JavaScript? Here is the code. CSS is on the top, JS on the bottom.
//CSS
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
//JS
// Init Context
let c = document.createElement('canvas').getContext('2d')
let postctx = document.body.appendChild(document.createElement('canvas')).getContext('2d')
let canvas = c.canvas
let vertices = []
// Effect Properties
let vertexCount = 7000
let vertexSize = 2
let oceanWidth = 204
let oceanHeight = -80
let gridSize = 32;
let waveSize = 16;
let perspective = 500;
// Common variables
let depth = (vertexCount / oceanWidth * gridSize)
let frame = 0
let { sin, cos, tan, PI } = Math
// Render loop
let loop = () => {
let rad = sin(frame / 100) * PI / 20
let rad2 = sin(frame / 50) * PI / 10
frame++
if (postctx.canvas.width !== postctx.canvas.offsetWidth || postctx.canvas.height !== postctx.canvas.offsetHeight) {
postctx.canvas.width = canvas.width = postctx.canvas.offsetWidth
postctx.canvas.height = canvas.height = postctx.canvas.offsetHeight
}
c.fillStyle = `hsl(200deg, 100%, 2%)`
c.fillRect(0, 0, canvas.width, canvas.height)
c.save()
c.translate(canvas.width / 2, canvas.height / 2)
c.beginPath()
vertices.forEach((vertex, i) => {
let ni = i + oceanWidth
let x = vertex[0] - frame % (gridSize * 2)
let z = vertex[2] - frame * 2 % gridSize + (i % 2 === 0 ? gridSize / 2 : 0)
let wave = (cos(frame / 45 + x / 50) - sin(frame / 20 + z / 50) + sin(frame / 30 + z*x / 10000))
let y = vertex[1] + wave * waveSize
let a = Math.max(0, 1 - (Math.sqrt(x ** 2 + z ** 2)) / depth)
let tx, ty, tz
y -= oceanHeight
// Transformation variables
tx = x
ty = y
tz = z
// Rotation Y
tx = x * cos(rad) + z * sin(rad)
tz = -x * sin(rad) + z * cos(rad)
x = tx
y = ty
z = tz
// Rotation Z
tx = x * cos(rad) - y * sin(rad)
ty = x * sin(rad) + y * cos(rad)
x = tx;
y = ty;
z = tz;
// Rotation X
ty = y * cos(rad2) - z * sin(rad2)
tz = y * sin(rad2) + z * cos(rad2)
x = tx;
y = ty;
z = tz;
x /= z / perspective
y /= z / perspective
if (a < 0.01) return
if (z < 0) return
c.globalAlpha = a
c.fillStyle = `hsl(${180 + wave * 20}deg, 100%, 50%)`
c.fillRect(x - a * vertexSize / 2, y - a * vertexSize / 2, a * vertexSize, a * vertexSize)
c.globalAlpha = 1
})
c.restore()
// Post-processing
postctx.drawImage(canvas, 0, 0)
postctx.globalCompositeOperation = "screen"
postctx.filter = 'blur(16px)'
postctx.drawImage(canvas, 0, 0)
postctx.filter = 'blur(0)'
postctx.globalCompositeOperation = "source-over"
requestAnimationFrame(loop)
}
// Generating dots
for (let i = 0; i < vertexCount; i++) {
let x = i % oceanWidth
let y = 0
let z = i / oceanWidth >> 0
let offset = oceanWidth / 2
vertices.push([(-offset + x) * gridSize, y * gridSize, z * gridSize])
}
loop()
Related
I'm working on another tunnel effect demo. This time I'm trying to make the tunnel move within the image.
However, the function that handles rendering the tunnel always throws an error, and I'm not entirely sure why:
function draw(time) {
let animation = time / 1000.0;
let shiftX = ~~(texWidth * animation);
let shiftY = ~~(texHeight * 0.25 * animation);
let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation))
let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation))
for (y = 0; y < buffer.height; y++) {
for (x = 0; x < buffer.width; x++) {
let id = (y * buffer.width + x) * 4;
let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth;
let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight;
let tex = (a * texture.width + d) * 4;
buffer.data[id] = texture.data[tex];
buffer.data[id+1] = texture.data[tex+1];
buffer.data[id+2] = texture.data[tex+2];
buffer.data[id+3] = texture.data[tex+3];
}
}
ctx.putImageData(buffer, 0, 0);
window.requestAnimationFrame(draw);
}
The rest of the code is viewable here, just in case the problem happens to be somewhere else.
I have identified a possible cause -- if the first index used to read from distanceTable or
angleTable is anything other than y, the error appears, even if it's simply a value being added to y. Unfortunately, I haven't figured out what causes it, or why the second index isn't affected by this.
I've also searched for similar questions, but it seems like the people asking them all got this error for different reasons, so I'm kind of stuck.
It appears that setting the for loops to use the canvas' height and width as the upper limit instead of the pixel buffer's width and height was enough to fix it.
I have absolutely no idea why, though. Was it because the buffer was twice the size of the canvas?
var texWidth = 256;
var texHeight = 256;
var screenWidth = 640;
var screenHeight = 480;
var canvas = document.createElement('canvas');
canvas.width = screenWidth;
canvas.height = screenHeight;
var ctx = canvas.getContext("2d");
var texture = new ImageData(texWidth, texHeight);
var distanceTable = [];
var angleTable = [];
var buffer = new ImageData(canvas.width * 2, canvas.height * 2);
for (let y = 0; y < texture.height; y++) {
for (let x = 0; x < texture.width; x++) {
let id = (y * texture.width + x) * 4;
let c = x ^ y;
texture.data[id] = c;
texture.data[id+1] = c;
texture.data[id+2] = c;
texture.data[id+3] = 255;
}
}
for (let y = 0; y < buffer.height; y++) {
distanceTable[y] = [];
angleTable[y] = [];
let sqy = Math.pow(y - canvas.height, 2);
for (let x = 0; x < buffer.width; x++) {
let sqx = Math.pow(x - canvas.width, 2);
let ratio = 32.0;
let distance = ~~(ratio * texHeight / Math.sqrt(sqx + sqy)) % texHeight;
let angle = Math.abs(~~(0.5 * texWidth * Math.atan2(y - canvas.height, x - canvas.width)) / Math.PI);
distanceTable[y][x] = distance;
angleTable[y][x] = angle;
}
}
function draw(time) {
let animation = time / 1000.0;
let shiftX = ~~(texWidth * animation);
let shiftY = ~~(texHeight * 0.25 * animation);
let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation))
let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation * 2.0))
for (y = 0; y < canvas.height; y++) {
for (x = 0; x < canvas.width; x++) {
let id = (y * buffer.width + x) * 4;
let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth;
let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight;
let tex = (a * texture.width + d) * 4;
buffer.data[id] = texture.data[tex];
buffer.data[id+1] = texture.data[tex+1];
buffer.data[id+2] = texture.data[tex+2];
buffer.data[id+3] = texture.data[tex+3];
}
}
ctx.putImageData(buffer, 0, 0);
window.requestAnimationFrame(draw);
}
document.body.appendChild(canvas);
window.requestAnimationFrame(draw);
Im noob with p5j and i need help from someone please! :-(
On the website i must upload it , they not allow the external links.
and i need to change the "let url" to a png/jpg local link ( not external link ).
i dont know what the solution is ( maybe something similar with: "loadImage" or something.... please help me :-) )
Thank you so much and have a blessed week!
Here is the code:
//let url = "https://coolors.co/3a2e39-1e555c-f4d8cd-edb183-f15152";
let url = "https://blog.logrocket.com/wp-content/uploads/2022/03/Creating-animations-p5-js.png";
let palette;
let font;
function preload() {
font = loadFont("https://openprocessing.org/sketch/1359269/files/Happy Monsters.ttf");
}
function setup() {
createCanvas(1112, 834);
colorMode(HSB, 360, 100, 100, 100);
angleMode(DEGREES);
palette = createPalette(url);
background(10);
}
function draw() {
//background(0, 0, 90);
let offset = 100//width / 100;
let margin = 0; //offset / 5;
let cells = 1//int(random(2, 8));
let d = (width - offset * 2 - margin * (cells - 1)) / cells;
for (let j = 0; j < cells; j++) {
for (let i = 0; i < cells; i++) {
let x = offset + i * (d + margin) + d / 2;
let y = offset + j * (d + margin) + d / 2;
drawFancyShape(x, y, d, palette.concat());
}
}
frameRate(0.5);
noLoop();
}
function drawFancyShape(x, y, d, colors, char = String.fromCodePoint(65 + int(random(26)))) {
let g = createGraphics(d, d);
let g2 = createGraphics(d, d);
colors = shuffle(colors);
let c0 = colors[0];
colors.splice(0, 1);
let ratio = 0.2;
let xStep, yStep;
for (let y = 0; y < g.height; y += yStep) {
yStep = random(ratio, 1 - ratio) * g.height / 2;
if (y + yStep > g.height) yStep = g.height - y;
if (g.height - y - yStep < g.height / 100) yStep = g.height - y;
for (let x = 0; x < g.width; x += xStep) {
xStep = random(ratio, 1 - ratio) * g.width / 2;
if (x + xStep > g.width) xStep = g.width - x;
if (g.width - x - xStep < g.width / 100) xStep = g.width - x;
let r = [];
for (let i = 0; i < 4; i++) {
r.push(int(random(5)) * max(xStep, yStep) / 4);
}
g.rectMode(CENTER);
g.fill(random(colors));
g.noStroke();
g.rect(x + xStep / 2, y + yStep / 2, xStep - 2, yStep - 2, r[0], r[1], r[2], r[3]);
}
}
g2.textSize(g.width * 0.6);
g2.textAlign(CENTER, CENTER);
g2.textFont(font)
g2.textStyle(BOLD);
g2.fill(c0);
// g2.stroke(0);
g2.text(char, g.width / 2, g.height / 2 - g.height / 8);
let g_tmp = g.get();
let g2_tmp = g2.get();
g_tmp.mask(g2_tmp);
// g_tmp.mask(g2_tmp);
drawingContext.shadowColor = color(0, 0, 0, 33);
drawingContext.shadowBlur = d / 10;
push();
translate(x, y);
imageMode(CENTER);
// image(g, 0, 0);
let scl = 1.1;
image(g2, 0, 0, g2.width * scl, g2.height * scl);
image(g_tmp, 0, 0, g_tmp.width * scl, g_tmp.height * scl);
pop();
}
function createPalette(_url) {
let slash_index = _url.lastIndexOf('/');
let pallate_str = _url.slice(slash_index + 1);
let arr = pallate_str.split('-');
for (let i = 0; i < arr.length; i++) {
arr[i] = color('#' + arr[i]);
}
return arr;
}
// save jpg
let lapse = 0; // mouse timer
function mousePressed(){
// prevents mouse press from registering twice
if (millis() - lapse > 400){
save("img_" + month() + '-' + day() + '_' + hour() + '-' + minute() + '-' + second() + ".jpg");
lapse = millis();
}
}
There is a simple JS code that renders a very basic Mandelbrot fractal.
let canvas = document.getElementsByTagName("canvas")[0],
canvasWidth = canvas.width,
canvasHeight = canvas.height,
ctx = canvas.getContext("2d");
const maxIterations = 100,
magnificationFactor = 200,
panX = 2,
panY = 1.25;
let drawPoint = (x, y, color) => {
var pointSize = 1;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, pointSize, 0, Math.PI * 2, true);
ctx.fill();
}
let mandelbrot = (c, z = 0) => z ^ 2 + c;
let BelongsToMandelbrotSet = (x, y) => {
let realComponentOfResult = x,
imaginaryComponentOfResult = y;
for (let i = 0; i < maxIterations; i++) {
let tempRealComponent = realComponentOfResult * realComponentOfResult - imaginaryComponentOfResult * imaginaryComponentOfResult + x,
tempImaginaryComponent = 2 * realComponentOfResult * imaginaryComponentOfResult + y;
realComponentOfResult = tempRealComponent;
imaginaryComponentOfResult = tempImaginaryComponent;
}
if (realComponentOfResult * imaginaryComponentOfResult < 5)
return true;
return false;
}
for (let x = 0; x < canvasWidth; x++) {
for (let y = 0; y < canvasHeight; y++) {
let belongsToSet =
BelongsToMandelbrotSet(x / magnificationFactor - panX,
y / magnificationFactor - panY);
if (belongsToSet)
drawPoint(x, y, '#000')
}
}
body {
margin: 0;
}
<canvas width="800" height="800"></canvas>
The task is to rotate this fractal by the random angle along its axis.
And it shouldn't be a canvas rotation or its image data, but I have to tweak the initial fractal formula to do that.
For example, if the angle is 45 degrees or PI / 4 in radians, the output should look like
I have tried to play with x = center.x + 500 * Math.cos(theta), y = center.y + 500 * Math.sin(theta) without any success.
You can try to transform the coordinates right in the main loop, where you do scaling and translation:
let x1 = x * Math.cos(theta) - y * Math.sin(theta)
let y1 = x * Math.sin(theta) + y * Math.cos(theta)
let belongsToSet = BelongsToMandelbrotSet(x1/magnificationFactor - panX, ...
...drawPoint(x, y, '#000')
To further simplify this, create an affine transformation matrix for all kinds of transforms and apply it once.
I am trying to draw intensity profile for an image with x axis as the length of the line on the image and the y-axis with intensity values along the length of the line. How can i do this on html 5 canvas? I tried the below code but I am not getting the right intensity values. Not sure where i am going wrong.
private getLineIntensityVals = function (lineObj, img) {
const slope = this.calculateSlopeOfLine(lineObj.upPos, lineObj.downPos);
const intercept = this.calculateIntercept(lineObj.downPos, slope);
const ctx = img.getContext('2d');
const coordinates = [];
const intensities = [];
for (let x = lineObj.downPos.x; x <= lineObj.upPos.x; x++) {
const y = slope * x + intercept;
const pixelData = ctx.getImageData(x, y, 1, 1).data;
pixelData[0] = 255 - pixelData[0];
pixelData[1] = 255 - pixelData[1];
pixelData[2] = 255 - pixelData[2];
const intensity = ((0.299 * pixelData[0]) + (0.587 * pixelData[1]) + (0.114 * pixelData[2]));
intensities.push(intensity);
}
return intensities;
};
private calculateSlopeOfLine = function (upPos, downPos) {
if (upPos.x === downPos.x || upPos.y === downPos.y) {
return null;
}
return (downPos.y - upPos.y) / (downPos.x - upPos.x);
};
private calculateIntercept = function (startPoint, slope) {
if (slope === null) {
return startPoint.x;
}
return startPoint.y - slope * startPoint.x;
};
private calculateLineLength(line) {
const dim = {width: Math.abs(line.downPos.x -line.upPos.x),height:Math.abs(line.downPos.y- line.upPos.y)};
length = Math.sqrt(Math.pow(dim.width, 2) + Math.pow(dim.height, 2));
return length;
};
Image data
Don't get the image data one pixel at a time. Gaining access to pixel data is expensive (CPU cycles), and memory is cheap. Get all the pixels once and reuse that data.
Sampling the data
Most lines will not fit into pixels evenly. To solve divide the line into the number of samples you want (You can use the line length)
Then step to each sample in turn getting the 4 neighboring pixels values and interpolating the color at the sample point.
As we are interpolating we need to ensure that we do not use the wrong color model. In this case we use sRGB.
We thus get the function
// imgData is the pixel date
// x1,y1 and x2,y2 are the line end points
// sampleRate is number of samples per pixel
// Return array 3 values for each sample.
function getProfile(imgData, x1, y1, x2, y2, sampleRate) {
// convert line to vector
const dx = x2 - x1;
const dy = y2 - y1;
// get length and calculate number of samples for sample rate
const samples = (dx * dx + dy * dy) ** 0.5 * Math.abs(sampleRate) + 1 | 0;
// Divide line vector by samples to get x, and y step per sample
const nx = dx / samples;
const ny = dy / samples;
const w = imgData.width;
const h = imgData.height;
const pixels = imgData.data;
const values = [];
// Offset line to center of pixel
var x = x1 + 0.5;
var y = y1 + 0.5;
var i = samples;
while (i--) { // for each sample
// make sure we are in the image
if (x >= 0 && x < w - 1 && y >= 0 && y < h - 1) {
// get 4 closest pixel indexes
const idxA = ((x | 0) + (y | 0) * w) * 4;
const idxB = ((x + 1 | 0) + (y | 0) * w) * 4;
const idxC = ((x + 1 | 0) + (y + 1 | 0) * w) * 4;
const idxD = ((x | 0) + (y + 1 | 0) * w) * 4;
// Get channel data using sRGB approximation
const r1 = pixels[idxA] ** 2.2;
const r2 = pixels[idxB] ** 2.2;
const r3 = pixels[idxC] ** 2.2;
const r4 = pixels[idxD] ** 2.2;
const g1 = pixels[idxA + 1] ** 2.2;
const g2 = pixels[idxB + 1] ** 2.2;
const g3 = pixels[idxC + 1] ** 2.2;
const g4 = pixels[idxD + 1] ** 2.2;
const b1 = pixels[idxA + 2] ** 2.2;
const b2 = pixels[idxB + 2] ** 2.2;
const b3 = pixels[idxC + 2] ** 2.2;
const b4 = pixels[idxD + 2] ** 2.2;
// find value at location via linear interpolation
const xf = x % 1;
const yf = y % 1;
const rr = (r2 - r1) * xf + r1;
const gg = (g2 - g1) * xf + g1;
const bb = (b2 - b1) * xf + b1;
/// store channels as uncompressed sRGB
values.push((((r3 - r4) * xf + r4) - rr) * yf + rr);
values.push((((g3 - g4) * xf + g4) - gg) * yf + gg);
values.push((((b3 - b4) * xf + b4) - bb) * yf + bb);
} else {
// outside image
values.push(0,0,0);
}
// step to next sample
x += nx;
y += ny;
}
return values;
}
Conversion to values
The array hold raw sample data. There are a variety of ways to convert to a value. That is why we separate the sampling from the conversion to values.
The next function takes the raw sample array and converts it to values. It returns an array of values. While it is doing the conversion it also get the max value so that the data can be plotted to fit a graph.
function convertToMean(values) {
var i = 0, v;
const results = [];
results._max = 0;
while (i < values.length) {
results.push(v = (values[i++] * 0.299 + values[i++] * 0.587 + values[i++] * 0.114) ** (1/2.2));
results._max = Math.max(v, results._max);
}
return results;
}
Now you can plot the data how you like.
Example
Click drag line on image (when loaded)
Results are plotted real time.
Move mouse over plot to see values.
Use full page to see all.
const ctx = canvas.getContext("2d");
const ctx1 = canvas1.getContext("2d");
const SCALE_IMAGE = 0.5;
const PLOT_WIDTH = 500;
const PLOT_HEIGHT = 150;
canvas1.width = PLOT_WIDTH;
canvas1.height = PLOT_HEIGHT;
const line = {x1: 0, y1: 0, x2: 0, y2:0, canUse: false, haveData: false, data: undefined};
var bounds, bounds1, imgData;
// ix iy image coords, px, py plot coords
const mouse = {ix: 0, iy: 0, overImage: false, px: 0, py:0, overPlot: false, button : false, dragging: 0};
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
const img = new Image;
img.crossOrigin = "Anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Black_and_yellow_garden_spider%2C_Washington_DC.jpg/800px-Black_and_yellow_garden_spider%2C_Washington_DC.jpg";
img.addEventListener("load",() => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img,0,0);
imgData = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height);
canvas.width = img.width * SCALE_IMAGE;
canvas.height = img.height * SCALE_IMAGE;
bounds = canvas.getBoundingClientRect();
bounds1 = canvas1.getBoundingClientRect();
requestAnimationFrame(update);
},{once: true});
function getProfile(imgData, x1, y1, x2, y2, sampleRate) {
x1 *= 1 / SCALE_IMAGE;
y1 *= 1 / SCALE_IMAGE;
x2 *= 1 / SCALE_IMAGE;
y2 *= 1 / SCALE_IMAGE;
const dx = x2 - x1;
const dy = y2 - y1;
const samples = (dx * dx + dy * dy) ** 0.5 * Math.abs(sampleRate) + 1 | 0;
const nx = dx / samples;
const ny = dy / samples;
const w = imgData.width;
const h = imgData.height;
const pixels = imgData.data;
const values = [];
var x = x1 + 0.5;
var y = y1 + 0.5;
var i = samples;
while (i--) {
if (x >= 0 && x < w - 1 && y >= 0 && y < h - 1) {
// get 4 closest pixel indexs
const idxA = ((x | 0) + (y | 0) * w) * 4;
const idxB = ((x + 1 | 0) + (y | 0) * w) * 4;
const idxC = ((x + 1 | 0) + (y + 1 | 0) * w) * 4;
const idxD = ((x | 0) + (y + 1 | 0) * w) * 4;
// Get channel data using sRGB approximation
const r1 = pixels[idxA] ** 2.2;
const r2 = pixels[idxB] ** 2.2;
const r3 = pixels[idxC] ** 2.2;
const r4 = pixels[idxD] ** 2.2;
const g1 = pixels[idxA + 1] ** 2.2;
const g2 = pixels[idxB + 1] ** 2.2;
const g3 = pixels[idxC + 1] ** 2.2;
const g4 = pixels[idxD + 1] ** 2.2;
const b1 = pixels[idxA + 2] ** 2.2;
const b2 = pixels[idxB + 2] ** 2.2;
const b3 = pixels[idxC + 2] ** 2.2;
const b4 = pixels[idxD + 2] ** 2.2;
// find value at location via linear interpolation
const xf = x % 1;
const yf = y % 1;
const rr = (r2 - r1) * xf + r1;
const gg = (g2 - g1) * xf + g1;
const bb = (b2 - b1) * xf + b1;
/// store channels as uncompressed sRGB
values.push((((r3 - r4) * xf + r4) - rr) * yf + rr);
values.push((((g3 - g4) * xf + g4) - gg) * yf + gg);
values.push((((b3 - b4) * xf + b4) - bb) * yf + bb);
} else {
// outside image
values.push(0,0,0);
}
x += nx;
y += ny;
}
values._nx = nx;
values._ny = ny;
values._x = x1;
values._y = y1;
return values;
}
function convertToMean(values) {
var i = 0, max = 0, v;
const results = [];
while (i < values.length) {
results.push(v = (values[i++] * 0.299 + values[i++] * 0.587 + values[i++] * 0.114) ** (1/2.2));
max = Math.max(v, max);
}
results._max = max;
results._nx = values._nx;
results._ny = values._ny;
results._x = values._x;
results._y = values._y;
return results;
}
function plotValues(ctx, values) {
const count = values.length;
const scaleX = ctx.canvas.width / count;
// not using max in example
// const scaleY = (ctx.canvas.height-3) / values._max;
const scaleY = (ctx.canvas.height-3) / 255;
ctx1.clearRect(0,0, ctx.canvas.width, ctx.canvas.height);
var i = 0;
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
while (i < count) {
const y = ctx.canvas.height - values[i] * scaleY + 1;
ctx.lineTo(i++ * scaleX, y);
}
ctx.stroke();
if (!mouse.button && mouse.overPlot) {
ctx.fillStyle = "#f008";
ctx.fillRect(mouse.px, 0, 1, ctx.canvas.height);
const val = values[mouse.px / scaleX | 0];
info.textContent = "Value: " + (val !== undefined ? val.toFixed(2) : "");
}
}
function update() {
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(img, 0, 0, img.width * SCALE_IMAGE, img.height * SCALE_IMAGE);
var atSample = 0;
if (!mouse.button) {
if (line.canUse) {
if (line.haveData && mouse.overPlot) {
const count = line.data.length;
const scaleX = ctx1.canvas.width / count
atSample = mouse.px / scaleX;
}
}
}
if (mouse.button) {
if (mouse.dragging === 1) { // dragging line
line.x2 = mouse.ix;
line.y2 = mouse.iy;
line.canUse = true;
line.haveData = false;
} else if(mouse.overImage) {
mouse.dragging = 1;
line.x1 = mouse.ix;
line.y1 = mouse.iy;
line.canUse = false;
line.haveData = false;
canvas.style.cursor = "none";
}
} else {
mouse.dragging = 0;
canvas.style.cursor = "crosshair";
}
if (line.canUse) {
ctx.strokeStyle = "#F00";
ctx.strokeWidth = 2;
ctx.beginPath();
ctx.lineTo(line.x1, line.y1);
ctx.lineTo(line.x2, line.y2);
ctx.stroke();
if (atSample) {
ctx.fillStyle = "#FF0";
ctx.beginPath();
ctx.arc(
(line.data._x + line.data._nx * atSample) * SCALE_IMAGE,
(line.data._y + line.data._ny * atSample) * SCALE_IMAGE,
line.data[atSample | 0] / 32,
0, Math.PI * 2
);
ctx.fill();
}
if (!line.haveData) {
const vals = getProfile(imgData, line.x1, line.y1, line.x2, line.y2, 1);
line.data = convertToMean(vals);
line.haveData = true;
plotValues(ctx1, line.data);
} else {
plotValues(ctx1, line.data);
}
}
requestAnimationFrame(update);
}
function mouseEvents(e){
if (bounds) {
mouse.ix = e.pageX - bounds.left;
mouse.iy = e.pageY - bounds.top;
mouse.overImage = mouse.ix >= 0 && mouse.ix < bounds.width && mouse.iy >= 0 && mouse.iy < bounds.height;
mouse.px = e.pageX - bounds1.left;
mouse.py = e.pageY - bounds1.top;
mouse.overPlot = mouse.px >= 0 && mouse.px < bounds1.width && mouse.py >= 0 && mouse.py < bounds1.height;
}
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas"></canvas>
<div id="info">Click drag line over image</div>
<canvas id="canvas1"></canvas>
Image source: https://commons.wikimedia.org/w/index.php?curid=93680693 By BethGuay - Own work, CC BY-SA 4.0,
I'm trying to draw parallel equidistant lines inside a circle. I've got to the stage that if i connect points from their opposite angles on the circumference I get parallel lines... but they're not equidistant...
Here's some code:
var num_lines = 8;
var num_points = num_lines * 2;
var start_angle = 100;
var points = [];
var radius = 200;
ctx.strokeCircle(w/2, h/2, radius, radius); // shorthand for ctx.arc( x, y, 5, 0, Math.PI * 2, true );
for (var i = 0; i < num_points; i++) {
var angle = 360/num_points * i;
ctx.fillStyle = "red";
if (i %2 == 0 ) ctx.fillStyle = "blue";
var x = w/2 + Math.cos(angle) * radius/2;
var y = h/2 + Math.sin(angle) * radius/2;
ctx.circle(x, y, 10, 10); // shorthand for ctx.arc( x, y, 5, 0, Math.PI * 2, true );
points.push({x: x, y: y});
}
for (var i = 0; i < num_lines; i++) {
ctx.line(points[i].x, points[i].y, points[points.length-i-1].x, points[points.length-i-1].y)
}
Use Pythagoras' theorem.
The ...
y: vertical position of the line relative to the center
x: horizontal distance of its endpoints from the center
r: radius of the circle
... must satisfy y^2 + x^2 = r^2.
Code:
var radius = 200;
var num_lines = 8;
// vertical spacing
var delta_y = (2.0 * radius) / (num_lines + 1);
ctx.strokeCircle(w/2, h/2, radius, radius);
for (var i = 0; i < num_lines; i++)
{
// applying pythagoras
var y = delta_y * (i + 1) - radius / 2;
var x = Math.sqrt(radius * radius - y * y);
// calculating endpoints
var left_x = w / 2 - x;
var right_x = w / 2 + x;
var end_y = h / 2 + y;
ctx.fillStyle = (i % 2 == 0) ? "blue" : "red";
ctx.circle(left_x, end_y, 10, 10);
ctx.circle(right_x, end_y, 10, 10);
ctx.line(left_x, end_y, right_x, end_y);
}
EDIT: rotation
To rotate a vector by angle a clockwise:
x' = x * cos(a) + y * sin(a)
y' = y * cos(a) - x * sin(a)
Code:
var radius = 200;
var num_lines = 50;
var angle = 60;
// temporary variables
var delta_y = (2.0 * radius) / (num_lines);
var cos_a = Math.cos(angle * Math.PI / 180.0);
var sin_a = Math.sin(angle * Math.PI / 180.0);
ctx.strokeCircle(w / 2, h / 2, radius * 2, radius * 2);
for (var i = 0; i < num_lines; i++)
{
// applying pythagoras
var y = delta_y * i - radius;
var x = Math.sqrt(radius * radius - y * y);
// rotating the displacement vector
var left_x = y * sin_a + x * cos_a;
var right_x = y * sin_a - x * cos_a;
var left_y = y * cos_a - x * sin_a;
var right_y = y * cos_a + x * sin_a;
ctx.fillStyle = (i % 2 == 0) ? "blue" : "red";
ctx.line(w / 2 + left_x , h / 2 + left_y ,
w / 2 + right_x, h / 2 + right_y);
}
got it to work like so... wondering how I can rotate the lines at an angle (mathematically, not using ctx.translate and ctx.rotate ):
var radius = 200;
var num_lines = 50;
// vertical spacing
var delta_y = (2.0 * radius) / (num_lines);
ctx.strokeCircle(w/2, h/2, radius * 2, radius * 2);
for (var i = 0; i < num_lines; i++)
{
// applying pythagoras
var y = delta_y * (i) - radius;
var x = Math.sqrt(radius * radius - y * y);
// calculating endpoints
var left_x = w / 2 - x;
var right_x = w / 2 + x;
var end_y = h / 2 + y;
ctx.fillStyle = (i % 2 == 0) ? "blue" : "red";
ctx.line(left_x, end_y, right_x, end_y);
}