Core JavaScript

Image Processing via Workers

To make a JavaScript program demonstrates how to use a worker to implement image processing operations.

ImageProcessing.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>XoaX.net's Workers Demo</title>
		<!-- module type is used to allow import and export commands -->
		<script src="Imaging.js" type="module"></script>
		<style>
			#idOutput {
				background: repeating-conic-gradient(black 0 25%, white 0 50%);
				background-size: 8px 8px;
				border: 1px solid black;
				padding: 5px;
				margin: 5px;
				float: left;
			}
		</style>
	</head>
	<body>
		<div>
			<label>Choose an image <input type="url" onfocus="e => { this.value = ''}" id="idImageUrl" list="idImageList">
				<datalist id="idImageList">
					<option value="AllegoryOfTheCatholicFaith_JohannesVermeer_c1670_1672.jpg"></option>
					<option value="TheLastSupper_1495_LeonardoDaVinci.png"></option>
					<option value="TheCrucifixion_LeonBonnat.png"></option>
					<option value="PrincessOlga.png"></option>
					<option value="TheLastSupper_1896_DagnanBouveret.png"></option>
				</datalist>
			</label>
			<label>Choose a filter <select id="idFilter">
				<option value="fnOriginal">Original</option>
				<option value="fnGrayscale">Grayscale</option>
				<option value="fnBrighten">Brighten</option>
				<option value="fnDarken">Darken</option>
				<option value="fnNegative">Negative</option>
				<option value="fnTransparent">Transparent</option>
				<option value="fnBlur">Blur</option>
			</select></label>
		</div>
		<div style="visibility: hidden;" id="idOutput"></div>
	</body>
</html>

Imaging.js

// NOTE: THIS REQUIRES A WEB SERVER TO RUN
const kqWorker = new Worker("Worker.js", { type: "module" });
kqWorker.onmessage = fnReceiveProcessedFromWorker;

const kqImageUrlElement = document.querySelector("#idImageUrl");
const kqFilterElement = document.querySelector("#idFilter");
const kqOutputDiv = document.querySelector("#idOutput");

kqImageUrlElement.oninput = fnDisplayNewImage;
kqFilterElement.oninput = fnSendImageToWorker;

let qImageData = null;
let qContext = null;
			
function fnDisplayNewImage() {
	const kqImage = new Image();
	kqImage.src = kqImageUrlElement.value;
	// Remove the image file name so that a new image can be selected.
	kqImageUrlElement.value = "";
	// Make the canvas container visible when an image is selected
	kqOutputDiv.style.visibility = "visible";

	kqImage.onload = () => {
		const kqCanvas = document.createElement("canvas");
		kqCanvas.width = kqImage.width;
		kqCanvas.height = kqImage.height;

		qContext = kqCanvas.getContext("2d");
		qContext.drawImage(kqImage, 0, 0);
		qImageData = qContext.getImageData(0, 0, kqCanvas.width, kqCanvas.height);

		fnSendImageToWorker();
		kqOutputDiv.replaceChildren(kqCanvas);
	};
}

function fnSendImageToWorker() {
	kqWorker.postMessage({ qImageData, filter: kqFilterElement.value });
}
			
function fnReceiveProcessedFromWorker(e) {
	qContext.putImageData(e.data, 0, 0);
}

Worker.js

import * as filters from "./Filters.js";

self.onmessage = e => {
  const { qImageData, filter } = e.data;
  filters[filter](qImageData);
  self.postMessage(qImageData, [qImageData.data.buffer]);
};

Filters.js

export function fnOriginal() {}

export function fnGrayscale({ data: d }) {
  for (let i = 0; i < d.length; i += 4) {
    const [r, g, b] = [d[i], d[i + 1], d[i + 2]];
    d[i] = d[i + 1] = d[i + 2] = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  }
};

export function fnBrighten({ data: d }) {
  for (let i = 0; i < d.length; ++i) {
    d[i] = ((1.2*d[i] > 255) ? 255 : 1.2*d[i]);
  }
};

export function fnDarken({ data: d }) {
  for (let i = 0; i < d.length; ++i) {
  	if (i % 4 != 3) {
    	d[i] = .8*d[i];
  	}
  }
};

