Home » Blog » Dev » It’s Not Easy Being Bleen: A Color-Changing Javascript Adventure

It’s Not Easy Being Bleen: A Color-Changing Javascript Adventure

DanPost-Kermit

It may come as no surprise that our name, Grue & Bleen, is a play on the words Blue & Green. Swap “Gr” and the “Bl” and you’ve got us. It also refers to a linguistic and philosophical theory related to the Riddle of Induction, which states that something as fundamental as our perception of color could change dramatically in the future.

Technology & Perception

The colors we currently perceive as green and blue, then, can be more accurately called “grue” and “bleen”, somewhere in between the two that allows room for this eventual perceptual shift. Some historians even believe that ancient civilizations may not have been able to distinguish between the color green and blue, and it was only once we (the Egyptians) were able to manufacture the color that we made any distinction at all between the two. This is a great example of how deeply the technologies we create can change us, even on the most basic perceptual levels.

Grue & Bleen demon overlords
I, for one, accept our Grue & Bleen demon overlords.

Let’s Experiment with Javascript Color Manipulation

Let’s make a perceptual leap ourselves! What if we woke up and everything green was blue? Let’s dive into some simple video color manipulation using Javascript to get a taste of the post-blue-green-shift world. We’re going to take a video our favorite amphibious singing puppet and make him blue!

Kermit the Frog Blue

Sample output. Maybe it’ll be easier on our friend Kermit than being green (or bleen!).

A Brief Intro to HSL Color: Degrees Above the Rest

The HTML canvas object uses the (perhaps more familiar) RGB system to manipulate color, but for our purposes, HSL color will be more useful. HSL stands for hue, saturation, and luminosity. We want to select a range of hue, in our case green, and easily shift the hue in that range to another, without changing the saturation or luminosity. HSL works on a degree sale, from 0 to 360, so it will make this shift easy.

HSL Color Wheel
HSL Color Wheel: hue is a value from 0 to 360. We’ll say that the green range is approximately between 55 and 185 degrees.

We’ll take colors in the green range (say, 55 degrees to 185 degrees), and add a set number of degrees to shift it into the blue range (around 185 to 270 degrees). This is not an exact science, but will be fine for our purposes.

The Markup

This is the code that will sit in a file called ‘index.html’. It consists of a video and two canvases. When the video plays, it will be copied frame by frame to the first canvas, and then we will manipulate the output onto the second canvas. We’ll hide the first canvas, since it’s really only there as an intermediate step between the video and our altered copy.

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html >
<head>
   <meta http-equiv=”content-type” content=”text/html; charset=UTF-8″ />
   <style>
   div {
       float: left;
       border: 1px solid #444444;
       padding: 10px;
       margin: 10px;
       background: #3B3B3B;
   }
   </style>
   <script type=”text/javascript” src=”main.js”></script>
</head>
<body onload=”processor.doLoad()”>
   <div>
       <video id=”video” src=”kermit.mp4″ controls=”true” width=”480″ height=”360″ type=”video/mp4″></video>
   </div>
   <div>
       <canvas id=”c1″ width=”480″ height=”360″ style=”display:none;”></canvas>
       <canvas id=”c2″ width=”480″ height=”360″></canvas>
   </div>
</body>
</html>

The Code

In the javascript, we’ll grab each frame from the video, and manipulate the color to our heart’s content, frame by frame and pixel by pixel. Explanation follows.

