<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Madhura Ravishan</title>
    <description>The latest articles on DEV Community by Madhura Ravishan (@madhura_ravishan).</description>
    <link>https://dev.to/madhura_ravishan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3982212%2Fc74e9734-f945-485d-8528-4a8a55e39d6c.jpg</url>
      <title>DEV Community: Madhura Ravishan</title>
      <link>https://dev.to/madhura_ravishan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/madhura_ravishan"/>
    <language>en</language>
    <item>
      <title>Painting the Terminal: Building a True-Color ASCII Art Generator in Python</title>
      <dc:creator>Madhura Ravishan</dc:creator>
      <pubDate>Sat, 13 Jun 2026 10:53:37 +0000</pubDate>
      <link>https://dev.to/madhura_ravishan/painting-the-terminal-building-a-true-color-ascii-art-generator-in-python-2abj</link>
      <guid>https://dev.to/madhura_ravishan/painting-the-terminal-building-a-true-color-ascii-art-generator-in-python-2abj</guid>
      <description>&lt;h1&gt;
  
  
  Painting the Terminal: Building a True-Color ASCII Art Generator in Python
&lt;/h1&gt;

&lt;p&gt;We've all seen ASCII art—those nostalgic, text-based images that hark back to the early days of computing. But usually, they are monochromatic and a bit flat. What if we could bring them into the modern era by injecting true RGB colors directly into the terminal window?&lt;/p&gt;

&lt;p&gt;I recently built a Terminal True-Color ASCII Art Generator using Python, and I wanted to share the engineering mindset behind it. In this deep dive, we'll explore the implementation flow, the logic behind mapping pixels to text, and how we can hack the terminal using ANSI escape sequences.&lt;/p&gt;

&lt;p&gt;If you are looking for a fun weekend project that touches on image processing, basic math, and terminal rendering, grab a coffee, and let's dive in. ☕&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;To keep things lightweight, I relied mostly on Python's built-in modules alongside one external library for image handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3&lt;/li&gt;
&lt;li&gt;Pillow (PIL) for image processing&lt;/li&gt;
&lt;li&gt;os, sys, time (Built-in)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. The Math: Fixing the Aspect Ratio
&lt;/h2&gt;

&lt;p&gt;The very first hurdle in converting an image to ASCII is the aspect ratio. Pixels in a digital image are perfectly square (1:1). Terminal characters, however, are not. They are typically about twice as tall as they are wide.&lt;/p&gt;

&lt;p&gt;If you map one pixel to one character directly, your resulting ASCII art will look distorted and vertically stretched—like looking in a funhouse mirror.&lt;/p&gt;

&lt;p&gt;To fix this, we have to compress the image vertically before we even start converting it. Here is the core logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resize_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="c1"&gt;# Adjusting the ratio by dividing by 2.2 to counter terminal font height
&lt;/span&gt;    &lt;span class="n"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.2&lt;/span&gt;  
    &lt;span class="n"&gt;new_height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;new_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_height&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By calculating the standard ratio and explicitly dividing it by 2.2 (a magic number that closely matches most default terminal line-heights), we ensure the final output retains the visual proportions of the original photograph.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Brightness Mapping: From Pixels to Characters
&lt;/h2&gt;

&lt;p&gt;Once the image is properly sized, we need to convert the visual density of the image into text. The core idea is simple: Darker pixels become dense characters (like @), and lighter pixels become sparse characters (like .).&lt;/p&gt;

&lt;p&gt;First, we define our palette, sorted from darkest to lightest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ASCII_CHARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we loop through every pixel in our resized image. For each pixel, we extract the Red, Green, and Blue (RGB) values and calculate its average brightness:&lt;/p&gt;

&lt;p&gt;Brightness = (R + G + B) / 3&lt;/p&gt;

&lt;p&gt;This gives us a grayscale value between 0 and 255. We then map this value to an index in our ASCII_CHARS array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getpixel&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ASCII_CHARS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ASCII_CHARS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: We divide by 256 to ensure our index stays perfectly within the bounds of the array length.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Magic: ANSI Escape Sequences
&lt;/h2&gt;

