Image Compression with React: A deep dive.
Introduction
Image Compression: Compressing Pixels Like Never Before
The Math Behind the Squeeze
Implementing Compression in React
Compression Gone Wrong: When Pixels Revolt
Real-time Tagging with Socket.io
The Socket.io Magic Formula
Building a Real-time Tagging System
When Tags Go Wild: Autocorrect's Revenge
Let’s conclude.
Introduction:
Hello everyone! Today, we’ll go through into an exciting topic of Image Compression and Real-time Tagging. Get ready, because we’ll be going through some fascinating concepts that combine technical precision with real-time data manipulation through React.
Image Compression: Compressing Pixels Like Never Before
Imagine you're at your family reunion, and your aunt insists on showing you ALL 10,000 photos from her recent trip. God bless your phone storage. Don’t fear, coz Image compression is here to save the day - Let’s see how.
The Math Behind the Squeeze
Don't worry, I promise it's more fun than your college algebra class!
The fundamental principle of image compression is reducing redundancy. One common method is the Discrete Cosine Transform (DCT), used in JPEG compression. The 2D DCT is defined as:
Where:
f(x,y) is the pixel value at position (x,y)
F(u,v) is the DCT coefficient at position (u,v)
N is the size of the block (typically 8)
C(u), C(v) = 1/√2 for u,v = 0, and 1 otherwise.
Implementing Compression in React
import React, { useState } from 'react';
import imageCompression from 'browser-image-compression';
const ImageCompressor = () => {
const [originalImage, setOriginalImage] = useState(null);
const [compressedImage, setCompressedImage] = useState(null);
const handleImageUpload = async (event) => {
const file = event.target.files[0];
setOriginalImage(file);
const options = {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
useWebWorker: true
}
try {
const compressedFile = await imageCompression(file, options);
setCompressedImage(compressedFile);
} catch (error) {
console.error('Error compressing image:', error);
}
}
return (
<div>
<input type="file" accept="image/*" onChange={handleImageUpload} />
{originalImage && (
<div>
<h3>Original Image</h3>
<img src={URL.createObjectURL(originalImage)} alt="Original" style={{maxWidth: '100%'}} />
<p>Size: {(originalImage.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
)}
{compressedImage && (
<div>
<h3>Compressed Image</h3>
<img src={URL.createObjectURL(compressedImage)} alt="Compressed" style={{maxWidth: '100%'}} />
<p>Size: {(compressedImage.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
)}
</div>
);
}
export default ImageCompressor;
This component allows users to upload an image, compresses it, and displays both the original and compressed versions with their sizes. It's like before and after photos, but for your data diet.
Compression Gone Wrong: When Pixels Revolt
Let’s put it simply: Imagine you’re compressing your vacation photos, and it goes a little too far. Suddenly, your perfect beach selfie looks like something out of Minecraft or Roblox : your face is a pixelated mess, and that seagull in the background? Lol.
It gets worse when you try to compress a group photo. The algorithm goes overboard, and suddenly your friends look like they belong in a horror movie : long, stretched faces with no features, like a scene from a Night at the Museum Horror.
The lesson? Compress wisely, Readers.
Real-time Tagging with Socket.io: Let’s Label
You're scrolling through your phone, showing your friend photos from your epic holiday destination. But wait! You can't remember if that blur in the background of your selfie was a rare bird or just a weirdly shaped cloud or is it your lens itself? If only you regret if you would have tagged at real-time.
The Socket.io Magic Formula
Before we dive into the code, let's talk about the math behind real-time communication. Socket.io uses the WebSocket protocol, which can be represented by this totally real and not-at-all made-up formula:
RT = (L + P) / (S * M)
Where:
RT is Real-time-ness (measured in units)
L is Latency (measured in milliseconds)
P is Packet size (measured in bytes)
S is Server awesomeness (measured in "How many cups of coffee has the dev had?" units)
M is Magic (because let's face it, sometimes it feels like magic)
Building a Real-time Tagging System
import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
const RealTimeTagging = ({ imageUrl }) => {
const [tags, setTags] = useState([]);
const [newTag, setNewTag] = useState('');
useEffect(() => {
socket.on('newTag', (tag) => {
setTags(prevTags => [...prevTags, tag]);
});
return () => {
socket.off('newTag');
};
}, []);
const handleSubmit = (e) => {
e.preventDefault();
if (newTag.trim()) {
socket.emit('addTag', newTag);
setNewTag('');
}
};
return (
<div>
<img src={imageUrl} alt="Taggable" style={{maxWidth: '100%'}} />
<ul>
{tags.map((tag, index) => (
<li key={index}>{tag}</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTag}
onChange={(e) => setNewTag(e.target.value)}
placeholder="Add a tag"
/>
<button type="submit">Add Tag</button>
</form>
</div>
);
};
export default RealTimeTagging;
This component displays an image and allows users to add tags in real-time. As soon as someone adds a tag, it appears for everyone viewing the image. It's like a collaborative game of "Fire boy and water girl", but with less frustration.
When Tags Go Wild: Autocorrect's Revenge
Imagine you're at a family gathering, trying to tag your cousin, now let’s take myself in a group photo using this real-time tagging system. You type "Vidhya," but autocorrect has other plans:
You type: "Vidhya" Autocorrect suggests: "Vada" You think, "No, that's not right," and try again: You type: "Vidhya Dharan" Autocorrect suggests: "Vada Dharan"
Now, thanks to the real-time nature of the system, everyone at the family reunion sees "Vada Dharan" (I get it, it’s cringe) pop up as a tag. Vidhya isn’t amused, but your aunt can't stop laughing because, let’s be honest, Vidhya did look a bit like a snack in that photo!
It doesn’t stop there.
"Uncle Gopal" becomes "Uncle Golap", "Family cat Rose" becomes "Family cat Jasmine" (sweet, but strange).
Before long, your family photo looks like it was tagged by a clueless robot trying to make sense of a Madurai family gathering. And that, my friends, is what happens when real-time tagging meets overly helpful autocorrect!
Lossy Compression: When "Good Enough" Is Good Enough.
Lossy compression is all about finding a balance between image quality and file size. One popular method is transform coding, which uses techniques like the Discrete Cosine Transform (DCT) we mentioned earlier. But let's get a bit more technical, shall we?
In transform coding, we can represent the compression process with this formula:
Y = QT(X)
Where:
X is the original image data
T is the transform (e.g., DCT)
Q is the quantization step
Y is the compressed data
The decompression process is then:
X' ≈ T^(-1)(Q^(-1)(Y))
Where:
X' is the reconstructed image
T^(-1) is the inverse transform
Q^(-1) is the dequantization step
The key here is the quantization step, which is where the "lossy" part comes in. It's like playing a game of "Red Dead Redemption" - only the strongest coefficients make it through!
Lossy Compression in React
import React, { useRef, useEffect, useState } from 'react';
const LossyCompressor = ({ imageUrl, quality }) => {
const canvasRef = useRef(null);
const [compressedSize, setCompressedSize] = useState(0);
const [originalSize, setOriginalSize] = useState(0);
useEffect(() => {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
const canvas = canvasRef.current;
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// Get original size
canvas.toBlob(
(blob) => setOriginalSize(blob.size),
'image/jpeg',
1
);
// Get compressed size
canvas.toBlob(
(blob) => setCompressedSize(blob.size),
'image/jpeg',
quality
);
};
img.src = imageUrl;
}, [imageUrl, quality]);
return (
<div>
<canvas ref={canvasRef} style={{ display: 'none' }} />
<img src={imageUrl} alt="Original" style={{ maxWidth: '100%' }} />
<p>Original Size: {(originalSize / 1024).toFixed(2)} KB</p>
<p>Compressed Size: {(compressedSize / 1024).toFixed(2)} KB</p>
<p>Compression Ratio: {((1 - compressedSize / originalSize) * 100).toFixed(2)}%</p>
</div>
);
};
export default LossyCompressor;
This component takes an image URL and a quality parameter, then uses the Canvas API to perform lossy JPEG compression. It's like a weight loss program for your images, but instead of counting calories, we're counting pixels.
Huffman Coding: Compressing Data
Huffman coding is a variable-length prefix coding algorithm used for lossless data compression. Here's a quick rundown of how it works:
Count the frequency of each symbol in the data.
Create a leaf node for each symbol and add it to a priority queue.
While there's more than one node in the queue: a. Remove the two nodes with the lowest frequency. b. Create a new internal node with these two nodes as children. c. Add the new node back to the queue.
The remaining node is the root of the Huffman tree.
The mathematical beauty of Huffman coding lies in its optimality. The average code length L is bounded by the entropy H:
H ≤ L < H + 1
Where H is calculated as:
H = -Σ(p_i * log_2(p_i))
And p_i is the probability of symbol i occurring.
Huffman Coding in React
import React, { useState } from 'react';
class HuffmanNode {
constructor(char, freq) {
this.char = char;
this.freq = freq;
this.left = this.right = null;
}
}
const buildHuffmanTree = (text) => {
const freqMap = new Map();
for (let char of text) {
freqMap.set(char, (freqMap.get(char) || 0) + 1);
}
const pq = Array.from(freqMap, ([char, freq]) => new HuffmanNode(char, freq));
pq.sort((a, b) => a.freq - b.freq);
while (pq.length > 1) {
const left = pq.shift();
const right = pq.shift();
const parent = new HuffmanNode(null, left.freq + right.freq);
parent.left = left;
parent.right = right;
pq.push(parent);
pq.sort((a, b) => a.freq - b.freq);
}
return pq[0];
};
const buildHuffmanCodes = (root) => {
const codes = new Map();
const dfs = (node, code) => {
if (node.char) {
codes.set(node.char, code);
return;
}
dfs(node.left, code + '0');
dfs(node.right, code + '1');
};
dfs(root, '');
return codes;
};
const HuffmanCompressor = () => {
const [inputText, setInputText] = useState('');
const [compressedText, setCompressedText] = useState('');
const [compressionRatio, setCompressionRatio] = useState(0);
const compressText = () => {
const root = buildHuffmanTree(inputText);
const codes = buildHuffmanCodes(root);
const compressed = inputText.split('').map(char => codes.get(char)).join('');
setCompressedText(compressed);
const ratio = (compressed.length / (inputText.length * 8)) * 100;
setCompressionRatio(ratio);
};
return (
<div>
<textarea
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter text to compress"
rows="4"
cols="50"
/>
<button onClick={compressText}>Compress</button>
<p>Compressed Text: {compressedText}</p>
<p>Compression Ratio: {compressionRatio.toFixed(2)}%</p>
</div>
);
};
export default HuffmanCompressor;
Conclusion
Remember, whether you're compressing your images or tagging them in real-time, the key is to find the right balance. Too much compression, and your selfies start looking like abstract art. Too little, and your phone storage fills up faster than a samosa plate at a wedding buffet.
Subscribe to my newsletter
Read articles from Vidhya dharan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Vidhya dharan
Vidhya dharan
"If I had my life to live over again, I would have made a rule to read some poetry and listen to some music at least once every week." - Charles Darwin.