Generating BlurHash Strings and Creating Blurred Placeholders with Node.js
In this post, we’ll explore how to generate a BlurHash string from an image and use it to create a blurred placeholder image. BlurHash is an efficient way to encode an image into a short string, which can later be decoded back into a low-quality blurred version. It’s great for improving user experiences by providing placeholders while images load.
We’ll be using the following packages:
axios
: To fetch images from URLs.fs
: For file system operations.blurhash
: To encode and decode images into a BlurHash.image-size
: To determine image dimensions.canvas
: To draw images onto a canvas and create placeholder images.
Step 1: Fetching Images
First, we need to fetch an image, whether from a URL or from the local filesystem.
async function fetchImage(url) {
const response = await axios.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
}
Here, we use axios
to fetch the image data as an arraybuffer
and convert it into a Buffer
.
Step 2: Extract Image Data
To process the image and create a BlurHash, we need to extract pixel data from it. We’ll use canvas
for this, resizing the image if it’s larger than a specified maximum size.
async function getImageData(imageBuffer, maxSize = 512) {
const image = await loadImage(imageBuffer);
const scale = Math.min(maxSize / image.width, maxSize / image.height, 1);
const width = Math.floor(image.width * scale);
const height = Math.floor(image.height * scale);
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height).data;
return {
data: new Uint8Array(imageData),
width,
height
};
}
This function:
- Loads the image using
loadImage()
. - Scales the image if necessary to fit within the
maxSize
constraint. - Draws the image onto a canvas and extracts the pixel data.
Step 3: Generating a BlurHash
Next, we take the pixel data and generate a BlurHash string using the blurhash
library. The number of components (e.g., 4 and 3 in the encode()
function) can be adjusted to control the level of detail.
async function generateBlurhash(imageSource, isUrl = false) {
try {
let imageBuffer;
let width, height;
if (isUrl) {
imageBuffer = await fetchImage(imageSource);
} else {
const dimensions = sizeOf(imageSource);
width = dimensions.width;
height = dimensions.height;
imageBuffer = fs.readFileSync(imageSource);
}
const { data, width: imgWidth, height: imgHeight } = await getImageData(imageBuffer);
const blurhash = encode(data, imgWidth, imgHeight, 4, 3); // Adjust components as needed
return { blurhash, imgWidth, imgHeight };
} catch (error) {
console.error('Error generating BlurHash:', error);
}
}
- If
isUrl
istrue
, the image is fetched from the web; otherwise, it’s read from the local filesystem. - The
getImageData()
function processes the image, and theblurhash
library encodes the pixel data into a BlurHash string.
Step 4: Decoding the BlurHash to Create a Placeholder
We now take the BlurHash string and decode it into an array of pixels, which is then drawn onto a canvas to create the blurred placeholder.
function createBlurredImage(blurhash, width, height, outputPath) {
const pixels = decode(blurhash, width, height);
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(width, height);
imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0);
const out = fs.createWriteStream(outputPath);
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => {
console.log('Blurred image saved to', outputPath);
});
}
This function:
- Decodes the BlurHash string into pixel data.
- Draws the decoded pixels onto a canvas and saves the blurred image as a PNG file.
Example Usage
Here’s how you can put everything together and generate both the BlurHash string and the blurred image:
async function exampleGenerateBlurhash() {
const imageSource = './mohanlal.jpg'; // Path to your local image
const isUrl = false; // Set to true if imageSource is a URL
const result = await generateBlurhash(imageSource, isUrl);
if (result) {
console.log('BlurHash:', result.blurhash);
}
}
async function exampleCreateBlurredImage() {
const imageSource = './mohanlal.jpg'; // Path to your local image
const isUrl = false;
const outputPath = './blurred-placeholder.png'; // Output file path
const result = await generateBlurhash(imageSource, isUrl);
if (result) {
createBlurredImage(result.blurhash, result.imgWidth, result.imgHeight, outputPath);
}
}
exampleGenerateBlurhash();
exampleCreateBlurredImage();
In this example:
- We first generate the BlurHash string using
exampleGenerateBlurhash()
. - Then, we create a blurred placeholder image using
exampleCreateBlurredImage()
.
const fs = require('fs');
const axios = require('axios');
const { encode, decode } = require('blurhash');
const sizeOf = require('image-size');
const { createCanvas, loadImage } = require('canvas');
// Fetch image from URL
async function fetchImage(url) {
const response = await axios.get(url, { responseType: 'arraybuffer' });
return Buffer.from(response.data);
}
// Extract image data
async function getImageData(imageBuffer, maxSize = 512) {
const image = await loadImage(imageBuffer);
// Resize if the image is larger than maxSize
const scale = Math.min(maxSize / image.width, maxSize / image.height, 1);
const width = Math.floor(image.width * scale);
const height = Math.floor(image.height * scale);
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height).data;
return {
data: new Uint8Array(imageData),
width,
height
};
}
// Generate blur hash
async function generateBlurhash(imageSource, isUrl = false) {
try {
let imageBuffer;
let width, height;
if (isUrl) {
// Handle network image
imageBuffer = await fetchImage(imageSource);
} else {
// Handle local file
const dimensions = sizeOf(imageSource);
width = dimensions.width;
height = dimensions.height;
imageBuffer = fs.readFileSync(imageSource);
}
const { data, width: imgWidth, height: imgHeight } = await getImageData(imageBuffer);
// Encode the image data to BlurHash
const blurhash = encode(data, imgWidth, imgHeight, 4, 3); // Adjust components as needed
return { blurhash, imgWidth, imgHeight };
} catch (error) {
console.error('Error generating BlurHash:', error);
}
}
// Create a blurred placeholder image from a blur hash
function createBlurredImage(blurhash, width, height, outputPath) {
// Decode the blur hash
const pixels = decode(blurhash, width, height);
// Create a canvas and draw the decoded image
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Create an ImageData object and set the pixel data
const imageData = ctx.createImageData(width, height);
imageData.data.set(pixels);
// Draw the image data onto the canvas
ctx.putImageData(imageData, 0, 0);
// Save the canvas as an image file
const out = fs.createWriteStream(outputPath);
const stream = canvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => {
console.log('Blurred image saved to', outputPath);
});
}
// Example usage of functions
// Generate blur hash from a local image or URL
async function exampleGenerateBlurhash() {
const imageSource = './mohanlal.jpg'; // Path to your local image
const isUrl = false; // Set to true if imageSource is a URL
const result = await generateBlurhash(imageSource, isUrl);
if (result) {
console.log('BlurHash:', result.blurhash);
// Optionally save the blur hash to a file or database
}
}
// Create a blurred placeholder image from a blur hash
async function exampleCreateBlurredImage() {
const imageSource = './mohanlal.jpg'; // Path to your local image
const isUrl = false; // Set to true if imageSource is a URL
const outputPath = './blurred-placeholder.png'; // Output file path
const result = await generateBlurhash(imageSource, isUrl);
if (result) {
createBlurredImage(result.blurhash, result.imgWidth, result.imgHeight, outputPath);
}
}
// Run the example functions
exampleGenerateBlurhash();
exampleCreateBlurredImage();
Conclusion
In this tutorial, we’ve covered how to generate a BlurHash string from an image and use it to create a blurred placeholder image. This technique is useful for improving user experience by showing a preview while the original image loads.