&lt;p&gt;If we stopped at the previous step, we'd have a perfectly fine grayscale ASCII image. But we promised true color.&lt;/p&gt;

&lt;p&gt;Terminals don't process standard HTML/CSS color codes. Instead, modern terminals use ANSI escape codes—special strings of characters that tell the terminal emulator how to format the text that follows.&lt;/p&gt;

&lt;p&gt;To print true 24-bit RGB colors in the terminal, the sequence looks like this:&lt;br&gt;
&lt;code&gt;\033[38;2;{r};{g};{b}m&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's break that down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;\033[&lt;/code&gt; : The escape character that tells the terminal "a formatting command is coming."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;38;2;&lt;/code&gt; : The specific code telling the terminal we are defining a foreground true-color RGB value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{r};{g};{b}&lt;/code&gt; : Our exact pixel colors injected into the string.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m&lt;/code&gt; : The end of the formatting sequence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wrap our chosen ASCII character in this sequence, and follow it up with a reset code (&lt;code&gt;\033[0m&lt;/code&gt;) so the color doesn't bleed into the next character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;color_char&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[38;2;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s"&gt;[0m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. The Final Implementation Flow
&lt;/h2&gt;

&lt;p&gt;When we put it all together, the flow is beautifully sequential:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the target JPG/PNG using Pillow.&lt;/li&gt;
&lt;li&gt;Resize the image, applying our / 2.2 math to correct the vertical stretch.&lt;/li&gt;
&lt;li&gt;Iterate through the Y (rows) and X (columns) coordinates of the pixels.&lt;/li&gt;
&lt;li&gt;Calculate brightness, select the character, and inject the original pixel's RGB data into an ANSI string.&lt;/li&gt;
&lt;li&gt;Print the final string, flushing the standard output for a smooth rendering effect.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  💡 Why this matters
&lt;/h2&gt;

&lt;p&gt;It's easy to blindly write code ("vibe coding") without understanding what is happening under the hood. Taking the time to parse out why a terminal stretches text, or how RGB array math translates to a visual palette, makes you a better engineer. It bridges the gap between high-level logic and low-level rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  🇱🇰 සිංහල පැහැදිලි කිරීම (Sinhala Explanation)
&lt;/h2&gt;

&lt;p&gt;මෙම ව්‍යාපෘතිය පිටුපස ඇති ඉංජිනේරුමය සංකල්පය (Engineering Mindset) සරලව තේරුම් ගනිමු:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aspect Ratio ගැටළුව:&lt;/strong&gt; සාමාන්‍යයෙන් අපි රූපයක් (Image) Terminal එකක මුද්‍රණය කරන්න හැදුවොත් එය දික් වෙලා (vertically stretched) පෙනෙනවා. මීට හේතුව Terminal එකේ අකුරක (character) පළලට වඩා උස වැඩි වීමයි. මෙය නිවැරදි කිරීමට අපි රූපයේ උස 2.2 න් බෙදා Aspect Ratio එක නිවැරදිව සකස් කරගන්නවා.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pixel එකක් අකුරක් බවට පත් කිරීම:&lt;/strong&gt; ඉන්පසු සෑම Pixel එකකම RGB අගයන්හි සාමාන්‍යය (R+G+B)/3 ගෙන, අඳුරු ස්ථාන සඳහා @, # වැනි ඝනකම් අකුරුත්, දීප්තිමත් ස්ථාන සඳහා ., , වැනි තුනී අකුරුත් ආදේශ කරනවා.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ANSI කේත මගින් වර්ණ ගැන්වීම:&lt;/strong&gt; වැදගත්ම කොටස තමයි Terminal එකට සැබෑ වර්ණ (True Colors) ලබා දීම. මේ සඳහා සාමාන්‍ය HTML/CSS වෙනුවට ANSI escape codes (&lt;code&gt;\033[38;2;R;G;Bm&lt;/code&gt;) භාවිතා කර, Pixel එකේ සැබෑ වර්ණයම අකුරට ලබා දෙනවා.&lt;/p&gt;

&lt;p&gt;නිකම්ම අන්තර්ජාලයෙන් කේතයක් (code) කොපි කරනවාට වඩා, මේ ආකාරයට පසුබිම් තර්කය (underlying logic) සහ ගණිතමය සංකල්ප අවබෝධ කර ගැනීම සාර්ථක මෘදුකාංග ඉංජිනේරුවරයෙකු වීමට ඉතා වැදගත්.&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Demystifying "Vibe Coding": How I Built a Real-Time Neon Gesture Tracker in the Browser</title>
      <dc:creator>Madhura Ravishan</dc:creator>
      <pubDate>Sat, 13 Jun 2026 06:56:47 +0000</pubDate>
      <link>https://dev.to/madhura_ravishan/demystifying-vibe-coding-how-i-built-a-real-time-neon-gesture-tracker-in-the-browser-3gfh</link>
      <guid>https://dev.to/madhura_ravishan/demystifying-vibe-coding-how-i-built-a-real-time-neon-gesture-tracker-in-the-browser-3gfh</guid>
      <description>&lt;p&gt;I'm a first-year IT undergrad at the University of Moratuwa, and lately, I've been diving into the world of AI-assisted development (often called "vibe coding"). It's incredibly powerful to prompt an AI and watch it generate a complex application. But there's a trap: if you don't understand the code it generates, you can't debug it, optimize it, or truly call yourself its creator.&lt;/p&gt;

&lt;p&gt;Recently, I built &lt;strong&gt;Neon Bridge&lt;/strong&gt;, a real-time web app that tracks my hands through my webcam and draws glowing neon lines connecting my fingertips. It even detects when I "pinch" my fingers together and turns the neon red.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4emrailts5jx1yeuyqzt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4emrailts5jx1yeuyqzt.gif" alt="Neon Bridge Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A quick look at the Neon Bridge in action.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It looks like magic, but I wanted to break down exactly &lt;em&gt;how&lt;/em&gt; it works under the hood so I (and hopefully you!) can understand the actual computer science behind the visual effects.&lt;/p&gt;

&lt;p&gt;Here is how you build a real-time gesture tracker in the browser, broken down into three core concepts: The Eyes, The Brain, and The Illusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Eyes (The Machine Learning Pipeline)
&lt;/h2&gt;

&lt;p&gt;Browsers can't inherently "see." To make the app recognize hands, I used &lt;strong&gt;Google's MediaPipe&lt;/strong&gt;. It's a pre-trained machine learning model that analyzes a video frame and outputs an array of 21 specific 3D coordinates (landmarks) for each detected hand.&lt;/p&gt;

&lt;p&gt;The biggest challenge? Running a neural network in JavaScript 60 times a second will usually freeze the browser.&lt;/p&gt;

&lt;p&gt;The solution in the code is using &lt;strong&gt;WebAssembly (Wasm)&lt;/strong&gt; and explicitly offloading the processing to the device's graphics card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GPU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;runningMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIDEO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the heavy tensor operations happen on the GPU, leaving the main JavaScript thread free to keep the UI smooth.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Brain (The Geometry Engine)
&lt;/h2&gt;

&lt;p&gt;How does the app know when I am "pinching"? It doesn't. Machine learning just gives us dots; the rest is pure mathematics.&lt;/p&gt;

&lt;p&gt;MediaPipe assigns an index number to every joint. The tip of the thumb is Landmark 4, and the tip of the index finger is Landmark 8. To detect a pinch, the code extracts the X and Y coordinates for those two points and calculates the distance between them using the Pythagorean theorem (Euclidean distance).&lt;/p&gt;

&lt;p&gt;JavaScript has a highly optimized function for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;thumbIndexDistance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thumb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thumb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thumb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that distance drops below a specific threshold (e.g., 0.06), a boolean flag flips to true, and the app registers a pinch! No complex AI needed for the gesture—just high school geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Illusion (The Render Loop)
&lt;/h2&gt;

&lt;p&gt;The glowing neon effect isn't a continuous video. It's an illusion created using the HTML5 &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; API and &lt;code&gt;requestAnimationFrame&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sixty times a second, the application does this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Grabs a single still frame from the webcam.&lt;/li&gt;
&lt;li&gt;Wipes the canvas completely clean.&lt;/li&gt;
&lt;li&gt;Draws the video frame, then adds a dark semi-transparent rectangle (&lt;code&gt;rgba(0, 0, 0, 0.45)&lt;/code&gt;) over it to dim the background.&lt;/li&gt;
&lt;li&gt;Draws the lines connecting the calculated coordinates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To get that cyberpunk neon glow, I drew every line twice: first as a thick, colored line with a heavy &lt;code&gt;shadowBlur&lt;/code&gt; (the glow), and then a thinner, pure white line directly on top of it (the hot core of the neon tube).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Using AI to prototype is an incredible accelerant. It helped me build the skeleton of this project in a fraction of the time it would normally take. But digging into the code, understanding the Wasm delegation, and tracing the geometry is what actually makes you a better developer.&lt;/p&gt;

&lt;p&gt;Don't just vibe code—read the code, break it, and figure out why it works.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live Demo:&lt;/strong&gt; [&lt;a href="https://madhuravishan.github.io/Neon-gesture-bridge/" rel="noopener noreferrer"&gt;https://madhuravishan.github.io/Neon-gesture-bridge/&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;💻 &lt;strong&gt;Source Code:&lt;/strong&gt; [&lt;a href="https://github.com/Madhuravishan/Neon-gesture-bridge" rel="noopener noreferrer"&gt;https://github.com/Madhuravishan/Neon-gesture-bridge&lt;/a&gt;]&lt;/p&gt;




&lt;h2&gt;
  
  
  For My Sri Lankan Tech Community 🇱🇰
&lt;/h2&gt;

&lt;p&gt;AI පාවිච්චි කරලා ලොකු ලොකු දේවල් විනාඩි කිහිපයකින් හදන්න පුළුවන් කාලෙක අපි ඉන්නේ (මේකට Vibe Coding කියලත් කියනවා). ඒත් ඒ හැදෙන code එක ඇතුළේ ඇත්තටම මොකද වෙන්නේ කියලා අපි දන්නේ නැත්නම්, අපිට කවදාවත් හොඳ Software Engineer කෙනෙක් වෙන්න බෑ.&lt;/p&gt;

&lt;p&gt;මම මෑතකදී හදපු මේ Neon Bridge app එකේ ප්‍රධාන කොටස් 3ක් තියෙනවා:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. ඇස් (Machine Learning)
&lt;/h3&gt;

&lt;p&gt;අපේ අත් දෙක අඳුරගන්න මම පාවිච්චි කළේ Google MediaPipe කියන AI model එක. මේකෙන් අපේ අතේ තියෙන points 21ක් (landmarks) හරියටම අඳුරගන්නවා. Web browser එකේ AI model එකක් run කරනකොට browser එක හිරවෙන්න පුළුවන් නිසා, මම WebAssembly සහ GPU එක පාවිච්චි කරලා ඒ බර සම්පූර්ණයෙන්ම Graphics Card එකට දුන්නා.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. මොළය (Geometry)
&lt;/h3&gt;

&lt;p&gt;App එක කොහොමද මම ඇඟිලි දෙක එකතු කරනවා (Pinch කරනවා) කියලා අඳුරගන්නේ? මෙතනදි AI නෙවෙයි, පිරිසිදු ගණිතය තමයි තියෙන්නේ.&lt;/p&gt;

&lt;p&gt;මහපට ඇඟිල්ලේ තුඩ (Landmark 4) සහ දබර ඇඟිල්ලේ තුඩ (Landmark 8) අතර තියෙන දුර, පයිතගරස් ප්‍රමේයය (Pythagorean theorem) පාවිච්චි කරලා හොයනවා. JavaScript වල තියෙන &lt;code&gt;Math.hypot&lt;/code&gt; function එකෙන් මේක ලේසියෙන්ම කරන්න පුළුවන්. ඒ දුර අඩුවෙනකොට, ඒක Pinch එකක් විදිහට අඳුරගෙන ලස්සන රතු පාට Neon ආලෝකයක් දෙනවා.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. මායාව (Render Loop)
&lt;/h3&gt;

&lt;p&gt;මේ ලස්සන Neon glow එක හදන්නේ HTML5 &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; එකක් පාවිච්චි කරලා. තත්පරේට වාර 60ක් අලුත් රූප අඳිනවා. මේ Neon ආලෝකය ගන්න මම එක line එකක් උඩින් තව line එකක් ඇඳලා &lt;code&gt;shadowBlur&lt;/code&gt; පාවිච්චි කළා.&lt;/p&gt;

&lt;h3&gt;
  
  
  දැන් වැඩිදුර විස්තර කිරීමකට යමු
&lt;/h3&gt;

&lt;h1&gt;
  
  
  🌌 Neon Gesture Bridge - Code Explained (Sinhala)
&lt;/h1&gt;

&lt;p&gt;මේ project එක webcam එකෙන් hand tracking කරන web app එකක්. README එකේ තියෙන විස්තරය අනුව, ක්‍රමය මෙහෙමයි:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Vision &amp;amp; Mapping (දැකීම සහ Mapping)
&lt;/h2&gt;

&lt;p&gt;Webcam feed එක Google's MediaPipe Hand Landmarker library එකට pass කරනවා. MediaPipe කියන්නේ "eyes" එක වගේ - එක එක hand එකේ 21 landmark points (X, Y, Z coordinates) detect කරනවා. මේක WebAssembly + GPU delegation use කරන නිසා browser එක ඇතුළෙම fast (high-framerate) run වෙනවා.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Logic &amp;amp; Math (Pinch Detection)
&lt;/h2&gt;

&lt;p&gt;Landmark 4 (Thumb Tip) සහ Landmark 8 (Index Finger Tip) කියන points දෙක continuously monitor කරනවා. &lt;code&gt;Math.hypot()&lt;/code&gt; use කරලා Pythagorean theorem එකෙන් මේ point දෙක අතර pixel distance එක calculate කරනවා. ඒ distance එක threshold එකකට වඩා අඩු වුණොත්, "Pinch" gesture එකක් ලෙස detect කරනවා - UI එකේ "Gesture: PINCH" කියලා පෙන්වනවා, neon colors red වෙනවා.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Cross-Hand Bridging
&lt;/h2&gt;

&lt;p&gt;Hands දෙකක් detect උනොත්, corresponding fingertips (thumb-to-thumb, index-to-index, etc.) අතර glowing lines draw කරනවා.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Generative Rendering (Neon Glow Effect)
&lt;/h2&gt;

&lt;p&gt;HTML5 Canvas API use කරලා, fingertip points &lt;code&gt;[4, 8, 12, 16, 20]&lt;/code&gt; isolate කරලා, multi-layered strokes draw කරනවා &lt;code&gt;shadowBlur&lt;/code&gt; සහ &lt;code&gt;shadowColor&lt;/code&gt; properties use කරලා - ඒකෙන් neon tube look එක create වෙනවා, dark video background එක උඩින්.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vanilla JavaScript + HTML5 Canvas&lt;/li&gt;
&lt;li&gt;MediaPipe Hand Landmarker (v0.10.14)&lt;/li&gt;
&lt;li&gt;Vite build system&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧩 Code එකේ Implementation Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup (Initialization)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;initLandmarker()&lt;/code&gt;&lt;/strong&gt; - MediaPipe CDN එකෙන් &lt;code&gt;HandLandmarker&lt;/code&gt; model එක load කරනවා, GPU delegate එක්ක VIDEO mode එකේ run වෙන විදියට configure කරනවා, max hands 2ක්.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;startCamera()&lt;/code&gt;&lt;/strong&gt; - webcam access ගන්නවා (&lt;code&gt;getUserMedia&lt;/code&gt;), video element එකට stream කරනවා, canvas size එක video size එකට set කරනවා.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main()&lt;/code&gt;&lt;/strong&gt; - මේ දෙකම call කරලා, ඉවර උනාම &lt;code&gt;render()&lt;/code&gt; loop එක start කරනවා.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Main Loop - &lt;code&gt;render()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;මේක &lt;code&gt;requestAnimationFrame&lt;/code&gt; එක්ක continuously run වෙනවා:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;video frame එක change වෙලාද කියලා check කරනවා (&lt;code&gt;lastVideoTime&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handLandmarker.detectForVideo()&lt;/code&gt; call කරලා current frame එකේ hands detect කරනවා - එකින් each hand එකට landmarks 21ක් (x, y, z) array එකක් එනවා&lt;/li&gt;
&lt;li&gt;Canvas clear කරලා, video frame එක flip කරලා (mirror effect - &lt;code&gt;scale(-1,1)&lt;/code&gt;) draw කරනවා&lt;/li&gt;
&lt;li&gt;Video උඩින් semi-transparent black overlay එකක් දානවා (&lt;code&gt;rgba(0,0,0,0.45)&lt;/code&gt;) - background dark කරන්න, neon glow visible වෙන්න&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Detection Logic
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;multiHandLandmarks&lt;/code&gt; = detect කරපු hands array&lt;/li&gt;
&lt;li&gt;Hand එකක් හරි detect උනොත් status update කරනවා ("1 hand(s) detected" etc.)&lt;/li&gt;
&lt;li&gt;Hand දෙකක්ම detect උනොත් &lt;code&gt;drawCrossHandFingertipLines()&lt;/code&gt; call කරනවා (cross-hand bridge lines)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pinch Detection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;thumbIndexDistance()&lt;/code&gt;&lt;/strong&gt; - landmark 4 (thumb tip) සහ landmark 8 (index tip) අතර &lt;code&gt;Math.hypot(dx, dy)&lt;/code&gt; use කරලා normalized distance (0-1 range) calculate කරනවා.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;isPinchGesture()&lt;/code&gt;&lt;/strong&gt; - ඒ distance එක &lt;code&gt;PINCH_THRESHOLD = 0.06&lt;/code&gt;ට වඩා අඩුනම් &lt;code&gt;true&lt;/code&gt; return කරනවා → pinch detected.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Drawing Functions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;drawFingertipLines(landmarks, isPinch)&lt;/code&gt;&lt;/strong&gt; - එක hand එකේ fingertips 5ම (&lt;code&gt;FINGERTIPS = [4,8,12,16,20]&lt;/code&gt;) එකිනෙකට connect කරන neon lines draw කරනවා (nested loop එකෙන් සියලුම combinations). Pinch නම් red color, නැත්නම් cyan/magenta/green palette එකෙන්.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;drawNeonLine()&lt;/code&gt;&lt;/strong&gt; - canvas එකේ &lt;code&gt;shadowBlur&lt;/code&gt; + &lt;code&gt;shadowColor&lt;/code&gt; use කරලා double-layer line draw කරනවා (thick glow layer + thin white core layer) → neon tube effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;drawNeonDot()&lt;/code&gt;&lt;/strong&gt; - fingertip එක උඩින් glowing dot එකක් draw කරනවා.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;drawCrossHandFingertipLines(hand0, hand1)&lt;/code&gt;&lt;/strong&gt; - hands දෙක ඇතුළේ matching fingertip pairs (thumb-thumb, index-index, etc.) connect කරනවා, finger එකකට වෙනම color එකක් (&lt;code&gt;CROSS_HAND_COLORS&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;drawGestureLabel(isPinch)&lt;/code&gt;&lt;/strong&gt; - top center එකේ "Gesture: PINCH" හෝ "Gesture: OPEN" text එක draw කරනවා, glow effect එක්ක.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 Flow Summary
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Webcam → MediaPipe → 21 landmarks per hand → fingertips extract කරනවා 
→ distance calculate කරලා pinch check 
→ canvas මත video + neon lines/dots draw කරනවා → loop repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  මේකෙන් ගත යුතු දේ....
&lt;/h3&gt;

&lt;p&gt;AI පාවිච්චි කරලා කේත ලිවීම (Vibe coding) ගොඩක් හොඳ දෙයක්. ඒත් ඒ ලියවෙන කේතය තේරුම් ගන්න එක තමයි අපිව සාර්ථක Developer කෙනෙක් කරන්නේ!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>computervision</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
