Bezier 1
written by reyrove
Setting Up the Basics
const ratio = 1 / 1;
const prefix = 'Bezier 1';
We start by defining a constant ratio
, which is set to 1:1. This is used to maintain the aspect ratio of our canvas. We also define a prefix
for our canvas, which will help in file naming for downloads.
const features = {};
let resizeTmr = null;
let thumbnailTaken = false;
Here, we declare an empty object features
, which will later store visual attributes like colors and the number of curves. We also initialize variables like resizeTmr
(used for debouncing resize events) and thumbnailTaken
to track when the thumbnail is captured.
const urlSearchParams = new URLSearchParams(window.location.search);
const urlParams = Object.fromEntries(urlSearchParams.entries());
let forceDownloaded = false;
We fetch URL parameters using URLSearchParams
and store them in urlParams
. These can be used to pass certain configuration values through the URL. The forceDownloaded
variable tracks whether the canvas has already been downloaded automatically.
const animated = false;
let nextFrame = null;
Here, animated
is a flag indicating whether the canvas should be animated. For now, we set it to false
. nextFrame
will be used to store the next frame in case of animation.
Defining Parameters
definitions = [
{
id: "number_id1",
name: "Number of Curves",
type: "number",
options: {
min: 3,
max: 50,
step: 1,
},
},
{
id: "number_id2",
name: "number Of Divisions",
type: "number",
options: {
min: 4,
max: 100,
step: 1,
},
}
];
$fx.params(definitions);
We define two customizable parameters here: the number of curves and the number of divisions for the curves. These parameters can be adjusted between given minimum and maximum values and will later influence the way the curves are drawn.
Initializing the Canvas and Features
const setup = () => {
const BackgroundColours = ['#040720', '#0C090A', '#34282C', '#151B54', '#033E3E', '#25383C', '#2C3539', '#004225', '#483C32', '#3B2F2F', '#43302E', '#551606', '#3F000F', '#2F0909', '#2B1B17', '#583759', '#36013F', '#2E1A47', '#342D7E'];
const BackgroundrNames = ['Black Blue', 'Night', 'Charcoal', 'Night Blue', 'Deep Teal', 'DarkSlateGray', 'Gunmetal', 'Lotus Green', 'Taupe', 'Dark Coffee', 'Old Burgundy', 'Blood Night', 'Chocolate Brown', 'Dark Maroon', 'Midnight', 'Plum Purple', 'Deep Purple', 'Midnight Purple', 'Blue Whale'];
const foregroundColours = ['#565051', '#C9C1C1', '#98AFC7', '#728FCE', '#0020C2', '#14A3C7', '#EBF4FA', '#57FEFF', '#77BFC7', '#01F9C6', '#5F9EA0', '#00A36C', '#808000', '#08A04B', '#E2F516', '#CCFB5D', '#FFFFCC', '#FBB117', '#FFA500', '#BCB88A', '#C9BE62', '#CD853F', '#966F33', '#665D1E', '#804A00', '#A0522D', '#C34A2C', '#B83C08', '#EB5406', '#C36241', '#FF5F1F', '#FA8072', '#FD1C03', '#C11B17', '#9F000F', '#CC7A8B', '#FFCBA4', '#F778A1', '#E75480', '#F6358A', '#FA2A55', '#F433FF', '#5E5A80', '#822EFF', '#C8C4DF', '#E9E4D4'];
const foregroundNames = ['Vampire Gray', 'Steampunk', 'Blue Gray', 'Light Purple Blue', 'Cobalt Blue', 'Cyan Blue', 'Water', 'Blue Zircon', 'Blue Hosta', 'Bright Teal', 'CadetBlue', 'Jade', 'Olive', 'Irish Green', 'Yellow Green Grosbeak', 'Tea Green', 'Cream', 'Beer', 'Orange', 'Sage', 'Ginger Brown', 'Peru', 'Wood', 'Antique Bronze', 'Dark Bronze', 'Sienna', 'Chestnut Red', 'Ginger Red', 'Red Gold', 'Rust', 'Bright Orange', 'Salmon', 'Neon Red', 'Chilli Pepper', 'Cranberry', 'Dusky Pink', 'Deep Peach', 'Carnation Pink', 'Dark Pink', 'Violet Red', 'Red Pink', 'Bright Neon Pink', 'Grape', 'Blue Magenta', 'Viola', 'Ash White'];
const BackgroundIndex = Math.floor($fx.rand() * BackgroundColours.length);
const BackgroundColor = BackgroundColours[BackgroundIndex];
features.backgroundColour = '#000000';
features.Color1 = BackgroundColor;
features.Color2 = foregroundColours;
features.NumberofCurves = $fx.getParam('number_id1');
const Color = [];
const readableFeaturesObj = {};
readableFeaturesObj['Background Color'] = BackgroundrNames[BackgroundIndex];
for (let j = features.NumberofCurves; j > 0; j--) {
const colorIndex = Math.floor($fx.rand() * features.Color2.length);
Color[j] = features.Color2[colorIndex];
const S = 'Foreground Color ' + j.toString();
readableFeaturesObj[S] = foregroundNames[colorIndex];
}
features.Color3 = Color;
readableFeaturesObj['Number of Curves'] = $fx.getParam('number_id1');
readableFeaturesObj['number Of Divisions'] = $fx.getParam('number_id2');
$fx.features(readableFeaturesObj);
console.table(readableFeaturesObj);
};
This is the heart of the setup
function where the colors and parameters are initialized:
- Colors : We define arrays of possible background and foreground colors.
-
Random Selection
: We use
$fx.rand()
to pick a random background and foreground color, ensuring that each render will be unique.
-
Readable Features
: We create a
readableFeaturesObj
that will store human-readable information about the generated art, which includes selected colors and the number of curves.
- Logging : Finally, the selected features are logged for viewing.
Drawing the Bezier Curves
function drawSmoothClosedBezierCurve(ctx, points, LineWidth, Color) {
if (points.length < 3) {
console.error("At least three points are required to draw a closed curve.");
return;
}
ctx.beginPath();
ctx.moveTo((points[0][0] + points[points.length - 1][0]) / 2, (points[0][1] + points[points.length - 1][1]) / 2);
for (let i = 0; i < points.length; i++) {
let nextIndex = (i + 1) % points.length;
let xc = (points[i][0] + points[nextIndex][0]) / 2;
let yc = (points[i][1] + points[nextIndex][1]) / 2;
ctx.quadraticCurveTo(points[i][0], points[i][1], xc, yc);
}
ctx.closePath();
ctx.lineWidth = LineWidth;
ctx.strokeStyle = Color;
ctx.shadowBlur = LineWidth;
ctx.shadowColor = Color;
ctx.stroke();
}
This function draws smooth closed Bezier curves.
- Points : The curve requires at least three points, and we calculate midpoints for each segment using quadratic Bézier curves.
- Styling : Each curve is given a specified LineWidth and Color . We also add shadow effects for a glowing effect.
function anglesForEqualDivisions(ctx, numberOfDivisions) {
const angle = (2 * Math.PI) / numberOfDivisions;
const angles = [];
for (let i = 0; i < numberOfDivisions; i++) {
angles.push(i * angle);
}
return angles;
}
This helper function divides a circle into equal angles, ensuring that our curves are evenly spaced.
Finalizing the Drawing
const drawCanvas = async () => {
window.cancelAnimationFrame(nextFrame);
const canvas = document.getElementById('target');
const ctx = canvas.getContext('2d');
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.fillStyle = features.Color1;
ctx.fillRect(0, 0, w, h);
ctx.translate(w / 2, h / 2);
const NumberofCurves = features.NumberofCurves;
const numberOfDivisions = $fx.getParam('number_id2');
for (let j = NumberofCurves; j > 0; j--) {
const Color = features.Color3[j];
const angle = anglesForEqualDivisions(ctx, numberOfDivisions);
const points = [];
for (let i = 0; i < angle.length; i++) {
const Z = $fx.rand() * 3 / 4 + 1 / 4;
points[i] = [Z * w / (2 * (j ** (1 / NumberofCurves))) * Math.cos(angle[i]), Z * h / (2 * (j ** (1 / NumberofCurves))) * Math.sin(angle[i])];
}
const LineWidth = w / (50 * (j ** (1 / NumberofCurves)) + numberOfDivisions);
drawSmoothClosedBezierCurve(ctx, points, LineWidth, Color);
}
if (!thumbnailTaken) {
$fx.preview();
thumbnailTaken = true;
}
if ('forceDownload' in urlParams && !forceDownloaded) {
forceDownloaded = true;
await autoDownloadCanvas();
window.parent.postMessage('forceDownloaded', '*');
}
if (animated) {
nextFrame = window.requestAnimationFrame(drawCanvas);
}
};
This is the drawCanvas
function where the magic happens. We:
- Set up the canvas : Translate the origin to the center and fill the background with the chosen color.
-
Draw the curves
: For each curve, calculate the points and pass them to the
drawSmoothClosedBezierCurve
function.
-
Capture the thumbnail
: We trigger
$fx.preview()
to capture the thumbnail.
-
Handle downloads
: If
forceDownload
is in the URL parameters, we automatically download the canvas as an image.
Putting it All Together
document.addEventListener('DOMContentLoaded', init);
Finally, we wait for the DOM content to load and trigger the init() function, which sets everything in motion.
With this article, I hope you've gained a deep understanding of how this Bezier curve generator works. Each part of the code works harmoniously to create smooth, random, and visually appealing curves, all while leveraging the power of JavaScript and HTML canvas.
Feel free to tweak the parameters, change the colors, or add new features to create your own version of this digital artwork. Whether it's adjusting the number of curves, playing with divisions, or experimenting with new color palettes, the possibilities are endless!
You can access the full code and explore this creative digital journey further. Click here to view and modify the complete code in our GitHub repository. Dive in, create your own versions, and don't forget to share your art with me on:
Your art will become part of our ever-growing digital tapestry, where creativity and code unite in harmony. Let’s inspire each other and show the world what love, creativity, and a bit of digital magic can do.
With endless love and creativity! 💙
_Frostbond Coders