Javascript abstract art! Click on the canvas for another masterpiece.
The current implementation uses a recursive function to split the canvas up into mini boxes. This is then ajusted to add a random factor in splitting and corrected to ensure that the boxes are of a minimum size. Start with the render()
function and then move forwards/backwards.
// globals const isDebug = false, minWidth = 20, minHeight = 20, aspect = 3; // react specific // setup a reference to the canvas element // calculate width, height and dynamically update if required const ref = useRef<HTMLCanvasElement>(null); const [width, setWidth] = useState(20); const [height, setHeight] = useState(20); const updateWidth = useCallback(() => { if (ref && ref.current) { const { width, height } = ref.current.parentElement!.getBoundingClientRect(); console.log([width, height]); let targetWidth = width; targetWidth = (targetWidth / 10) * 10; let targetHeight = Math.round(targetWidth / aspect); targetHeight = (targetHeight / 10) * 10; targetHeight = 300; // override setWidth(targetWidth); setHeight(targetHeight); } }, [ref]); useEffect(() => { updateWidth(); window.addEventListener("resize", updateWidth); return () => { window.removeEventListener("resize", updateWidth); }; }, [updateWidth]); useEffect(() => { render(width, height); }, [width, height]); // generic function, renders the art const render = function (width: number, height: number) { var canvas: HTMLCanvasElement = document.getElementById("artist-canvas")! as HTMLCanvasElement; // canvas.setAttribute("width", String(width)); // canvas.setAttribute("height", String(height)); var cWidth = width; var cHeight = height; var depth = 4; console.log([cWidth, cHeight, depth]); if (canvas.getContext) { var ctx = canvas.getContext("2d")!; ctx.clearRect(0, 0, cWidth, cHeight); try { splitGrid(ctx, depth, 0, 0, cWidth, cHeight); } catch (err) { console.error(err); } } }; /** * Split a given bounding box width-wise randomly and * for each width split, create 2 vertical bounding boxes. * * Depending on the depth, recursively call to split the grid further * or simply fill the bounding box using a similar algorithm */ var splitGrid = function(ctx, depth, gx, gy, cWidth, cHeight) { if (depth % 2 !== 0 || depth < 2) { throw new Error('depth needs to be a multiple of 2 (min 2)'); } var x = 0, y = 0, width = 0, height = 0; // // start at 0 and then loop until the width has been reached, splitting on each loop // // note that while the algorithm works on x, y, width and height as a bounding box // the actual values that need to be passed along need to be corrected via gx and gy // so that the final drawing on the context knows where to go. // while (x < cWidth) { // reset y to 0, we split our box vertically only once y = 0; // get the minimum width (relative to the depth) // then correct for overflow to keep it smooth // also need to check for shady widths if (cWidth <= self.minWidth * depth) { width = cWidth; } else { width = chance.integer({ min: self.minWidth * depth, max: cWidth }); width = width - (width % (self.minWidth * depth)); } // correct for the last box in the series if (x + width > cWidth) { width = cWidth - x; } // similar concept with height except no last box correction required as we only split it once if (cHeight <= self.minHeight * depth) { height = cHeight; } else { height = chance.integer({ min: self.minHeight * depth, max: cHeight }); height = height - (height % (self.minHeight * depth)); } if (depth <= 2) { fillGrid(ctx, x + gx, y + gy, width, height); } else { splitGrid(ctx, depth - 2, x + gx, y + gy, width, height); } // simple split twice y = height; height = cHeight - height; if (height != 0) { if (depth <= 2) { fillGrid(ctx, x + gx, y + gy, width, height); } else { splitGrid(ctx, depth - 2, x + gx, y + gy, width, height); } } x += width; } } /** * Use a similar algorithm as fill grid to randomly split the bounding box width-wise * and draw rectangles (2 per width split again) */ var fillGrid = function(ctx, gx, gy, cWidth, cHeight) { if (cWidth <= 0 || cHeight <= 0) { return; } var x = 0, y = 0, width = 0, height = 0; while (x < cWidth) { y = 0; // because we have a dynamically sized canvas, it is possible to end up with shady widths and heights if (cWidth <= minWidth) { width = cWidth; } else { width = chance.integer({ min: minWidth, max: cWidth }); width = width - (width % minWidth); } if (x + width > cWidth) { width = cWidth - x; } if (cHeight <= minHeight) { height = cHeight; } else { height = chance.integer({ min: minHeight, max: cHeight }); height = height - (height % minHeight); } ctx.fillStyle = getColor(); ctx.fillRect(x + gx, y + gy, width, height); y = height; height = cHeight - height; ctx.fillStyle = getColor(); ctx.fillRect(x + gx, y + gy, width, height); x += width; } } var getColor = function() { var r = chance.integer({ min: 1, max: 255 }), g = chance.integer({ min: 1, max: 255 }), b = chance.integer({ min: 1, max: 255 }); var color = 'rgb(' + r + ',' + g + ',' + b + ')'; return color; }
See also JavaScript Art: Triangles for a simpler concept.
Feel free to send me an email with a screenshot of your favorite canvas :)