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:

gray = (red + green + blue) / 3

Color inversion is simple, each color channel is subtracted from the max value of 255:

newR = 255 - R
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:

  1. Synchronized with browser repaint
  2. Optimized for animations
  3. Runs as fast as the screen will allow
  4. 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
})();
Ad1
advertisement
Ad2
advertisement
Ad3
advertisement
Ad4
advertisement
Ad5
advertisement
Ad6
advertisement