JS Image Processing

Foundations · Prereqs: JS Arrays + Loops
--- theme: default routerMode: hash favicon: https://oim3690.github.io/favicon.svg titleTemplate: "%s - OIM3690" title: "JS Image Processing" info: | pixels and RGBA, canvas + getImageData, the per-pixel for loop, image filters (red/grayscale/negative/brightness) <br><br> <b>Custom:</b> <kbd>t</kbd> Timer · <kbd>T</kbd> Start/pause · <kbd>w</kbd> Draw <br> <b>Slidev:</b> <kbd>o</kbd> Overview · <kbd>d</kbd> Dark mode · <kbd>f</kbd> Fullscreen · <kbd>←</kbd> <kbd>→</kbd> Navigate --- # JS Image Processing Every image is just numbers you can change. --- # You Already Use This Pixel manipulation powers a lot of what you use every day: - Instagram and Snapchat filters - Photoshop adjustments - AI vision (every model starts from pixel arrays) By the end, you will know what is happening underneath. --- layout: image-right image: ./images/image-pixels.jpg --- # Images Are Pixels A photo is a grid of tiny squares called **pixels**. Each pixel holds one color. A 1920x1080 photo has over **2 million** pixels, and JavaScript can reach every one. --- # How a Pixel Stores Color Each pixel is **four numbers**, each from 0 to 255: - **R** red - **G** green - **B** blue - **A** alpha (transparency) ``` (255, 0, 0, 255) = solid red ( 0, 0, 0, 255) = black (255, 255, 255, 255) = white (128, 128, 128, 255) = gray ``` --- # ▶️ Mix a Color Change the numbers, watch the color. Each is 0 to 255: <script setup> const rgbaHtml = `<div class="wrap"> <div id="box"></div> <p id="label"></p> </div>` const rgbaCss = `.wrap { text-align: center; } #box { width: 140px; height: 140px; margin: 0 auto; border-radius: 10px; border: 1px solid #ccc; }` const rgbaJs = `const r = 255; const g = 100; const b = 0; const color = \`rgb(\${r}, \${g}, \${b})\`; document.querySelector("#box").style.background = color; document.querySelector("#label").textContent = color;` </script> <CodePlayground default-tab="js" :html="rgbaHtml" :css="rgbaCss" :js="rgbaJs" /> --- # Image Data Is One Big Array <div class="grid grid-cols-[3fr_2fr] gap-6 items-center"> <div> The browser hands you every pixel as a single flat array: ```js // [R, G, B, A, R, G, B, A, ...] [255, 0, 0, 255, 0, 255, 0, 255, ...] // pixel 0 pixel 1 ``` Its `length` is width × height × 4. To process an image, you loop over this array. </div> <div> <img src="./images/html5-canvas-imageData-four-pixels.png" class="rounded" style="max-width: 100%" /> </div> </div> --- layout: center --- # Canvas --- # The `<canvas>` Element `<canvas>` is a drawing surface you control with JavaScript: ```html <canvas id="myCanvas"></canvas> ``` ```js const canvas = document.querySelector("#myCanvas"); const ctx = canvas.getContext("2d"); ``` `ctx` (the "context") is your toolbox for drawing and reading pixels. --- # Draw an Image on the Canvas ```js const img = new Image(); img.src = "photo.jpg"; img.onload = function () { canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); }; ``` Wait for `onload`: the image is not ready the instant you set `src`. --- # Get the Pixels ```js const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const pixels = imageData.data; console.log(pixels.length); // width * height * 4 const r = pixels[0]; // red of the first pixel const g = pixels[1]; // green const b = pixels[2]; // blue const a = pixels[3]; // alpha ``` --- # ▶️ Draw and Read a Pixel Draw four squares, then read the color of one pixel. Change `x` and `y`: <script setup> const cvHtml = `<canvas id="c" width="400" height="100"></canvas> <p id="out"></p>` const cvCss = `#c { border: 1px solid #ccc; }` const cvJs = `const ctx = document.querySelector("#c").getContext("2d"); ctx.fillStyle = "red"; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = "green"; ctx.fillRect(100, 0, 100, 100); ctx.fillStyle = "blue"; ctx.fillRect(200, 0, 100, 100); ctx.fillStyle = "purple"; ctx.fillRect(300, 0, 100, 100); const x = 10; const y = 10; const p = ctx.getImageData(x, y, 1, 1).data; document.querySelector("#out").textContent = \`pixel (\${x}, \${y}) = rgba(\${p[0]}, \${p[1]}, \${p[2]}, \${p[3]})\`;` </script> <CodePlayground default-tab="js" :height="380" :html="cvHtml" :css="cvCss" :js="cvJs" /> --- layout: center --- # Filters --- # The Loop Pattern Walk the array **four values at a time**, one pixel per step: ```js for (let i = 0; i < pixels.length; i += 4) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; // change the pixel here } ctx.putImageData(imageData, 0, 0); // draw the result back ``` The same `for` loop you know, now over millions of pixels. --- # Filter: Red Channel Zero out green and blue, keep red: ```js for (let i = 0; i < pixels.length; i += 4) { pixels[i + 1] = 0; // no green pixels[i + 2] = 0; // no blue } ``` --- # Filter: Grayscale Average the three colors, then set all three to that average: ```js for (let i = 0; i < pixels.length; i += 4) { // 1. average pixels[i], pixels[i + 1], pixels[i + 2] // 2. set all three to that average } ``` We will write this one together. --- # Filter: Negative Subtract each color from 255: ```js for (let i = 0; i < pixels.length; i += 4) { // subtract each of pixels[i], pixels[i + 1], pixels[i + 2] from 255 // leave alpha (pixels[i + 3]) alone } ``` Your turn to try before we compare. --- # Filter: Brightness Add to each color, but **clamp** so values stay in 0 to 255: ```js function clamp(v) { return Math.max(0, Math.min(255, v)); } const amount = 50; for (let i = 0; i < pixels.length; i += 4) { // add `amount` to each color, wrapped in clamp(...) } ``` --- # ✏️ Your Turn: Write the Filters 1. Download **[image-filters.html](https://oim3690.github.io/resources/image-filters.html)** into your **oim3690** repo 2. Open it with **Live Server**, then pick any image 3. Study the **Red** filter (the worked example) 4. Implement **Grayscale**, **Negative**, and **Brightness** with the loop pattern (use the `clamp` helper for brightness) **Then with AI:** ask it to add a **blur** effect or more filters (sepia, threshold). Read the loop before you keep it. --- # Key Takeaways 1. An image is a grid of **pixels**; each is `[R, G, B, A]`, 0 to 255 2. `canvas` + `getImageData` give you the pixels as one flat array 3. A `for` loop stepping `i += 4` visits every pixel 4. A filter rewrites those numbers, then `putImageData` draws it back 5. **Clamp** values so they stay in 0 to 255 --- # References - [MDN: Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) - [MDN: getImageData](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData) - [MDN: Pixel manipulation with canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas)

Topics Covered

  • pixels and RGBA
  • canvas + getImageData / putImageData
  • per-pixel for loop
  • filters (red / grayscale / negative / brightness)

Content Slides Open fullscreen ↗