JS Image Processing
---
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)