var processor = {
 timerCallback: function() {
   if (this.video.paused || this.video.ended) {
     return;
   }
   this.computeFrame();
   var self = this;
   setTimeout(function () {
       self.timerCallback();
     }, 0);
 },
 doLoad: function() {
   this.video = document.getElementById(“video”);
   this.c1 = document.getElementById(“c1”);
   this.ctx1 = this.c1.getContext(“2d”);
   this.c2 = document.getElementById(“c2”);
   this.ctx2 = this.c2.getContext(“2d”);
   var self = this;
   this.video.addEventListener(“play”, function() {
       self.width = self.video.videoWidth;
       self.height = self.video.videoHeight;
       self.timerCallback();
     }, false);
 },
 computeFrame: function() {
   this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
   var frame = this.ctx1.getImageData(0, 0, this.width, this.height);
var l = frame.data.length / 4;
   for (var i = 0; i < l; i++) {
     var r = frame.data[i * 4 + 0];
     var g = frame.data[i * 4 + 1];
     var b = frame.data[i * 4 + 2];
     var hsl=this.rgbToHsl(r,g,b);
     var hue=hsl.h*360;
     // green to blue
     if (hue > 55 && hue < 185){
       var newRgb=this.hslToRgb(hsl.h+.35,hsl.s,hsl.l);
       frame.data[i * 4 + 0] = newRgb.r;
       frame.data[i * 4 + 1] = newRgb.g;
       frame.data[i * 4 + 2] = newRgb.b;
       frame.data[i * 4 + 3] = 255;
     }
     // blue to green
     if (hue >= 185 && hue < 270){
       var newRgb=this.hslToRgb(hsl.h-.25,hsl.s,hsl.l);
       frame.data[i * 4 + 0] = newRgb.r;
       frame.data[i * 4 + 1] = newRgb.g;
       frame.data[i * 4 + 2] = newRgb.b;
       frame.data[i * 4 + 3] = 255;
     }
   }
   this.ctx2.putImageData(frame, 0, 0);
   return;
 },
 ////////////////////////
 // Helper functions
 //
 rgbToHsl: function(r, g, b){
   r /= 255, g /= 255, b /= 255;
   var max = Math.max(r, g, b), min = Math.min(r, g, b);
   var h, s, l = (max + min) / 2;
   if(max == min){
     h = s = 0; // achromatic
   }else{
     var d = max – min;
     s = l > 0.5 ? d / (2 – max – min) : d / (max + min);
     switch(max){
       case r: h = (g – b) / d + (g < b ? 6 : 0); break;
       case g: h = (b – r) / d + 2; break;
       case b: h = (r – g) / d + 4; break;
     }
     h /= 6;
   }
   return({ h:h, s:s, l:l });
 },
 hslToRgb: function(h, s, l){
   var r, g, b;
   if(s == 0){
     r = g = b = l; // achromatic
   }else{
     function hue2rgb(p, q, t){
       if(t < 0) t += 1;
       if(t > 1) t -= 1;
       if(t < 1/6) return p + (q – p) * 6 * t;
       if(t < 1/2) return q;
       if(t < 2/3) return p + (q – p) * (2/3 – t) * 6;
       return p;
     }
     var q = l < 0.5 ? l * (1 + s) : l + s – l * s;
     var p = 2 * l – q;
     r = hue2rgb(p, q, h + 1/3);
     g = hue2rgb(p, q, h);
     b = hue2rgb(p, q, h – 1/3);
   }
   return({
     r:Math.round(r * 255),
     g:Math.round(g * 255),
     b:Math.round(b * 255),
   });
 }
};

The Explanation

We have just a few functions we’re working with:

doLoad

This function does all of our setup. It gets a reference to the video and the first and second canvas, along with their contexts. It then adds an event listener to the video, so that when it plays it calls timerPlayback to begin processing the frames.

timerCallback

If the video is paused or stopped, return. Otherwise, call computeFrame then recursively call timerCallback to continue processing frames.

computeFrame

This is where most of the magic happens.

  1. We draw the current frame onto the first canvas
  2. ‍Then we grab the image data from the first canvas, for us to play with
  3. ‍Then we loop through every pixel in that frame, one by one
  4. We get the r, g, and b channels of the current pixel, and then use a helper function to convert the rgb value to an HSL value
  5. If the value is in the green range (between 55 and 185), we add .35 to the value to bump it into the blue range
  6. ‍If it’s in the blue range, we subtract .25 to lower it into the green range
  7. Then, we push the altered image data onto the second canvas

rgbToHsl & hslToRgb

These are helper functions to convert between RGB and HSL.

See it in action!

Right here: It’s not easy being Bleen! Eventually, we want to make an augmented reality app that flips your color perception. Stay tuned for that!

CUBE-Solved-2.svg

About Great Big Digital

Achieve your website goals with customized data, intuitive UX, and intentional design.