export function fnNegative({ data: d }) {
  for (let i = 0; i < d.length; ++i) {
		if (i % 4 != 3) {
	    d[i] = 255 - d[i];
		}
  }
}

export function fnTransparent({ data: d }) {
  for (let i = 0; i < d.length; ++i) {
		if (i % 4 == 3) {
	    d[i] >>= 1;
		}
  }
}

export function fnBlur(qImageData) {
	let ui8PixelsRGBA = qImageData.data;
	const kiFilterWidth = 11;
	const kiNeighbors = Math.floor(kiFilterWidth/2);
	const kiHeight = qImageData.height;
	const kiWidth = qImageData.width;
	const kui8aSums = new Uint8Array(4*ui8PixelsRGBA.length);
	let ui32aArray = new Uint32Array(kui8aSums.buffer);
	// Horizontal sums
	for (let j = 0; j < kiHeight; ++j) {
		for (let i = 0; i < kiWidth; ++i) {
			let iLowIndex = ((i - kiNeighbors < 0) ? 0 : i - kiNeighbors);
			let iHighIndex = ((i + kiNeighbors + 1 > kiWidth) ? kiWidth : i + kiNeighbors + 1);
			let iLength = iHighIndex - iLowIndex;
			// Initialize each entry to zero.
			for (let iChannel = 0; iChannel < 4; ++iChannel) {
				ui32aArray[((i + j*kiWidth)*4 + iChannel)] = 0;
			}
			for (let k = iLowIndex; k < iHighIndex; ++k) {
				for (let iChannel = 0; iChannel < 4; ++iChannel) {
					ui32aArray[((i + j*kiWidth)*4 + iChannel)] += ui8PixelsRGBA[(k + j*kiWidth)*4 + iChannel];
				}
			}
		}
	}
	const kui8aSums2 = new Uint8Array(4*ui8PixelsRGBA.length);
	let ui32aArray2 = new Uint32Array(kui8aSums2.buffer);
	// Vertical sums
	for (let j = 0; j < kiHeight; ++j) {
		let iLowIndexJ = ((j - kiNeighbors < 0) ? 0 : j - kiNeighbors);
		let iHighIndexJ = ((j + kiNeighbors + 1 > kiHeight) ? kiHeight : j + kiNeighbors + 1);
		let iLengthJ = iHighIndexJ - iLowIndexJ;
		for (let i = 0; i < kiWidth; ++i) {
			let iLowIndexI = ((i - kiNeighbors < 0) ? 0 : i - kiNeighbors);
			let iHighIndexI = ((i + kiNeighbors + 1 > kiWidth) ? kiWidth : i + kiNeighbors + 1);
			let iLengthI = iHighIndexI - iLowIndexI;
			for (let iChannel = 0; iChannel < 4; ++iChannel) {
				ui32aArray2[((i + j*kiWidth)*4 + iChannel)] = 0;
			}
			for (let k = iLowIndexJ; k < iHighIndexJ; ++k) {
				for (let iChannel = 0; iChannel < 4; ++iChannel) {
					ui32aArray2[((i + j*kiWidth)*4 + iChannel)] += ui32aArray[((i + k*kiWidth)*4 + iChannel)];
				}
			}
		}
	}
	for (let j = 0; j < kiHeight; ++j) {
		let iLowIndexJ = ((j - kiNeighbors < 0) ? 0 : j - kiNeighbors);
		let iHighIndexJ = ((j + kiNeighbors + 1 > kiHeight) ? kiHeight : j + kiNeighbors + 1);
		let iLengthJ = iHighIndexJ - iLowIndexJ;
		for (let i = 0; i < kiWidth; ++i) {
			let iLowIndexI = ((i - kiNeighbors < 0) ? 0 : i - kiNeighbors);
			let iHighIndexI = ((i + kiNeighbors + 1 > kiWidth) ? kiWidth : i + kiNeighbors + 1);
			let iLengthI = iHighIndexI - iLowIndexI;
			for (let iChannel = 0; iChannel < 4; ++iChannel) {
				ui8PixelsRGBA[(i + j*kiWidth)*4 + iChannel] = ui32aArray2[((i + j*kiWidth)*4 + iChannel)]/(iLengthI*iLengthJ);
			}
		}
	}
}
 

Output

 
 

© 2007–2026 XoaX.net LLC. All rights reserved.