Storing Colors in a Pixel Grid

To create our page, we’re going to be storing pixels in an Ethereum smart contract.

To store colors, we have to pick a format to encode them in. Every byte we store on the Ethereum blockchain costs ether. So it’s important that we’re careful about what format we use to store our pixels.

Thankfully, we don’t need to invent our own format, we’re going to use 24-bit RGB color.

What that means is that we’re going to store 1 byte each for the red, green, and blue component for each pixel.

For example, when we use the color picker in Chrome, we can specify colors in RGBA.

RGBA stands for red-green-blue-alpha. Each one of these is called a “channel”, as in, the red channel or the alpha channel. The alpha specifies the transparency of the color, which we’re not going to use, but you could if you wanted to.

Notice that as we pick a different color, the values for red, green, and blue vary as a number between 0 and 255.

That’s because each channel is represented by 1 byte. And, I can understand if you haven’t been working with bits and bytes recently, so recall that 1 byte is 8 bits.

A bit can hold two values, either one or zero. And with 8 bits we can hold 2 to the 8th values, which is 256 values.

2 ** 8
// => 256

In JavaScript we can convert numbers between bases like this:

parseInt(255, 10).toString(2)
// => '11111111'
parseInt(235, 10).toString(2)
// => '11101011'

This converts 255 in base-10 into binary base-2.

There’s another common way to view the bytes of a color code and that is hex encoding. If you’ve done any web development at all you’ve certainly seen this:

#a62a40

This is just another way of encoding the same thing. Each RGB channel is encoded in two base-16 letters which are then concatenated together in one string.

[slide where they are colored properly]

So to convert our RGB value to hex we could do this:

var [r, g, b] = [166, 42, 64]
parseInt(r, 10).toString(16)
// => 'a6'
parseInt(g, 10).toString(16)
// => '2a'
parseInt(b, 10).toString(16)
// => '40'
[r, g, b].map((byte) => parseInt(byte, 10).toString(16)).join("")
// => 'a62a40'

And you’ll notice that’s the same hex code we see in our browser for that same color.

But there’s one caveat here, that’s specific to our implementation. Conventional hex color codes require that the hex code have exactly six digits. But if our input channel is less than 16, we are only given a single digit.

var low = 12
parseInt(low, 10).toString(16)
// => 'c'  // <-- Bad!

This is incorrect as it should be 0c. We can fix this by a JavaScript trick where we prepend zeros and slice them off:

var low = 12
('00' + parseInt(low, 10).toString(16)).slice(-2);
// => '0c' // <-- Good!

We can also convert colors the other direction:

parseInt('0c', 16).toString(10)
// => '12'

In this case we’re taking a base-16 number and converting it to base 10.

We’ll use this sort of conversion throughout the course.

  • We’re going to store our pixels in our smart contract storage as 3 raw bytes
  • When we draw this pixels in our browser’s canvas, we’re going to convert from these raw bytes into base 10
  • When we uploading pixels to our page from an image, we’re going to convert decimal colors back into hexadecimal.

We’re not going to have a ton of bit manipulation, but because we’re passing pixels around, understanding this color encoding will help a lot.

Course Cover image: Pixel art landscape by Mockingjay1701 CC By-SA 3.0

Discussion

0 comments