cyber-circle-creator/js/image.js

279 lines
No EOL
9.4 KiB
JavaScript

// Configuration
// ---------------------------------------------------------------------------------
const config = {
logoSrc: 'img/logo.png', // Path to the logo image
logoHeight: 50, // Logo height
backgroundColor: '#090909', // Canvas background color
lineColor: '#159262', // Default dashed line color
lineThickness: 3, // Dashed line thickness
randomizeYellowLines: true, // Enable or disable random yellow lines
yellowLineColor: '#9d921d', // Yellow line color
avatarBackgroundColor: '#090909', // Background color behind the avatar
borderThickness: 5, // Border thickness around avatars
borderColor: '#1bec99', // Border color around avatars
urlText: '[ Create your own at ccc.cyber.to ]', // Text to display on the canvas
urlFont: '16px monospace', // Font for URL text
urlColor: '#159262', // URL text color
avatarScale: 75, // Avatar scale percentage
drawMazeBackground: true, // Whether to draw a random maze background
mazeColor: '#05130f', // Color of the maze lines
mazeThickness: 2, // Thickness of the maze lines
drawRedLine: true, // Enable or disable the red line
redLineColor: '#9B283C', // Red line color
consoleLogging: false, // Enable or disable console logging
};
// ---------------------------------------------------------------------------------
// Converts degrees to radians
const toRad = (x) => {
if (config.consoleLogging) console.log('Function call: toRad');
return x * (Math.PI / 180);
};
const dist = [200, 330, 450]; // Distance of each user layer from the center
const numb = [8, 15, 26]; // Number of users in each layer
const radius = [64, 58, 50]; // Radius of avatars in each layer
let userNum = 0; // Counter for the number of users rendered
let remainingImg = 0; // Counter for remaining images to load
let totalImg = 0; // Total images to load
function render(users, selfUser) {
if (config.consoleLogging) console.log('Function call: render');
userNum = 0;
remainingImg = 0;
// Get canvas element
const canvas = document.getElementById("canvas");
if (!canvas) return;
const ctx = canvas.getContext("2d"); // Get canvas context
const width = canvas.width;
const height = canvas.height;
// Draw maze if enabled
if (config.drawMazeBackground) {
drawMazeBackground(ctx, width, height);
}
// Configure canvas background
ctx.fillStyle = config.backgroundColor;
ctx.globalCompositeOperation = 'destination-over';
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
const userPositions = [];
const tasks = [];
const mainAvatarRadius = 110 * (config.avatarScale / 100); // Scale main avatar
totalImg += 1;
remainingImg += 1;
loadImage(
ctx,
selfUser.avatar,
(width / 2) - mainAvatarRadius,
(height / 2) - mainAvatarRadius,
mainAvatarRadius,
null,
tasks
);
// Position and render user avatars
for (let layerIndex = 0; layerIndex < 3; layerIndex++) {
const angleSize = 360 / numb[layerIndex];
for (let i = 0; i < numb[layerIndex]; i++) {
if (userNum >= users.length) break;
totalImg += 1;
remainingImg += 1;
const offset = layerIndex * 30;
const r = toRad(i * angleSize + offset);
const centerX = Math.cos(r) * dist[layerIndex] + width / 2;
const centerY = Math.sin(r) * dist[layerIndex] + height / 2;
userPositions.push({ x: centerX, y: centerY });
// Render connecting lines
ctx.beginPath();
ctx.setLineDash([10, 3]);
ctx.moveTo(width / 2, height / 2);
ctx.lineTo(centerX, centerY);
ctx.strokeStyle = config.lineColor;
ctx.lineWidth = config.lineThickness;
ctx.stroke();
ctx.setLineDash([]);
loadImage(
ctx,
users[userNum].avatar,
centerX - radius[layerIndex] * (config.avatarScale / 100),
centerY - radius[layerIndex] * (config.avatarScale / 100),
radius[layerIndex] * (config.avatarScale / 100),
null,
tasks
);
userNum++;
}
}
// Draw random yellow lines if enabled
if (config.randomizeYellowLines) {
randomizeYellowLines(ctx, userPositions, 10);
}
// Draw red line if enabled
if (config.drawRedLine) {
drawRedLine(ctx, width, height, userPositions);
}
const logo = new Image();
logo.onload = function () {
// Load and draw logo
const aspectRatio = logo.width / logo.height;
const logoWidth = config.logoHeight * aspectRatio;
ctx.drawImage(logo, 10, 10, logoWidth, config.logoHeight);
};
logo.src = config.logoSrc;
// Draw URL text
ctx.font = config.urlFont;
ctx.fillStyle = config.urlColor;
const textWidth = ctx.measureText(config.urlText).width;
const textXPosition = width - textWidth - 10;
const textYPosition = height - 10;
ctx.fillText(config.urlText, textXPosition, textYPosition);
}
// Draws a maze background
function drawMazeBackground(ctx, width, height) {
if (config.consoleLogging) console.log('Function call: drawMazeBackground');
const cellSize = 20;
ctx.strokeStyle = config.mazeColor;
ctx.lineWidth = config.mazeThickness;
for (let y = 0; y < height; y += cellSize) {
for (let x = 0; x < width; x += cellSize) {
if (Math.random() > 0.5) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + cellSize, y + cellSize);
ctx.stroke();
} else {
ctx.beginPath();
ctx.moveTo(x + cellSize, y);
ctx.lineTo(x, y + cellSize);
ctx.stroke();
}
}
}
}
// Draw random yellow lines
function randomizeYellowLines(ctx, userPositions, maxLines) {
if (config.consoleLogging) console.log('Function call: randomizeYellowLines');
const numberOfLines = Math.min(maxLines, Math.floor(Math.random() * userPositions.length));
for (let i = 0; i < numberOfLines; i++) {
const index1 = Math.floor(Math.random() * userPositions.length);
let index2;
do {
index2 = Math.floor(Math.random() * userPositions.length);
} while (index2 === index1);
const startPos = userPositions[index1];
const endPos = userPositions[index2];
ctx.beginPath();
ctx.setLineDash([10, 3]);
ctx.moveTo(startPos.x, startPos.y);
ctx.lineTo(endPos.x, endPos.y);
ctx.strokeStyle = config.yellowLineColor;
ctx.lineWidth = config.lineThickness;
ctx.stroke();
ctx.setLineDash([]);
}
}
// Draw a random red line
function drawRedLine(ctx, width, height, userPositions) {
if (config.consoleLogging) console.log('Function call: drawRedLine');
if (userPositions.length === 0) return;
const selectedPosition = userPositions[Math.floor(Math.random() * userPositions.length)];
ctx.beginPath();
ctx.setLineDash([10, 3]);
ctx.moveTo(width / 2, height / 2);
ctx.lineTo(selectedPosition.x, selectedPosition.y);
ctx.strokeStyle = config.redLineColor;
ctx.lineWidth = config.lineThickness;
ctx.stroke();
ctx.setLineDash([]);
}
// Load and draw user images
function loadImage(ctx, url, x, y, r, name, tasks) {
if (config.consoleLogging) console.log('Function call: loadImage');
let progress = document.getElementById("outInfo");
if (!progress) return;
tasks.push(() => { /* No text addition here */ });
// Decrement image counter
const decrementRemaining = () => {
remainingImg -= 1;
progress.innerText = `Loading avatars: ${totalImg - remainingImg}/${totalImg}`;
if (remainingImg <= 0) {
progress.innerText = "";
tasks.forEach((task) => task());
}
};
const img = new Image();
// Handle image loading errors
const drawImageWithFallback = (imageSrc) => {
img.onload = function() {
ctx.save();
ctx.beginPath();
ctx.arc(x + r, y + r, r, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
ctx.fillStyle = config.avatarBackgroundColor;
ctx.beginPath();
ctx.arc(x + r, y + r, r, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.drawImage(img, x, y, r * 2, r * 2);
ctx.beginPath();
ctx.arc(x + r, y + r, r, 0, Math.PI * 2, true);
ctx.lineWidth = config.borderThickness;
ctx.strokeStyle = config.borderColor;
ctx.stroke();
ctx.closePath();
ctx.restore();
decrementRemaining();
};
img.onerror = function() {
console.error(`IMG Error: ${imageSrc}`);
if (imageSrc !== 'img/placeholder.png') {
drawImageWithFallback('img/placeholder.png');
} else {
decrementRemaining();
}
};
img.src = imageSrc;
};
drawImageWithFallback(url);
}