Cracking JS Hexagons 🔍🤔
Tags: javascript, beginners, webdev, tutorial
Published: 2023-10-20 17:59:26

**Author**: Affolter Matias


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7vvnwih4tols96ov8rnz.png)

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"](https://eperezcosano.github.io/hex-grid/)

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.

```JavaScript
/* 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 };
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40wgrtzwwakue0glk9o2.png)