Skip to content

Commit b185c8d

Browse files
committed
Updates
1 parent cc16574 commit b185c8d

File tree

3 files changed

+54
-67
lines changed

3 files changed

+54
-67
lines changed

posts/image-toolkit-deep-dive.txt

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,56 @@ The ASCII art filter converts the image to ASCII characters. We first convert th
9090
const ascii = asciiArt(imageData, '@%#*+=-:. ');
9191
```
9292

93+
## How It Works: A Deeper Look
94+
95+
Here's a breakdown of how the Image Toolkit is built, covering the React structure, canvas manipulation, and the filter algorithms.
96+
97+
### 1. React Component Structure
98+
99+
The `ImageToolkitPage` is a functional React component that uses hooks to manage its state and behavior:
100+
101+
* **`useState`**: This hook manages the application's state:
102+
* `image`: Stores the uploaded image as a data URL.
103+
* `activeEffect`: Tracks the currently selected filter.
104+
* `blurAmount`: Holds the value for the blur filter's intensity.
105+
* `asciiArtOutput`: Stores the generated ASCII art string.
106+
* **`useRef`**: This provides direct references to the `<canvas>` and the original `<img>` elements in the DOM.
107+
* **`useEffect`**: This is the core of the image processing. It runs whenever the `image` or `activeEffect` state changes, triggering the drawing and filtering logic.
108+
* **`useToast`**: This is a custom hook used to display toast notifications for actions like copying text.
109+
110+
### 2. Image Upload
111+
112+
When you click "Select Image," the `handleImageUpload` function is triggered. It uses the browser's `FileReader` API to read the selected image file and convert it into a data URL. This data URL is then stored in the `image` state, which causes the component to re-render and display the uploaded image.
113+
114+
### 3. Canvas and `useEffect`
115+
116+
The `useEffect` hook orchestrates the image manipulation:
117+
118+
1. It waits for an `image` to be present and for the `canvasRef` to be attached to the canvas element.
119+
2. It gets the 2D rendering context of the canvas (`ctx`).
120+
3. An `Image` object is created, and its `src` is set to the data URL of the uploaded image.
121+
4. In the image's `onload` event, the original image is drawn onto the canvas using `ctx.drawImage()`.
122+
5. A series of `if/else if` statements checks the `activeEffect` state. Based on which filter is active, it calls the corresponding function to manipulate the image on the canvas.
123+
124+
### 4. Filter Implementation: Pixel-by-Pixel Manipulation
125+
126+
The magic of the filters happens by directly manipulating the pixel data of the canvas.
127+
128+
1. **`getImageData()`**: To get the pixel data, we call `ctx.getImageData()`. This returns an `ImageData` object.
129+
2. **`ImageData.data`**: This object contains a `data` property, which is a `Uint8ClampedArray`. This array is a flat list of RGBA (Red, Green, Blue, Alpha) values for every pixel in the image. For example, the first four values in the array (`data[0]` to `data[3]`) represent the RGBA of the very first pixel.
130+
3. **Manipulation**: Each filter's algorithm iterates through this `data` array and modifies the R, G, and B values according to its logic.
131+
4. **`putImageData()`**: After the `data` array has been modified, `ctx.putImageData()` is called to draw the new pixel data back onto the canvas, displaying the filtered image.
132+
133+
For the **Blur** filter specifically, the `stackblur-canvas` library is used. It provides a highly optimized and performant blur algorithm that is much faster than a manual implementation.
134+
135+
### 5. UI and Event Handling
136+
137+
The user interface is built with standard React components and styled using **Tailwind CSS** for a clean and modern look.
138+
139+
* **Filter Buttons**: Each filter button has an `onClick` event handler (e.g., `handleConvertToMonochrome`). When clicked, this handler updates the `activeEffect` state with the name of the filter. This state change triggers the `useEffect` hook, which then applies the selected filter's logic to the canvas.
140+
* **Download Button**: The "Download Image" button creates a temporary `<a>` (link) element. Its `href` is set to the canvas's current content as a data URL (`canvas.toDataURL()`), and the `download` attribute is set. The link is then programmatically "clicked" to initiate the download.
141+
* **Copy Button**: The "Copy" button for the ASCII art uses the modern `navigator.clipboard.writeText()` API to easily copy the generated ASCII string to the user's clipboard.
142+
93143
## The `useCallback` and `useEffect` Dependency Array Error
94144

95145
You might have encountered this warning while developing the Image Toolkit app:
@@ -108,4 +158,4 @@ const toGrayscale = useCallback((imageData) => {
108158
}, []);
109159
```
110160

