How to Convert an Image to Black and White with JavaScript

A black-and-white binary image consists of pixels that can have one of exactly two colors: black and white. Converting an image to black and white has some usages:

  • File size reduction: a 1-bit binary image requires less data for storage than a 24-bit color image.
  • Image processing: many image processing algorithms have to work on binary images.
  • For display and printing: some input/output devices, such as laser printers, fax machines, and bilevel computer displays, can only handle bilevel images.
  • Aesthetics: we can see pixels vividly in a binary image which is a kind of pixel art.

The procedure of converting an image to black and white is called thresholding which usually does the following:

  • Convert an image to grayscale
  • Replace each pixel with a black one if its intensity is less than a threshold and with a white one if its intensity is larger than a threshold

A sample image converted to black and white:

binary conversion

In this article, we are going to talk about two ways to convert an image to black and white with JavaScript.

Online demo

Build an HTML5 Page to Convert Images to Black and White

Create a new HTML5 page with the following template and then let’s add the color conversion function to it.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Convert an Image to Black and White</title>
  <style>
  </style>
</head>
<body>
  <div class="home">
    <h2>Convert an Image to Black and White</h2>
    <button id="convertButton">Convert</button>
  </div>
  <script>
  </script>
</body>
</html>

Load an Image File

Add an input element for selecting files and use a button element to trigger it. The image file will be loaded into two image elements, one for displaying the converted result and one for storing the original image.

HTML:

<button id="loadFileButton">Load a File</button>
<input style="display:none;" type="file" id="file" onchange="loadImageFromFile();" accept=".jpg,.jpeg,.png,.bmp" />
<div class="imageContainer">
  <img id="image"/>
  <img id="imageHidden"/>
</div>
<style>
  .imageContainer {
    max-width: 50%;
  }

  #image {
    max-width: 100%;
  }

  #imageHidden {
    display: none;
  }
</style>

JavaScript:

function loadImageFromFile(){
  let fileInput = document.getElementById("file");
  let files = fileInput.files;
  if (files.length == 0) {
    return;
  }
  let file = files[0];
  fileReader = new FileReader();
  fileReader.onload = function(e){
    document.getElementById("image").src = e.target.result;
    document.getElementById("imageHidden").src = e.target.result;
  };
  fileReader.onerror = function () {
    console.warn('oops, something went wrong.');
  };
  fileReader.readAsDataURL(file);
}

Convert an Image to Black and White with Canvas

HTML5 provides a canvas element which provides a way to manipulate image data. We can use it to get an image converted to black and white.

  1. Add a hidden canvas element to the page.

     <canvas id="canvasHidden"></canvas>
     <style>
       #canvasHidden {
         display: none;
       }
     </style>
    
  2. Add an input element to set the threshold.

     <div class="thresholdControls" style="display:inline">
       <label>
         Threshold (0-255):
         <input id="threshold" type="number" min="0" max="255" value="127">
       </label>
     </div>
    
  3. Set the canvas’s size to match the image’s size.

     const image = document.getElementById("image");
     const canvas = document.getElementById("canvasHidden");
     canvas.width = image.naturalWidth;
     canvas.height = image.naturalHeight;
    
  4. Get the context of the canvas to perform actions.

     const context = canvas.getContext("2d");
    
  5. Draw the image onto the canvas.

     context.drawImage(image, 0, 0);
    
  6. Get the ImageData of the image:

     const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    

    ImageData stores the pixel values in a Uint8ClampedArray in the RGBA order, with integer values between 0 and 255 (inclusive). The order goes by rows from the top-left pixel to the bottom-right.

  7. Create a function to get the gray value from the RGB values.

     //https://github.com/image-js/image-js/blob/9ab86a86f6c13a9a7d14c62566c1396c3c6f54f4/src/image/transform/greyAlgorithms.js
     function RGBToGrayScale(red,green,blue){
       //return red * 0.2126 + green * 0.7152 + blue * 0.0722;
       return (red * 6966 + green * 23436 + blue * 2366) >> 15;
     }
    

    There are many ways to calculate the gray value. Here, we use Rec. 709 luma coefficients for the conversion. The calculation speed is optimized using multiplication with integer and bit shifting.

  8. Create a function to infer whether to use black or white for the pixel based on the threshold.

     //return true if the value should be black. return false if the value should be white
     function threshold(grayscale){
       const thresholdValue = parseInt(document.getElementById("threshold").value);
       if (grayscale < thresholdValue) {
         return true;
       }else{
         return false;
       }
     }
    
  9. Iterate all the pixels and set the RGB values to white (255) or black (0).

     const pixels = imageData.data; //[r,g,b,a,...]
     for (var i = 0; i < pixels.length; i += 4) {
       const red = pixels[i];
       const green = pixels[i + 1];
       const blue = pixels[i + 2];
       const grayscale = RGBToGrayScale(red, green, blue)
       if (threshold(grayscale)) {
         pixels[i] = 0;
         pixels[i + 1] = 0;
         pixels[i + 2] = 0;
       }else{
         pixels[i] = 255;
         pixels[i + 1] = 255;
         pixels[i + 2] = 255;
       }
     }
    
  10. Put back the ImageData.

    context.putImageData(imageData, 0, 0);
    
  11. Display the converted image.

    document.getElementById("image").src = canvas.toDataURL();
    

Determine the Threshold with OTSU’s Method

