Pixel manipulation & Animations
The ImageData interface
Introduction
The ImageData
interface represents the pixel data on the <canvas>
element.
ctx.createImageData(width, height);
ctx.createImageData(imagedata);
- Creates a new, blank ImageData object with the specified dimensions.
ctx.getImageData(sx, sy, sw, sh);
- Return an ImageData object representing the underlying pixel data for the area of the canvas.
ctx.putImageData(imagedata, dx, dy);
ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyW, dirtyH);
- Paint data from the given ImageData object onto the bitmap.
imagedata.data
- Get the Uint8ClampedArray containing the data in the RGBA order, with integer values between 0 and 255.
imagedata.height
- Get the actual height, in pixels, of the ImageData.
imagedata.width
- Get the actual width, in pixels, of the ImageData.
Note: Uint8ClampedArray
contains the image data in the RGBA order: [r0, g0, b0, a0, r1, g1, b1, a1, ..., rn, gn, bn, an]
Pixel manipulation
Example 1
This program creates a 2D Canvas and uses ctx.getImageData
to retrieve the ImageData
object representing the underlying pixel data for the area of the canvas. Afterward, it changes every 10th pixel to a solid green color:
HTML
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="c" width="200", height="200" style="border:1px solid #000;">
Pixel manipulation: Change every 10th pixel to a solid green color.
</canvas>
<script>
var canvas = document.getElementById('c');
// Check for canvas support
if (canvas.getContext) {
// Access the rendering context
var ctx = canvas.getContext('2d');
// ImageData object
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// One-dimensional array containing the data in the RGBA order
var data = imageData.data;
function paintGreen(data) {
// data represents the Uint8ClampedArray containing the data
// in the RGBA order [r0, g0, b0, a0, r1, g1, b1, a1, ..., rn, gn, bn, an]
var numPixels = data.length / 4;
for (let i = 0; i < numPixels; i += 10) {
data[i * 4 + 1] = 255; // Green channel
data[i * 4 + 3] = 255; // Alpha channel
}
// Paint pixel data into the context
ctx.putImageData(imageData, 0, 0);
}
paintGreen(data);
}
</script>
</body>
</html>
Result
Example 2
This program demonstrates the process of converting images to grayscale and the process of color-inversion using simple techniques.
Averaging is the fastest and easiest grayscale conversion technique and it works like this:
Color inversion is simple, each color channel is subtracted from the max value of 255:
newG = 255 - G
newB = 255 - B
HTML
<!DOCTYPE html>
<html>
<head>
<style>
canvas {
max-width: 200px;
border:1px solid #000;
display: block;
}
input {
margin-top: 1rem;
}
</style>
</head>
<body>
<canvas id='c'>
Pixel manipulation: Grayscaling and Color inversion
</canvas>
<input id='grayscaleBtn' type='button' value='Grayscale'>
<input id='invertBtn' type='button' value='Invert'>
<input id='resetBtn' type='button' value='Reset'>
<script>
var canvas = document.getElementById('c');
// Check for canvas support
if (canvas.getContext) {
// Initialization
function init(image) {
// Use the intrinsic size of image in CSS pixels
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
// Draw image onto the canvas
ctx.drawImage(image, 0, 0);
}
// Grayscaling
var grayscale = function () {
// ImageData object
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// One-dimensional array containing the data in the RGBA order
var data = imageData.data;
// data represents the Uint8ClampedArray containing the data
// in the RGBA order [r0, g0, b0, a0, r1, g1, b1, a1, ..., rn, gn, bn, an]
for (let i = 0; i < data.length; i += 4) {
// Averaging method: gray = (r + g + b) / 3
let gray = (data[i] + data[i+1] + data[i+2]) / 3;
// Red channel
data[i] = gray;
// Green channel
data[i+1] = gray;
// Blue channel
data[i+2] = gray;
}
// Paint pixel data into the context
ctx.putImageData(imageData, 0, 0);
}
// Color inversion
var invert = function () {
// ImageData object
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// One-dimensional array containing the data in the RGBA order
var data = imageData.data;
// data represents the Uint8ClampedArray containing the data
// in the RGBA order [r0, g0, b0, a0, r1, g1, b1, a1, ..., rn, gn, bn, an]
for (let i = 0; i < data.length; i += 4) {
// Red channel
data[i] = 255 - data[i];
// Green channel
data[i+1] = 255 - data[i+1];
// Blue channel
data[i+2] = 255 - data[i+2];
}
// Paint pixel data into the context
ctx.putImageData(imageData, 0, 0);
}
// Access the rendering context
var ctx = canvas.getContext('2d');
// Create image
var img = new Image();
img.src = '../images/image-1.jpg';
// The callback will be called when the image has finished loading
img.onload = function() {
// Initalize canvas
init(this);
};
// Click events
document.getElementById('grayscaleBtn').addEventListener('click', grayscale);
document.getElementById('invertBtn').addEventListener('click', invert);
document.getElementById('resetBtn').addEventListener('click', function() {
// Erase any previously drawn content
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw image onto the canvas
ctx.drawImage(img, 0, 0);
});
}
</script>
</body>
</html>
Result
Note: For more info on image processing please visit MDN and Tanner Helland.
requestAnimationFrame()
Introduction
Traditionally to create an animation in JavaScript, we relied on setTimeout()
called recursively or setInterval()
to repeatedly execute some code to make changes to an element frame by frame, such as once every 50 milliseconds. The delay inside these functions is often not honored due to changes in user system resources at the time, leading to inconsistent delay intervals between animation frames.
The window.requestAnimationFrame()
method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.
The window.requestAnimationFrame()
method is:
- Synchronized with browser repaint
- Optimized for animations
- Runs as fast as the screen will allow
- Battery Saver
Is recommended not to use use setInterval()
or setTimeOut()
for animations, use requestAnimationFrame()
instead.
Examples
Example 1
This example demonstrates a simple animation loop using window.requestAnimationFrame()
.
HTML
<!DOCTYPE html>
<html>
<head>
<style>
body {
padding: 0;
margin: 0;
}
div {
overflow: hidden;
}
p {
/* Relative positioning moves an element RELATIVE to its original position */
position: relative;
border: none;
border-radius: .25rem;
background: blue;
color: white;
height: 50px;
text-align: center;
}
</style>
</head>
<body>
<div id="area">
<p id="box"></p>
</div>
<script>
// Select <p> element
var box = document.getElementById("box");
// Set the width of the <p> element
box.style.width = "10%";
// Set the width of the <div> element
document.getElementById("area").style.width = "100%";
// <p> is just outside the left side of the <div> element
var currentPos = -10;
// Animation loop (recursive)
function animationLoop() {
box.style.left = currentPos + "%";
currentPos++;
// <p> is just outside the right side of the <div> element
if(currentPos === 100) {
currentPos = -10;
}
window.requestAnimationFrame(animationLoop);
}
animationLoop();
</script>
</body>
</html>
Result
Example 2
This example shows how we can play videos on canvas and specifically how to play a video in sync with the source video.
context.drawImage()
is used to draw each video frame onto the canvas. The method is called recursively for each frame.
HTML
<!DOCTYPE html>
<html>
<head>
<style>
canvas {
max-width: 200px;
border:1px solid #000;
display: block;
}
</style>
</head>
<body>
<video id="v" controls loop width="200" height="200" src="../videos/people.mp4">
Sorry, your browser doesn't support embedded videos!
</video>
<canvas id="c">
Playing video in canvas.
</canvas>
<script>
var canvas = document.getElementById('c');
var video = document.getElementById('v');
// Check for canvas support
if (canvas.getContext) {
// Access the rendering context
var ctx = canvas.getContext("2d");
// The loadedmetadata event is fired when the metadata has been loaded.
video.addEventListener("loadedmetadata", function() {
canvas.width = this.videoWidth;
canvas.height = this.videoHeight;
});
// Draw image onto canvas
function draw () {
ctx.drawImage(video, 0, 0);
// Tell the browser that you wish to perform an animation and request
// that the browser calls the specified function to update an
// animation before the next repaint.
window.requestAnimationFrame(draw)
}
// The play event is fired when playback has begun.
video.addEventListener("play", function() {
draw();
});
}
</script>
</body>
</html>
Result
DO NOT use setInterval()
or setTimeOut()
for animations, use requestAnimationFrame()
instead. requestAnimationFrame()
only sends a request if the last frame is already finished drawing. With setInterval()
and setTimeOut()
, If the drawing takes a really long time to finish a single frame, and the interval is too small it can cause the browser to slow down, drop frames or crash.
This example can be extended to process videos in real-time applying image processing techniques on each video frame.
Example 3
requestAnimationFrame()
is also used in game loops and ensures that game programs run smoothly. The game loop is the overall flow control for the game program. It's a sequence of events that run continuously. A simple game loop may look like the following:
JavaScript
;(function () {
function gameLoop() {
// Run code frame-by-frame
window.requestAnimationFrame(gameLoop);
// Your game loop contents
processInput();
moveObjectsAndEnemies();
drawAllTheThings();
}
gameLoop(); // Start the cycle
})();