Image Compression with React: A deep dive.

Vidhya dharanVidhya dharan
8 min read
  • 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:

Lossy Data Compression: JPEG

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:

  1. Count the frequency of each symbol in the data.

  2. Create a leaf node for each symbol and add it to a priority queue.

  3. 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.

  4. 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.

0
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.