2.2: Working with Colors
- Download code
- 02-canvas.zip
- Live example
- examples/02-canvas/index.html
In the last module, we used the HTML canvas element to draw a Piet Mondrian canvas. Drawing individual rectangles by hand is kinda cool, but things don’t get really exciting until we start using JS code to calculate the colors of the pixels we’re drawing.
We’re going to introduce a few concepts here.
Colors
Colors in Radiance are based on the HTML RGB color model: a color has red, green, and blue values, which range from 0 through 255. (You’ll sometimes see 255 sometimes written as 0xff
, for consistency with the HTML hex color model.)
Create a new file in your project called modules/color.js
, with the following content:
// modules/color.js
export class Color {
#r = 0;
#g = 0;
#b = 0;
constructor(r, g, b) {
this.#r = r;
this.#g = g;
this.#b = b;
}
get r() { return this.#r; }
get g() { return this.#g; }
get b() { return this.#b; }
get rgba() { return [this.r, this.g, this.b, 0xFF] };
get html() { return `rgb(${this.r},${this.g},${this.b})` };
static White = new Color(255, 255, 255);
static Black = new Color(0, 0, 0);
static Grey = new Color(127, 127, 127);
static Red = new Color(255,0,0);
static Green = new Color(0,255,0);
static Blue = new Color(0,0,255);
static Yellow = new Color(255,255,0);
static Magenta = new Color(255,0,255);
static Cyan = new Color(0,255,255);
/** Parse an HTML color string like #fff, #abc123 or rgb(10,20,30),
* and return an array of [r,g,b,a] values */
static parse = str => {
str = str.replace(/\s/g, ''); // Remove all spaces
let values, r, g, b;
// Checks for 6 digit hex (#abc123)
if (values = /#([\da-f]{2})([\da-f]{2})([\da-f]{2})/i.exec(str)) {
[r,g,b] = values.slice(1).map(c => parseInt(c, 16));
return new Color(r,g,b);
}
// Checks for 3 digit hex (#abc)
if (values = /#([\da-f])([\da-f])([\da-f])/i.exec(str)) {
[r,g,b] = values.slice(1).map(c => parseInt(c, 16) * 17);
return new Color(r,g,b);
}
// Checks for rgb(1,2,3) string and cast using unary + operator
if (values = /rgb\(([\d]+),([\d]+),([\d]+)\)/.exec(str)) {
[r,g,b] = [+values[1], +values[2], +values[3]];
return new Color(r,g,b);
}
throw Error(`Invalid color: ${str}`);
}
add = (that) => new Color(this.r + that.r, this.g + that.g, this.b + that.b);
multiply = (that) => {
let rr = Math.floor(this.r * that.r / 0xff);
let gg = Math.floor(this.g * that.g / 0xff);
let bb = Math.floor(this.b * that.b / 0xff);
return new Color(rr, gg, bb);
}
scale = (factor) => new Color(this.r * factor, this.g * factor, this.b * factor);
}
Constructing new colors
We want to be able to create colors by providing either numeric RGB values or on HTML color strings.
Many object-oriented languages, like Java and C#, support something called method overloading, which lets us define multiple methods with the same name but with different arguments, and because we can use method overloading on constructors we could write something like this:
public Color(string htmlString) {
/* create a color from an HTML string */
}
public Color(int red, int green, int blue) {
/* create a color from the specified RGB values */
}
Because JavaScript is a dynamically typed languages, it doesn’t support this kind of strict method overloading – so instead, we’re going to define two different methods.
The constructor for our Color
class takes three r, g, b values:
constructor(r, g, b) {
this.#r = r;
this.#g = g;
this.#b = b;
}
To construct a color based on an HTML color string like #ff9900
or rgb(50,20,0)
, call the Color.parse
static method:
var red = Color.parse("#F00");
var green = Color.parse("rgb(0,255,0)");
The Color
class also defines a set of predefined colors we can use in our scenes:
static Black = new Color(0, 0, 0);
static White = new Color(255, 255, 255);
static Grey = new Color(127, 127, 127);
static Red = new Color(255,0,0);
static Green = new Color(0,255,0);
static Blue = new Color(0,0,255);
static Yellow = new Color(255,255,0);
static Magenta = new Color(255,0,255);
static Cyan = new Color(0,255,255);
Finally, we define three methods for doing color arithmetic.
Adding two colours together always makes colours brighter - think of shining a red light and a green light onto the same white wall; the red will combine with the green and the wall will appear yellow. Your computer screen is actually made of red, green and blue pixels, so where you see anything on your screen that’s white, that’s creating by adding red, green, and blue.
Scaling a colour makes it lighter or darker. Scaling by 2 makes a colour twice as bright; scaling by 0.5 makes it half as bright.
Multiplying colours will multiply each channel separately, and we use this to simulate how light interacts with coloured surfaces. If you’ve ever stood under a yellow sodium lamp wearing a blue shirt, you might have noticed your shirt looks black: blue fabric appears blue because it absorbs yellow light and reflects blue light, but if the only light falling on it is yellow, there’s no light to reflect and it looks black.
In colour arithmetic, it might help to think of white as 1 and black as 0:
red × white = red
red × black = black
red + black = red
red + white = white
Review & Recap
- Radiance uses the HTML color model; colors have a red, green, and blue value, each from 0 to 255.
One of the simplest procedural patterns is a chessboard pattern.
Here’s the getColorAtPixel
function for drawing a chessboard pattern. Based on the tile size, we work out whether this pixel is in an odd-numbered or an even-numbered row and column:
(x, y) => {
const xOdd = (x % (2 * size) < size);
const yOdd = (y % (2 * size) < size);
return (xOdd != yOdd ? color1 : color2);
}
Try it live: examples/02-canvas/index.html#chessboard
We can also calculate individual red, green, and blue pixel values based on the x/y coordinates passed into the function.
(x, y) => {
let r = (4 * x) % 256
let g = (x + y) % 256
let b = y % 256
return `rgb(${r},${b},${g})`
}
You should get an image something like this:
Try it live: examples/02-canvas/index.html#gradiance
Exercise: Procedural Patterns
Download the code for this section from examples/02-canvas.zip
Add a new pattern to modules/patterns.js
:
-
Add a new
export function MyPattern
tomodules/patterns.js
, -
Come up with a new method for translating the
x,y
coordinates intor,g,b
color values - there’s some suggestions below -
Add a new
case
to theswitch
statement inmain.js
:case "#mypattern": Patterns.MyPattern(myCanvas); break;
-
View your pattern by going to
index.html#mypattern
Here’s a couple of fun things to try:
- The modulus operator in JavaScript is
%
, so an expression likex % 256
will always give you a value between 0 and 255 - useful for constructing valid RGB colors where each of the red, green, and blue values has be between 0 and 255. - You can also write
256
as0xff
, which might look more natural if you’re used to HTML hex color values. Math.abs(x)
will give you the absolute value (i.e. always positive) ofx
- The trigonometry functions
Math.sin(x)
andMath.cos(x)
will give you a value between -1 and +1; try multiplying this by thex
ory
values
Here’s a few more examples:
Supernova
Try it live: examples/02-canvas/index.html#supernova
(x, y) => {
let r = (x * (1 + Math.sin(y / 100))) % 255 // 4*x % 255;
let g = Math.abs(20 * Math.tan(y)) % 255
let b = (y * (1 + Math.cos(x / 2))) % 255 // (x+y) % 255;
return `rgb(${r},${g},${b})`
}
Lasers
Try it live: examples/02-canvas/index.html#lasers
(x, y) => {
let r = 255 * Math.sin(200 - x / 20) + 255 * Math.cos(150 - y / 20)
let g = 255 * Math.sin(200 - x / 20)
let b = 255 * Math.cos(150 - y / 20)
return `rgb(${r},${g},${b})`
}
The HTML Canvas: Review & Recap
- The
canvas
element and API give us a way to draw graphics using JavaScript - To draw graphics, we need to get a graphics context for our canvas element.
- We control the color we’re drawing by setting the context’s
fillStyle
to an HTML color value. - We can draw individual pixels by using the
fillRect
method and specifying a width and height of ` pixel.
References and Further Reading
- The Canvas API: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
- HTML Colors reference at w3schools: https://www.w3schools.com/html/html_colors.asp
- Download code
- 02-canvas.zip
- Live example
- examples/02-canvas/index.html