<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>f20x</title><description>a blog about random web development topics</description><link>https://f20x.com</link><item><title>Demystifying the Random Avatar Generator: A JavaScript Code Analysis</title><link>https://f20x.com/posts/random-pattern-generator</link><guid isPermaLink="true">https://f20x.com/posts/random-pattern-generator</guid><description>A beginner-friendly breakdown of the JavaScript patterns behind a random avatar generator.</description><pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Have you ever wondered how websites generate those unique, pixelated default profile pictures for new users? They might look like simple geometric shapes, but under the hood, there is a fascinating mix of mathematics, software design, and web technologies at play.&lt;/p&gt;
&lt;p&gt;This article analyzes a specific open-source library, the &lt;a href=&quot;https://github.com/fractalsoftware/random-avatar-generator&quot;&gt;&lt;strong&gt;Random Avatar Generator&lt;/strong&gt;&lt;/a&gt;, created by &lt;em&gt;Fractal Software&lt;/em&gt;. Designed as part of an academic publication and a side project, this library serves an educational purpose: to illustrate different aspects of JavaScript&apos;s capabilities and general software development concepts.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;fractalsoftware/random-avatar-generator&quot;}&lt;/p&gt;
&lt;p&gt;The tool generates random pixel-pattern avatars with a focus on &lt;strong&gt;low collision&lt;/strong&gt; (ensuring you rarely get the same avatar twice) and &lt;strong&gt;zero external dependencies&lt;/strong&gt; (meaning it doesn&apos;t rely on massive third-party libraries). Let&apos;s dive into the patterns, programming aspects, and computer science concepts this elegant script employs.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Separation of Concerns (Architecture)&lt;/h2&gt;
&lt;p&gt;A fundamental principle in computer science is &lt;strong&gt;Separation of Concerns (SoC)&lt;/strong&gt;—dividing a computer program into distinct sections so each section addresses a separate concern. In this library, the creator explicitly divided the avatar creation process into two distinct phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Data Generation&lt;/strong&gt; (&lt;code&gt;generateRandomAvatarData&lt;/code&gt;): This method computes the math and logic to randomly determine which &quot;pixels&quot; are turned on or off and what colors they should be. It outputs a lightweight string of data (e.g., &lt;code&gt;0-6-6te25-9d9p0-xd5g&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rendering&lt;/strong&gt; (&lt;code&gt;getAvatarFromData&lt;/code&gt;): This method takes the data string and translates it into an actual visual graphic (an SVG image).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why is this a good pattern?&lt;/strong&gt; Because the application state (the data string) is separate from the user interface (the SVG). If you want to save a user&apos;s generated avatar in a database, you don&apos;t need to save a massive image file; you just save the tiny string &lt;code&gt;0-6-6te25-9d9p0-xd5g&lt;/code&gt; and re-render it whenever needed! To make things easier for developers who just want a quick image, the author also includes a helper function called &lt;code&gt;getRandomAvatar()&lt;/code&gt; that combines both steps.&lt;/p&gt;
&lt;h2&gt;2. The Strategy Pattern for Rendering&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Strategy Pattern&lt;/strong&gt; is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate function, and make their objects interchangeable.&lt;/p&gt;
&lt;p&gt;The library includes an API parameter called &lt;code&gt;renderMethod&lt;/code&gt;. By default, the avatar uses a &lt;code&gt;square&lt;/code&gt; rendering strategy, but developers can change it to a &lt;code&gt;circle&lt;/code&gt; strategy. Even better, the library accepts a &lt;strong&gt;custom callback function&lt;/strong&gt; to draw entirely new shapes.&lt;/p&gt;
&lt;p&gt;For example, a developer can pass a custom function to draw triangles instead of squares:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const drawTriangle = (resolution, indexX, indexY) =&amp;gt; {
   return `M${indexX * resolution + resolution / 2},${indexY * resolution} l${ resolution / 2 } ${resolution} l-${resolution} 0z`;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a beginner, this is a prime example of &lt;strong&gt;Polymorphism&lt;/strong&gt; and &lt;strong&gt;High Extensibility&lt;/strong&gt;. The core engine of the library doesn&apos;t need to know &lt;em&gt;how&lt;/em&gt; to draw a triangle; it just delegates the drawing logic to whatever function you provide it.&lt;/p&gt;
&lt;h2&gt;3. Dealing with &quot;Collision&quot; (Probability and Math)&lt;/h2&gt;
&lt;p&gt;In computer science, a &quot;collision&quot; happens when two different inputs produce the exact same output. In the context of an avatar generator, a collision means two different users randomly getting the exact same profile picture.&lt;/p&gt;
&lt;p&gt;To solve this, the script relies heavily on matrix generation and probability manipulation. According to the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&quot;In order to have bigger numeric space to prevent collision, each row within the matrix has a random color number.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By associating randomization not just with the coordinates (X and Y axes), but also injecting color variation at the row level, the total permutations (the number of possible unique avatars) skyrocket. The strings generated (like &lt;code&gt;6te25&lt;/code&gt;) represent integers converted into alphanumeric base formats (like base-36 or hexadecimal). Base conversion is a highly effective way to compress large numerical values into short, readable strings.&lt;/p&gt;
&lt;h2&gt;4. Scalable Vector Graphics (SVG) and Template Literals&lt;/h2&gt;
&lt;p&gt;Instead of using an HTML Canvas element or generating an image file (like a PNG or JPG), the script generates &lt;strong&gt;SVG (Scalable Vector Graphics)&lt;/strong&gt; code.&lt;/p&gt;
&lt;p&gt;SVG is an XML-based markup language used for describing two-dimensional vector graphics. Since SVGs are built using mathematical formulas (lines, paths, and curves) instead of fixed pixels, they can scale to any size without losing quality.&lt;/p&gt;
&lt;p&gt;The library uses modern JavaScript &lt;strong&gt;Template Literals&lt;/strong&gt; (strings wrapped in backticks &lt;code&gt;`&lt;/code&gt;) to dynamically inject variables directly into the SVG path strings:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`&amp;lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;100%&quot; height=&quot;100%&quot; viewBox=&quot;0 0 ${size} ${size}&quot;&amp;gt;...`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For entry-level developers, manipulating DOM elements or drawing directly via JavaScript can be cumbersome. Building a raw markup string and returning it is often a faster, more flexible approach that works identically on a Web Browser, inside a Node.js server, or inside a React component.&lt;/p&gt;
&lt;h2&gt;5. Functional Default Parameters&lt;/h2&gt;
&lt;p&gt;Another modern JavaScript concept used across the library&apos;s API is the use of &lt;strong&gt;Default Parameters&lt;/strong&gt;. Functions like &lt;code&gt;generateRandomAvatarData&lt;/code&gt; and &lt;code&gt;getAvatarFromData&lt;/code&gt; have fallback values if the user decides not to configure them.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;complexity&lt;/code&gt;: Defaults to &lt;code&gt;16&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;renderMethod&lt;/code&gt;: Defaults to &lt;code&gt;&quot;square&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;: Defaults to &lt;code&gt;256&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This represents a design philosophy called &lt;strong&gt;Convention over Configuration&lt;/strong&gt;. It means the library works flawlessly out of the box with sensible defaults, while still allowing power-users to open the hood and tinker with the parameters.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Playground&lt;/h2&gt;
&lt;p&gt;You can see this application in action, an generate your own patterns here: &lt;a href=&quot;https://morra.co/random-avatar-generator/&quot;&gt;Random Avatar Generator&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What appears to be a simple script for rendering colorful boxes is actually a rich educational playground. By reading through scripts like the Random Avatar Generator, computer science students and junior developers can see practical implementations of architectural decoupling, base-conversions, strategy patterns, and clever utilization of native web capabilities like SVG. Next time you encounter a randomly generated default avatar, you&apos;ll know exactly what kind of software magic is happening behind the scenes!&lt;/p&gt;
</content:encoded><author>Manuel Herrera</author></item><item><title>How (and why) I vibe-coded an app to test Markov chains for image generation</title><link>https://f20x.com/posts/markov-chain-vibe-coded-app</link><guid isPermaLink="true">https://f20x.com/posts/markov-chain-vibe-coded-app</guid><description>Exploring how to build fast applications with modern AI tools. A personal approach.</description><pubDate>Tue, 05 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some days ago, binge-watching on YouTube, I &quot;found&quot; the latest video from the famous channel &lt;a href=&quot;https://www.youtube.com/@veritasium&quot;&gt;Veritasium&lt;/a&gt; about the mathematical concept called Markov chains. Of course, its catchy (clickbait) title, &lt;a href=&quot;https://www.youtube.com/watch?v=KZeIEiBrT_w&quot;&gt;The Strange Math That Predicts (Almost) Anything&lt;/a&gt;, engaged me immediately. After half an hour engaged by its storytelling and well produced content, I wanted to test the concepts exposed there.&lt;/p&gt;
&lt;p&gt;I must admit that I believed that, before watching that video, I understood what is a Markov chain. Wrong. The simplicity of the math behind left me surprised and fully motivated to experiment in a field that I always find exciting: image manipulation.&lt;/p&gt;
&lt;h2&gt;A proof of concept&lt;/h2&gt;
&lt;p&gt;Initially, I wanted to iterate on a simple command-line method that produces a sequence of characters that can be used as &quot;pixels&quot;. ASCII characters came to help: &lt;code&gt;[&apos; &apos;, &apos;░&apos;, &apos;▒&apos;, &apos;▓&apos;, &apos;█&apos;, &apos;▄&apos;, &apos;▀&apos;]&lt;/code&gt;. Now, with probabilities matrix, the work here is only iterate over a given number of steps and, with a random seed, check the probability of an ocurrency:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function markov(steps = 1000) {
    const states = [&apos; &apos;, &apos;░&apos;, &apos;▒&apos;, &apos;▓&apos;, &apos;█&apos;, &apos;▄&apos;, &apos;▀&apos;];
    const transitionProbabilities = {
        [states[0]]: { [states[0]]: 0.9, [states[1]]: 0.0, [states[2]]: 0.0, [states[3]]: 0.0, [states[4]]: 0.1, [states[5]]: 0.0, [states[6]]: 0.0 },
        [states[1]]: { [states[0]]: 0.1, [states[1]]: 0.9, [states[2]]: 0.0, [states[3]]: 0.0, [states[4]]: 0.9 , [states[5]]: 0.0, [states[6]]: 0.9 },
        [states[2]]: { [states[0]]: 0.0, [states[1]]: 0.1, [states[2]]: 0.9, [states[3]]: 0.0, [states[4]]: 0.0 , [states[5]]: 0.1, [states[6]]: 0.0 },
        [states[3]]: { [states[0]]: 0.0, [states[1]]: 0.0, [states[2]]: 0.1, [states[3]]: 0.9, [states[4]]: 0.1 , [states[5]]: 0.1, [states[6]]: 0.0 },
        [states[4]]: { [states[0]]: 0.0, [states[1]]: 0.0, [states[2]]: 0.0, [states[3]]: 0.0, [states[4]]: 0.0 , [states[5]]: 0.1, [states[6]]: 0.1},
        [states[5]]: { [states[0]]: 0.1, [states[1]]: 0.0, [states[2]]: 0.0, [states[3]]: 0.9, [states[4]]: 0.0 , [states[5]]: 0.1, [states[6]]: 0.0},
        [states[6]]: { [states[0]]: 0.0, [states[1]]: 0.1, [states[2]]: 0.0, [states[3]]: 0.0, [states[4]]: 0.1 , [states[5]]: 0.0, [states[6]]: 0.1},
    }

    let currentState = states[4];
    let accumulator = currentState;

    while (steps &amp;gt; 0) {
        const nextState = states[Math.floor(Math.random() * states.length)];
        const probability = transitionProbabilities[currentState][nextState];
        if (Math.random() &amp;lt; probability) {
            if (steps % 90 === 0) {
                accumulator += &apos;\n&apos;;
            }
            currentState = nextState;
            accumulator += currentState;
            steps--;
        }
    }

    return accumulator;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result? A promising nice pattern like this one:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./naivemarkovimage.webp&quot; alt=&quot;Pattern from Markov chain #pixelated&quot; title=&quot;Output pattern from the Markov chain generator&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Learning (machines)&lt;/h2&gt;
&lt;p&gt;But, the interesting part of Markov chains is their ability to learn from previous sources. In the previous example, the &lt;code&gt;transitionsProbabilities&lt;/code&gt; matrix was deliberated, tunned manually to obtain similar patterns. What does it means? We need to provide a way to build or calculate that matrix from an initial source. A new method must be introduced to perform that calculations. Additionally, it required to evaluate the probabilities from an external image or a set of images.&lt;/p&gt;
&lt;p&gt;As the previous example showed, we have a limited set of &quot;colors&quot;, 7 in total. Reducing the number of colors of the source image will improve the speed of processing and creation of the output image.&lt;/p&gt;
&lt;p&gt;The final matrix will be a simplified representation of the current image, keeping the general structure and probabilities of the original image.&lt;/p&gt;
&lt;p&gt;I wanted an interactive application that can be deployed in any static server: basic HTML, JavaScript and CSS.&lt;/p&gt;
&lt;p&gt;This list of restictions will be a good starting point to develop a more robust solution, but requires going deeper in the following aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Image manipulation using the Canvas API.&lt;/li&gt;
&lt;li&gt;Color reduction algorithm.&lt;/li&gt;
&lt;li&gt;Markov chains learning algorithm.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Vibe prompting&lt;/h2&gt;
&lt;p&gt;As something that I wanted to explore quick, I decided to use some generative AI to create this app faster. But as many knows, a good prompt is the key of a good result. Why not use the same tool to create a well documented set of instructions to generate the expected application? Using &lt;a href=&quot;https://gemini.google.com/&quot;&gt;Gemini&lt;/a&gt; 2.5 flash, I asked for:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;write a detailed document that instructs an AI to develop an application in javascript that, using the Markov chains concept, learns from a set of base images, subscaling and reducing their colors to a user defined total. Then, with the obtained probabilities, generates a new image&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The result: an impressive and highly detailed, a thousand words long document that details the requirements of this application: &lt;a href=&quot;https://github.com/manuelhe/markov-image-generator/blob/main/ai-instructions.md&quot;&gt;ai-instructions.md&lt;/a&gt;. It went way beyond my expectations, describing the HTML document, a general requirement for the styles, and the structure of the JavaScript file. But it makes a strong emphasis describing the inner process of the application split in three sections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Image Preprocessing (Subscaling and Color Reduction)&lt;/li&gt;
&lt;li&gt;Markov Chain Learning&lt;/li&gt;
&lt;li&gt;Image Generation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each step has a brief description and a set of instructions for the AI, e.g.,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Objective&lt;/strong&gt;: Implement the logic to load images, resize them, and reduce their color palette to a user-defined number of colors.
&amp;lt;br&amp;gt;&lt;strong&gt;Instructions for AI&lt;/strong&gt;:
&amp;lt;br&amp;gt;1. Image Loading and Subscaling (&lt;code&gt;processImages&lt;/code&gt; function):
&amp;lt;br&amp;gt;- When the &quot;Process Images &amp;amp; Learn&quot; button is clicked, iterate through the selected files from the file input...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some instructions offers different alternatives, but makes a final decision at the end,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pixel Traversal&lt;/strong&gt;: For each image, traverse its pixels. Consider different neighborhood relationships for the Markov chain:
&amp;lt;br&amp;gt;- &lt;strong&gt;Option 1 (Simple - 1D)&lt;/strong&gt;: Learn probabilities based on a pixel and its right neighbor (&lt;code&gt;(x,y)&lt;/code&gt; to &lt;code&gt;(x+1,y)&lt;/code&gt;). This is simpler but might produce less cohesive images.
&amp;lt;br&amp;gt;- &lt;strong&gt;Option 2 (Slightly More Complex - 2D)&lt;/strong&gt;: Learn probabilities based on a pixel and its right neighbor AND its bottom neighbor (&lt;code&gt;(x,y)&lt;/code&gt; to &lt;code&gt;(x+1,y)&lt;/code&gt; and &lt;code&gt;(x,y)&lt;/code&gt; to &lt;code&gt;(x,y+1)&lt;/code&gt;). This generally produces better results.
&amp;lt;br&amp;gt;- &lt;strong&gt;AI Choice&lt;/strong&gt;: Implement Option 2 for a more robust model.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;or simply mention other possible solutions,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Recommended Algorithm&lt;/strong&gt;: Use a K-means clustering algorithm or a similar perceptual color reduction method (e.g., Octree Quantization if simpler to implement in JS).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other sections, it even presents code suggestions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The structure of markovChainModel should be,&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;color1&quot;: {
        &quot;next_color_1&quot;: probability,
        &quot;next_color_2&quot;: probability,
        // ...
    },
    &quot;color2&quot;: {
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or describes an algorith to solve a particular problem,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Counting Transitions&lt;/strong&gt;:
&amp;lt;br&amp;gt;- For each pixel &lt;code&gt;P1&lt;/code&gt; at &lt;code&gt;(x,y)&lt;/code&gt;:
&amp;lt;br&amp;gt; - Get its color &lt;code&gt;C1&lt;/code&gt;.
&amp;lt;br&amp;gt; - Get the color &lt;code&gt;C2&lt;/code&gt; of its right neighbor &lt;code&gt;P2&lt;/code&gt; at &lt;code&gt;(x+1,y)&lt;/code&gt; (if within bounds).
&amp;lt;br&amp;gt; - Get the color &lt;code&gt;C3&lt;/code&gt; of its bottom neighbor &lt;code&gt;P3&lt;/code&gt; at &lt;code&gt;(x,y+1)&lt;/code&gt; (if within bounds).
&amp;lt;br&amp;gt; - Increment counts in &lt;code&gt;markovChainModel&lt;/code&gt;:
&amp;lt;br&amp;gt; - &lt;code&gt;markovChainModel[C1][C2]&lt;/code&gt; (for horizontal transitions)
&amp;lt;br&amp;gt; - &lt;code&gt;markovChainModel[C1][C3]&lt;/code&gt; (for vertical transitions)
&amp;lt;br&amp;gt;- Handle edge cases (last row/column pixels).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On a following step, called &lt;em&gt;Refinements and Error Handling&lt;/em&gt;, it makes some suggestions related with the user interface, like &lt;em&gt;Input Validation&lt;/em&gt; or &lt;em&gt;User Feedback&lt;/em&gt;. Others related with the &lt;em&gt;Performance Considerations&lt;/em&gt; and &lt;em&gt;Visual Quality Enhancements&lt;/em&gt;. It even dedicate a section to the &lt;em&gt;Code Structure&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Finally, a list of deliverables where, alongside the item, it presents a really brief description of each file. Another deliverable suggested is a documentation (it doesn´t specify an output format), highlighting the use instructions, the technical details behind the Markov chain implementation, and possible limitations of the app.&lt;/p&gt;
&lt;h2&gt;Vibe coding&lt;/h2&gt;
&lt;p&gt;Using the &lt;strong&gt;Canvas&lt;/strong&gt; option enabled in the Gemini web interface, I asked to build the solution:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Using the previous response, generate the described application&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, after a short wait, I got this,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I have created the Markov Chain Image Generation application you described. Below are the complete, self-contained files for the application. You can save these three files (&lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;style.css&lt;/code&gt;, and &lt;code&gt;script.js&lt;/code&gt;) in the same folder and open &lt;code&gt;index.html&lt;/code&gt; in your web browser to run the application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;followed by three files ready to be downloaded. The &lt;strong&gt;Canvas&lt;/strong&gt; option in the Gemini UI, allows the live previsualization of web applications.&lt;/p&gt;
&lt;h3&gt;Mixed results&lt;/h3&gt;
&lt;p&gt;The first iteration returned an almost unusable application, with missing links to the correct Tailwind styles and broken JavaScript methods. after aking to fix it, it came with a decent result that,at the end required manual tweaking in most of its parts.&lt;/p&gt;
&lt;p&gt;Here you can experiment with the manually edited result: &lt;a href=&quot;https://morra.co/markov-image-generator/&quot;&gt;Markov Image Generator &lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;This is a promising technology that will transform in less than a year the way software is developed. Engineers will need a new set of skills required to orchestrate autonomous workers, understanding problems in a macro perspective, know how to write tailored instructions, and be aware of the limitations of the current tools.&lt;/p&gt;
&lt;p&gt;Great times will come, scary but thrilling times!&lt;/p&gt;
</content:encoded><author>Manuel Herrera</author></item><item><title>Shrink Your Stash: Breaking Down the Ultimate WebP Converter Script</title><link>https://f20x.com/posts/bulkwebp-bash-script</link><guid isPermaLink="true">https://f20x.com/posts/bulkwebp-bash-script</guid><description>Hoarding massive images? Let´s dissect a slick Bash script that multi-threads WebP conversions.</description><pubDate>Tue, 17 Mar 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hey there. So, you’ve been hoarding high-res PNGs and JPEGs. Maybe it’s a folder of 4K cat photos, maybe it’s an overblown website assets directory. We’ve all been there. Well, as an Web Developer, I have a physical hard drive that gets bogged down with giant files, and absolutely feel your pain when things run slower than they need to.&lt;/p&gt;
&lt;p&gt;Today, we’re going to take a nice, slow walk through a pretty slick Bash script called &lt;code&gt;bulkwebp&lt;/code&gt; (&lt;a href=&quot;https://gist.github.com/manuelhe/b63057edce8f8f79523a0baf1c399dd7&quot;&gt;source code&lt;/a&gt;). It doesn’t just convert your images to WebP (Google&apos;s highly efficient image format); it throws all your CPU cores at the problem so you aren&apos;t waiting around until the next ice age, and then it gives you a neat little receipt of the megabytes you saved.&lt;/p&gt;
&lt;p&gt;Let’s kick back, grab a beverage, and dissect how this bad boy works, piece by piece.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Setting the Stage (and Finding Your Cores)&lt;/h2&gt;
&lt;p&gt;Right out of the gate, the script establishes some ground rules.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REPLACE=false
RECURSIVE_MODE=false 
CORES=$(nproc 2&amp;gt;/dev/null || sysctl -n hw.ncpu 2&amp;gt;/dev/null || echo 1) 
FILE_TYPE=&quot;&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Safety Nets:&lt;/strong&gt; &lt;code&gt;REPLACE&lt;/code&gt; and &lt;code&gt;RECURSIVE_MODE&lt;/code&gt; default to &lt;code&gt;false&lt;/code&gt;. The script isn&apos;t going to go rogue and delete your original files or dive deep into your forbidden subdirectories unless you explicitly tell it to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Core Sniffer:&lt;/strong&gt; That &lt;code&gt;CORES&lt;/code&gt; line is a thing of beauty. It tries to use &lt;code&gt;nproc&lt;/code&gt; (common on Linux) to see how many CPU cores you have. If that fails, it tries &lt;code&gt;sysctl&lt;/code&gt; (the macOS way). If &lt;em&gt;that&lt;/em&gt; fails, it just throws its hands up and assumes you have &lt;code&gt;1&lt;/code&gt; core. It&apos;s doing the heavy lifting so you don&apos;t have to guess your machine&apos;s specs.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. The Bouncer: Argument Parsing&lt;/h2&gt;
&lt;p&gt;Next up, the script has to figure out what you actually want it to do based on the flags you passed in the terminal.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while [ $# -gt 0 ]; do
  case &quot;$1&quot; in
    -r|--recursive)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;code&gt;while&lt;/code&gt; loop is basically a bouncer at the club, checking IDs. It looks at the first argument (&lt;code&gt;$1&lt;/code&gt;), figures out what flag it is (like &lt;code&gt;-r&lt;/code&gt; or &lt;code&gt;--cores&lt;/code&gt;), sets the internal variable, and then uses &lt;code&gt;shift&lt;/code&gt; to kick that argument out of line so it can check the next one.&lt;/p&gt;
&lt;h2&gt;:::note
Look at the &lt;code&gt;-t|--type&lt;/code&gt; block. It actually strips out a leading dot if you accidentally type &lt;code&gt;-t .png&lt;/code&gt; instead of &lt;code&gt;-t png&lt;/code&gt;. That is some excellent, forgiving UX design right there in a terminal script!
:::&lt;/h2&gt;
&lt;h2&gt;3. The Bloodhound: Building the &lt;code&gt;find&lt;/code&gt; Command&lt;/h2&gt;
&lt;p&gt;Instead of hardcoding how to search for files, the script dynamically builds a &lt;code&gt;find&lt;/code&gt; command based on the flags you set.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Staying shallow:&lt;/strong&gt; If &lt;code&gt;RECURSIVE_MODE&lt;/code&gt; is false, it slaps &lt;code&gt;-maxdepth 1&lt;/code&gt; onto the command so it only looks in the current folder.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Picking targets:&lt;/strong&gt; If you specified a file type, it targets that. Otherwise, it casts a wide net for the usual suspects: &lt;code&gt;.png&lt;/code&gt;, &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;.jpeg&lt;/code&gt;, and &lt;code&gt;.tiff&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end of this section, &lt;code&gt;FIND_CMD&lt;/code&gt; is a fully loaded string ready to sniff out your hefty images.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. The Heavy Lifting: &lt;code&gt;xargs&lt;/code&gt; and Parallel Processing&lt;/h2&gt;
&lt;p&gt;This is where the script goes from &quot;neat&quot; to &quot;absolute beast.&quot;&lt;/p&gt;
&lt;p&gt;Instead of converting files one by one in a slow, agonizing queue, it uses &lt;code&gt;xargs -P &quot;$CORES&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Exporting Variables:&lt;/strong&gt; It uses &lt;code&gt;export REPLACE&lt;/code&gt; so the mini-scripts running in parallel know if they have permission to delete the original files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Payload (&lt;code&gt;SCRIPT&lt;/code&gt;):&lt;/strong&gt; It defines a chunk of code that uses &lt;code&gt;cwebp&lt;/code&gt; to do the actual conversion. It also uses &lt;code&gt;wc -c&lt;/code&gt; to count the exact byte size of the original file and the new WebP file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Secret Messages:&lt;/strong&gt; Notice how it &lt;code&gt;echo&lt;/code&gt;s out things like &lt;code&gt;✅ Converted&lt;/code&gt; but &lt;em&gt;also&lt;/em&gt; outputs a weird line like &lt;code&gt;SIZE_STATS 150000 45000&lt;/code&gt;? That is a hidden data string meant for the next part of our script. It’s like the workers passing notes to the accountant.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;5. The Accountant: &lt;code&gt;awk&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Finally, all this parallel terminal output gets piped (&lt;code&gt;|&lt;/code&gt;) into &lt;code&gt;awk&lt;/code&gt;, which is basically the spreadsheet nerd of the Linux world.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;awk &apos;/^SIZE_STATS/ {
    orig_total += $2
    new_total += $3
    next
}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; sits at the end of the pipeline, reading every line of text that the conversion workers spit out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If it sees a line starting with &lt;code&gt;SIZE_STATS&lt;/code&gt;, it says, &lt;em&gt;&quot;Ah, data!&quot;&lt;/em&gt; It intercepts those numbers, adds them to a running total, and hides the line from your screen (&lt;code&gt;next&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If it sees any other line (like the green checkmarks or the trash can emojis), it just prints it normally so you can watch the progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once all the files are done (the &lt;code&gt;END&lt;/code&gt; block), &lt;code&gt;awk&lt;/code&gt; does some quick math to convert those bytes into Megabytes, calculates the percentage of space you saved, and prints out a beautiful little receipt.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The Verdict&lt;/h2&gt;
&lt;p&gt;This script is a fantastic example of gluing together standard Unix tools (&lt;code&gt;find&lt;/code&gt;, &lt;code&gt;xargs&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;) to create something highly efficient. It respects your time by using parallel processing, and it respects your terminal by giving you a clean, readable summary at the end instead of just vomiting data onto the screen.&lt;/p&gt;
&lt;h2&gt;The Full Script&lt;/h2&gt;
&lt;p&gt;In case you where wondering what I&apos;m talking about, and you missed the link in the introduction, here you have the full script for your delight:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# Default values for flags
REPLACE=false
RECURSIVE_MODE=false # Use a simple boolean for logic
CORES=$(nproc 2&amp;gt;/dev/null || sysctl -n hw.ncpu 2&amp;gt;/dev/null || echo 1) # Get core count
FILE_TYPE=&quot;&quot; # Variable to store the optional file extension

##
# Displays help message
##
show_help() {
cat &amp;lt;&amp;lt; EOF
Usage: bulkwebp [OPTIONS]
Converts images in the directory to WebP format and calculates space saved.

This high-performance version processes files in parallel for maximum speed.

Options:
  -r, --recursive   Recursively search for images in all subdirectories.
  -p, --replace     Delete the original source image after a successful conversion.
  -c, --cores NUM   Specify the number of parallel jobs (default: all available cores).
  -t, --type EXT    Specify a single input file extension to convert (e.g., png, jpg). Defaults to all common formats.
  -h, --help        Display this help message and exit.
EOF
}

# --- Argument Parsing ---
while [ $# -gt 0 ]; do
  case &quot;$1&quot; in
    -r|--recursive)
      RECURSIVE_MODE=true
      shift
      ;;
    -p|--replace)
      REPLACE=true
      shift
      ;;
    -c|--cores)
      CORES=&quot;$2&quot;
      shift 2
      ;;
    -t|--type)
      # Remove any leading dot if the user accidentally typed &quot;-t .png&quot; instead of &quot;-t png&quot;
      FILE_TYPE=&quot;${2#.}&quot; 
      shift 2
      ;;
    -h|--help)
      show_help
      exit 0
      ;;
    *)
      echo &quot;Error: Unknown option &apos;$1&apos;&quot; &amp;gt;&amp;amp;2
      show_help
      exit 1
      ;;
  esac
done

# --- Build find command ---
# Start with the base command
FIND_CMD=&quot;find .&quot;

# Add maxdepth option ONLY if not in recursive mode
if [ &quot;$RECURSIVE_MODE&quot; = &quot;false&quot; ]; then
    FIND_CMD+=&quot; -maxdepth 1&quot;
fi

# Add the file type and name patterns dynamically
if [ -n &quot;$FILE_TYPE&quot; ]; then
    # If a specific type is provided, only look for that extension
    FIND_CMD+=&quot; -type f -iname &apos;*.$FILE_TYPE&apos; -print0&quot;
else
    # Default behavior: look for standard image formats
    FIND_CMD+=&quot; -type f \( -iname &apos;*.png&apos; -o -iname &apos;*.jpg&apos; -o -iname &apos;*.jpeg&apos; -o -iname &apos;*.tiff&apos; \) -print0&quot;
fi

# --- Main Execution ---
# Export the REPLACE variable so it&apos;s available to the subshells created by xargs
export REPLACE

# The `sh -c &apos;...&apos;` script that will be executed by xargs for each file
SCRIPT=&apos;
    filepath=&quot;$1&quot;
    outfile=&quot;${filepath%.*}.webp&quot;
    
    # Extract original size in bytes (cross-platform friendly)
    orig_size=$(wc -c &amp;lt; &quot;$filepath&quot;)
    
    if cwebp -quiet &quot;$filepath&quot; -o &quot;$outfile&quot;; then
        # Extract new size in bytes
        new_size=$(wc -c &amp;lt; &quot;$outfile&quot;)
        
        echo &quot;✅ Converted: $outfile&quot;
        # Output a hidden data string for awk to intercept and calculate
        echo &quot;SIZE_STATS $orig_size $new_size&quot;
        
        if [ &quot;$REPLACE&quot; = &quot;true&quot; ]; then
            rm &quot;$filepath&quot;
            echo &quot;🗑️  Removed:   $filepath&quot;
        fi
    else
        echo &quot;❌ Failed:    $filepath&quot; &amp;gt;&amp;amp;2
    fi
&apos;

# Execute the command using a pipe and xargs for parallel processing.
eval &quot;$FIND_CMD&quot; | xargs -0 -P &quot;$CORES&quot; -I {} sh -c &quot;$SCRIPT&quot; _ {} | awk &apos;
/^SIZE_STATS/ {
    orig_total += $2
    new_total += $3
    next # Skip printing this specific line
}
{ print } # Print all other lines (like ✅ Converted or 🗑️ Removed)
END {
    if (orig_total &amp;gt; 0) {
        saved = orig_total - new_total
        percent = (saved / orig_total) * 100
        
        printf &quot;\n--------------------------------------\n&quot;
        printf &quot;📊 Total Original Size: %.2f MB\n&quot;, orig_total / 1048576
        printf &quot;📊 Total WebP Size:     %.2f MB\n&quot;, new_total / 1048576
        if (saved &amp;gt; 0) {
            printf &quot;🎉 Total Space Saved:   %.2f MB (%.1f%% reduction)\n&quot;, saved / 1048576, percent
        } else {
            printf &quot;⚠️ Space Increased by:   %.2f MB\n&quot;, (new_total - orig_total) / 1048576
        }
        printf &quot;--------------------------------------\n&quot;
    } else {
        print &quot;\nNo images were found or converted.&quot;
    }
}&apos;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>Manuel Herrera</author></item></channel></rss>