111-
By wrapping all the image processing functions in `useCallback`, we can prevent the `useEffect` hook from running on every render and fix the infinite loop.
161+
By wrapping all the image processing functions in `useCallback`, we can prevent the `useEffect` hook from running on every render and fix the infinite loop.

rss.xml

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
<link>https://fezcode.com</link>
1010
</image>
1111
<generator>RSS for Node</generator>
12-
<lastBuildDate>Mon, 10 Nov 2025 21:15:53 GMT</lastBuildDate>
12+
<lastBuildDate>Mon, 10 Nov 2025 21:23:34 GMT</lastBuildDate>
1313
<atom:link href="https://fezcode.com/rss.xml" rel="self" type="application/rss+xml"/>
14-
<pubDate>Mon, 10 Nov 2025 21:15:53 GMT</pubDate>
14+
<pubDate>Mon, 10 Nov 2025 21:23:34 GMT</pubDate>
1515
<copyright><![CDATA[2025 Ahmed Samil Bulbul]]></copyright>
1616
<language><![CDATA[en]]></language>
1717
<managingEditor><![CDATA[samil.bulbul@gmail.com (Ahmed Samil Bulbul)]]></managingEditor>
@@ -25,69 +25,6 @@
2525
<dc:creator><![CDATA[Ahmed Samil Bulbul]]></dc:creator>
2626
<pubDate>Mon, 10 Nov 2025 00:00:00 GMT</pubDate>
2727
<content:encoded><![CDATA[<h1>Image Toolkit Deep Dive</h1>
28-
<p>In this blog post, we&#39;ll take a deep dive into the implementation of the Image Toolkit app. We&#39;ll explore the various image filters and their algorithms, and we&#39;ll also discuss a common React Hook-related warning and how to fix it.</p>
29-
<p>You can try it here <a href="/#/apps::itk">apps::itk</a></p>
30-
<h2>The Filters</h2>
31-
<p>The Image Toolkit app provides a variety of filters that you can apply to your images. Let&#39;s take a look at each one and the algorithm behind it.</p>
32-
<h3>Monochrome</h3>
33-
<p>The monochrome filter converts an image to grayscale. The algorithm for this is quite simple. For each pixel in the image, we calculate the average of the red, green, and blue values. Then, we set the red, green, and blue values of the pixel to this average value.</p>
34-
<pre><code class="language-javascript">const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
35-
data[i] = avg; // red
36-
data[i + 1] = avg; // green
37-
data[i + 2] = avg; // blue
38-
</code></pre>
39-
<h3>Blur</h3>
40-
<p>The blur filter applies a blur effect to the image. We use the <code>stackblur-canvas</code> library to achieve this effect. The <code>canvasRGBA</code> function from this library takes the canvas, the coordinates of the area to blur, and the blur radius as input.</p>
41-
<pre><code class="language-javascript">canvasRGBA(canvas, 0, 0, canvas.width, canvas.height, blurAmount);
42-
</code></pre>
43-
<h3>Dithering</h3>
44-
<p>Dithering is a technique used to create the illusion of more colors than are actually available. We use the Bayer dithering algorithm. This algorithm uses a threshold map (the Bayer matrix) to determine whether a pixel should be black or white.</p>
45-
<pre><code class="language-javascript">const bayerMatrix = [
46-
[1, 9, 3, 11],
47-
[13, 5, 15, 7],
48-
[4, 12, 2, 10],
49-
[16, 8, 14, 6]
50-
];
51-
const threshold = bayerMatrix[y % matrixSize][x % matrixSize] * 16;
52-
const newValue = gray &lt; threshold ? 0 : 255;
53-
</code></pre>
54-
<h3>Cel Shading</h3>
55-
<p>Cel shading is a non-photorealistic rendering technique that makes 3D computer graphics appear to be flat. To achieve this effect, we first apply color quantization to reduce the number of colors in the image. Then, we use the Sobel operator to detect the edges in the image. Finally, we combine the quantized image and the edges to create the cel-shaded effect.</p>
56-
<h3>Halftone</h3>
57-
<p>The halftone filter simulates the effect of printing an image with a series of dots. We first convert the image to grayscale. Then, for each grid of pixels, we calculate the average brightness and draw a circle with a radius proportional to the brightness.</p>
58-
<h3>Solarization</h3>
59-
<p>Solarization is an effect where the image is partially reversed. We set a threshold and for each pixel, if the color component is less than the threshold, we invert it.</p>
60-
<pre><code class="language-javascript">if (r &lt; threshold) data[i] = 255 - r;
61-
if (g &lt; threshold) data[i + 1] = 255 - g;
62-
if (b &lt; threshold) data[i + 2] = 255 - b;
63-
</code></pre>
64-
<h3>Posterization</h3>
65-
<p>Posterization is a process in which the number of colors in an image is reduced. For each color component of a pixel, we round it to the nearest value in a smaller set of values.</p>
66-
<h3>Sepia</h3>
67-
<p>The sepia filter gives the image a warm, brownish tone. We use a set of coefficients to calculate the new red, green, and blue values for each pixel.</p>
68-
<pre><code class="language-javascript">data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
69-
data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
70-
data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
71-
</code></pre>
72-
<h3>Pixelization</h3>
73-
<p>The pixelization filter creates a blocky, pixelated effect. We divide the image into a grid of blocks and fill each block with the color of the top-left pixel in that block.</p>
74-
<h3>Duotone</h3>
75-
<p>The duotone filter uses two colors to create a two-toned image. We first convert the image to grayscale. Then, we interpolate between a dark color and a light color based on the brightness of each pixel.</p>
76-
<h3>ASCII Art</h3>
77-
<p>The ASCII art filter converts the image to ASCII characters. We first convert the image to grayscale. Then, for each pixel, we map its brightness to a character from a character ramp.</p>
78-
<pre><code class="language-javascript">const ascii = asciiArt(imageData, &#39;@%#*+=-:. &#39;);
79-
</code></pre>
80-
<h2>The <code>useCallback</code> and <code>useEffect</code> Dependency Array Error</h2>
81-
<p>You might have encountered this warning while developing the Image Toolkit app:</p>
82-
<pre><code>The &#39;toGrayscale&#39; function makes the dependencies of useEffect Hook (at line 348) change on every render. To fix this, wrap the definition of &#39;toGrayscale&#39; in its own useCallback() Hook
83-
</code></pre>
84-
<p>This warning occurs because the <code>toGrayscale</code> function is defined inside the <code>ImageToolkitPage</code> component. This means that on every render of the component, a new <code>toGrayscale</code> function is created. Since <code>toGrayscale</code> is a dependency of the <code>useEffect</code> hook, the hook will run on every render, causing an infinite loop.</p>
85-
<p>To fix this, we can wrap the definition of <code>toGrayscale</code> in its own <code>useCallback</code> hook. The <code>useCallback</code> hook will memoize the function, so that it is not recreated on every render.</p>
86-
<pre><code class="language-javascript">const toGrayscale = useCallback((imageData) =&gt; {
87-
// ...
88-
}, []);
89-
</code></pre>
90-
<p>By wrapping all the image processing functions in <code>useCallback</code>, we can prevent the <code>useEffect</code> hook from running on every render and fix the infinite loop.</p>
9128
<p><a href="https://fezcode.com/#/blog/image-toolkit-deep-dive">Read more...</a></p>]]></content:encoded>
9229
</item>
9330
<item>

static/js/main.e1c5692c.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)