Author: Affolter Matias
Have you ever wondered how to turn a simple image into a mesmerizing hexagon masterpiece? No? Well, we're going to show you anyway! Our hero for today is a JavaScript function that can generate stunning hexagonal patterns from regular images. π¨β¨
The Hexagon Enigma π€―β
The generateFinalBase64
function is our main magician. It takes an originalImageData
and a radius as parameters. But wait, what's the deal with that radius value? Why must it be between 2 and 36? Well, good question! It turns out that we can't just make hexagons of any size; there are limits to the magic. πͺ
"Invalid radius value. Must within 2 and 36." - The Magic Barrier π§ββοΈ
Now, let's dive into the incantations of this function:
a
, r
, and d
are constants that we need for hexagon math. Why? Because hexagons are all about angles (radians) radius and diameter! ππ
We create a canvas and set its width and height. But the calculations for the canvas dimensions are like deciphering an ancient hexagonal script. What's with that Math.sin(a)
and Math.cos(a)
stuff? It's all part of the hexagon secret code. ππ’
==> Hexagon secret code "explained"
There's a function called getUint32
. It takes some data and an index and returns a magical 32-bit integer. Ever wondered how colors are represented in the digital realm? It's all about these bits of wizardry! π§ββοΈπ
And then, there's uint32ToHex
. It turns those magical 32-bit integers into colorful HTML hex codes. It's like turning lead into gold, but much more colorful! ππ
-
getColor
gets the color of a pixel from our image data and makes it look pretty. Who doesn't love a beautiful hexagon in their favorite color? π¨π -
drawHexagon
does what its name says - it draws hexagons with style. We even give them a stroke and a fill. Who knew hexagons had such fashion sense? ππ -
drawGrid
is the mastermind behind the big picture. It takes care of arranging hexagons in a grid, complete with zigzags! It's like a symphony of hexagons dancing on a canvas. πΆπ
Finally, we bring everything together, paint it on a canvas, and return our hexagon masterpiece in all its glory. It's like making a delicious hexagonal cake and serving it to the world. π°π
The Grand Finale πβ¨
But that's not the end of our adventure! There's one more function called hexagonrender
. It takes an image_data
, a scale, and something called pool. And it returns a promise! What's the promise, you ask? The promise of a scaled-up image data. It's like a magician pulling a rabbit out of a hat! ππ©
And that, my friends, is how you unlock the secrets of the hexagon code. Go forth, create hexagonal wonders, and remember, every great piece of art starts with a little bit of magic! β¨π¨
Used in https://pixa.pics/ where you can draw pixel art.
/* MIT License, Copyright (c) 2023 Affolter Matias */
function generateFinalBase64(originalImageData, radius) {
if (radius <= 2 || radius >= 36) {
throw new Error("Invalid radius value. Must within 2 and 36");
}
// Create constant
const a = 2 * Math.PI / 6;
const r = radius;
const d = r * 2;
// Create an intermediate canvas to draw hexagon onto it
const canvas = document.createElement("canvas");
canvas.width = originalImageData.width * d - (Math.floor(originalImageData.width/2)*r); // Hexagons are taking 1x Diameter less 1/2 radius of width
canvas.height = originalImageData.height * (d * Math.sin(a)) + (originalImageData.width % 2 === 0 ? d: r); // Hexagons are taking a height that is computed differently
const ctx = canvas.getContext('2d');
function getUint32(data, index) {
// Verify the given index is within the bounds of the r, g, b, a uint8array
if (index >= 0 && index < data.length) {
// "Sum up" 4 x 8bits into 1 x 32 bit unsigned integer
return ((data[index] << 24) | (data[index+1] << 16) | (data[index+2] << 8) | data[index+3] | 0) >>> 0
}
return 0;
}
function uint32ToHex(uint32) {
// Converting the number to a hexadecimal string which is added to a string made of zeroes
// The hexadecimal with padding is cut to represent a fixed length of rrggbbaa which are added to "#"
return "#".concat("00000000".concat(uint32.toString(16)).slice(-8));
}
function getColor(data, w, x, y) {
let index = (y * w + x) * 4; // Compute the index (within the data where 4 elements gives a color
return uint32ToHex(getUint32(data, index));
}
function drawHexagon(ctx, x, y, color) {
// Define the style of painting on our canvas context
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.beginPath();
// Draw all intersections in a new path
for (let i = 0; i < 6; i++) {
ctx.lineTo(x + r * Math.cos(a * i), y + r * Math.sin(a * i));
}
// Close the path and fill the area
ctx.closePath(); ctx.stroke(); ctx.fill();
}
function drawGrid(ctx, data, width, height, sizeX, sizeY) {
let posX = 0, posY = 0;
// When we return to a new line, if we have an odd number of column
// We end up going to the bottom from a higher y coordinate (ZIGZAG in Y)
let RorD = sizeX % 2 === 1 ? r: d; // Must go once or twice to the bottom
// As long as we have column to then change y coordinate to the new lower line
for (let y = r; posY < sizeY; y += RorD * Math.sin(a)) {
posX = 0;
for (
let x = r, j = 0; // Reset cursor x
posX < sizeX; // As long as we still have some column in line
// Do the zigzag magic between hexagon of a line
x += r * (1 + Math.cos(a)),
y += (-1) ** j++ * r * Math.sin(a)
) {
// Get the hexadecimal HTML5 color and draw the shape
drawHexagon(ctx, x, y, getColor(data, sizeX, posX, posY));
posX++; // Update current coordinate X
}
posY++; // Update current coordinate Y
}
}
// Paint our new image into our working canvas
drawGrid(ctx, originalImageData.data, canvas.width, canvas.height, originalImageData.width, originalImageData.height);
// Paint the working canvas into a new one with a ratio that is not distorted
// Since hexagon are placed asymmetrically in columns and lines, we have to flatten the height a bit
const canvas2 = document.createElement("canvas");
canvas2.width = canvas.width; // The width doesn't change
// Yet we apply the same mathematical operation of the one used for the width yet for the height
canvas2.height = originalImageData.height * d - (Math.floor(originalImageData.height/2)*r);
const ctx2 = canvas2.getContext('2d');
// Draw the image with a correct ratio
ctx2.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, canvas2.width, canvas2.height);
// Return the new image data
return ctx2.getImageData(0, 0, canvas2.width, canvas2.height);
}
// This function return a promise that return the up scaled image data
function hexagonrender(image_data, scale, pool) {
return new Promise( function(resolve, reject){
resolve(generateFinalBase64(image_data, scale));
});
}
module.exports = { hexagonrender: hexagonrender };
Top comments (0)