DEV Community

ViperT
ViperT

Posted on

Cracking JS Hexagons πŸ”πŸ€”

Author: Affolter Matias

Image description

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 };
Enter fullscreen mode Exit fullscreen mode

Image description

Top comments (0)