We can use OTSU’s method to automatically determine the optimal threshold to separate the background and the foreground.

  1. Include the following JS file in the page.

    //https://github.com/cawfree/otsu/
    const histo = (data, bins) =>
      data.reduce((arr, e) => {
        arr[bins.indexOf(e)] += 1;
        return arr;
      }, [...Array(bins.length)].fill(0));
    
    const width = (hist, s, e) => {
      let v = 0;
      for (let i = s; i < e; i += 1) {
        v += hist[i];
      }
      return v;
    };
    
    const bins = data => Array.from(new Set(data)).sort((e0, e1) => e0 - e1);
    
    const weight = (hist, s, e, total) => {
      let v = 0;
      for (let i = s; i < e; i += 1) {
        v += hist[i];
      }
      return v / total;
    };
    
    const mean = (hist, bins, s, e, width) => {
      let v = 0;
      for (let i = s; i < e; i += 1) {
        v += hist[i] * bins[i];
      }
      return v * width;
    };
    
    const variance = (hist, bins, s, e, mean, width) => {
      let v = 0;
      for (let i = s; i < e; i += 1) {
        const d = bins[i] - mean;
        v += d * d * hist[i];
      }
      return v * width;
    };
    
    const cross = (wb, vb, wf, vf) => wb * vb + wf * vf;
    
    const otsu = (data) => {
      const b = bins(data);
      const h = histo(data, b);
      const { length: total } = data;
      const vars = [...Array(b.length)].map((_, i) => {
        const s0 = 0;
        const e0 = i;
        const s1 = i;
        const e1 = h.length;
    
        const w0 = 1 / width(h, s0, e0);
        const w1 = 1 / width(h, s1, e1);
    
        const wb = weight(h, s0, e0, total);
        const vb = variance(h, b, s0, e0, mean(h, b, s0, e0, w0), w0);
    
        const wf = weight(h, s1, e1, total);
        const vf = variance(h, b, s1, e1, mean(h, b, s1, e1, w1), w1);
    
        const x = cross(wb, vb, wf, vf);
    
        return !isNaN(x) ? x : Number.POSITIVE_INFINITY;
      });
    
      return b[vars.indexOf(Math.min(...vars))];
    };
    
  2. Calculate the threshold with OTSU. We have to pass the gray histogram to the otsu function.

    function calculateThresholdWithOTSU(){
      const image = document.getElementById("imageHidden");
      const canvas = document.getElementById("canvasHidden");
      const width = image.naturalWidth;
      const height = image.naturalHeight;
      const context = canvas.getContext('2d');
      canvas.width = width;
      canvas.height = height;
      context.drawImage(image, 0, 0);
      const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
      const pixels = imageData.data; //[r,g,b,a,...]
      const grayscaleValues = [];
      for (var i = 0; i < pixels.length; i += 4) {
        const red = pixels[i];
        const green = pixels[i + 1];
        const blue = pixels[i + 2];
        const grayscale = RGBToGrayScale(red, green, blue)
        grayscaleValues.push(grayscale);
      }
      document.getElementById("threshold").value = otsu(grayscaleValues);
    }
    

Convert an Image to Black and White with Dynamic Web TWAIN

Dynamic Web TWAIN is a document scanning SDK making it possible to scan documents in the browser. It provides various image processing methods. We can use its ConvertToBW method to convert an image to black and white.

The advantage of using it is that it can be used to batch process a large volume of images as the processing is done using a native process. The output image file also has a smaller size as native image libraries like libpng are used to optimize the size.

Here are the steps to use it:

  1. Include Dynamic Web TWAIN in the page.

    <script src="https://unpkg.com/dwt@18.4.2/dist/dynamsoft.webtwain.min.js"></script>
    
  2. Initialize an instance of Web TWAIN and use it to convert the images. You can apply for its license here.

    let DWObject;
    Dynamsoft.DWT.AutoLoad = false;
    Dynamsoft.DWT.ProductKey = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day trial
    Dynamsoft.DWT.ResourcesPath = "https://unpkg.com/dwt@18.4.2/dist";
       
    async function convertWithDWT(){
      if (!DWObject) {
        await initDWT();
      }
      DWObject.RemoveAllImages();
      let response = await fetch(document.getElementById("imageHidden").src);
      let buffer = await response.arrayBuffer();
      DWObject.LoadImageFromBinary(buffer,
      function(){
        const convert = async () => {
          const thresholdValue = parseInt(document.getElementById("threshold").value);
          await DWObject.RunCommandAsync({command:"convertToBW",parameter:{index:0,threshold:thresholdValue}});
          document.getElementById("image").src = DWObject.GetImageURL(0);
        }
        convert();
      },
      function(errorCode, errorString){
        console.log(errorString);
      })
    }
       
    function initDWT(){
      return new Promise((resolve, reject) => {
        const title = document.querySelector("h2").innerText;
        document.querySelector("h2").innerText = "Loading Dynamic Web TWAIN...";
        Dynamsoft.DWT.CreateDWTObjectEx(
        {
          WebTwainId: 'dwtcontrol'
        },
        function(obj) {
          DWObject = obj;
          document.querySelector("h2").innerText = title;
          resolve();
        },
        function(err) {
          console.log(err);
          document.querySelector("h2").innerText = "Failed to load Dynamic Web TWAIN";
          reject(err);
        }
      );
      })
    }
    

Source Code

You can find all the code and an online demo in the following repo:

https://github.com/tony-xlh/Color-Conversion-JavaScript