My digital image format
2015-03-26A friend asked me how computer images work? Or maybe I should say digital images. Instead of explaining one of the complicated computer image formats that already exists today. I decided to build my own image format form the scratch. It will be much easier to explain it if I take it from the beginning and explain each of the layers in an image format as I add them to the image format. If I create the format I can very simply create readers or viewers of my image format to prove that this is the way the digital images work.
A digital image consists of small squares that can have a color. These squares with color are called a pixel.
Ok, let’s begin with the creation then. My image format will be stored as plain text so that it's possible read and create it with a simple text editor if needed.
Black and white image
A black and white digital image only has two colors to keep track of for each pixel and that is if the pixel should be black or white. I decided to represent the white pixel as 0 and a black pixel as 1, when I store my image in a file.
With this format, you will be able to see the picture almost as ASCI art also in the storage format.
00000000000000000000\n
00000011111000000000\n
00000011111000000000\n
00000011111000000000\n
00000011111000000000\n
00000000000000000000\n
But storing the numbers zero and one in a file is not worth anything if you cannot display it in a web browser or in your application. So I also need to create a “translator” that can translate the storage format of the image to actual pixels on a webpage or in an application.
In this example I have created an image translator in JavaScript, so that means that I can distribute my image together with my image translator to any browser that can execute JavaScript. This is very powerful, since I don’t need to depend on if a specific browser supports my image format or not. As you can see my image translator or image renderer is only a few lines of code.
var res = image.split("\n");
var imageLineNumber = 0;
res.forEach(function(imageLine){
if(imageLineNumber === 0)
{
canvas.width = imageLine.length;
canvas.height = res.length;
}
for(i=0; i < imageLine.length; i++)
{
if(imageLine.charAt(i) === "1")
{
ctx.fillRect(i,imageLineNumber ,1,1);
}
}
imageLineNumber++;
})
As you see in this code example, it only takes a few lines of code to display my image in a HTML canvas. You can easily create an image renderer with other languages also like C#, java, or something else also.
The image my image renderer produces looks like this (it’s a bit enhanced, so that you can see it).
Then I think it’s interesting to see how big storage size of the images will be for my file format and then compare it with the PNG image format. So I will sow those numbers like this for each enhancement of my image format I do.
Color to the image
Great, now I have an image format that I can display on a webpage. But it would be nice to have an image format with color also. So, let’s start simple with some colors. Let’s first create a format that only can handle 10 different colors, because then we only need 1 character to represent the color information for a specific pixel in the image.
00000000000000000000\n
00000011111000000000\n
00000011111000000000\n
00000022222000000000\n
00000022222000000000\n
00000000000000000000\n
To be able to render the new image format I now have to update my image renderer to handle 10 different colors instead of the just the 2 colors that I had earlier.
var res = image.split("\n");
var imageLineNumber = 0;
res.forEach(function(imageLine){
if(imageLineNumber === 0)
{
canvas.width = imageLine.length;
canvas.height = res.length;
}
for(i=0; i < imageLine.length; i++)
{
if(imageLine.charAt(i) === "1")
{
ctx.fillStyle='#000000';
ctx.fillRect(i,imageLineNumber ,1,1);
}
else if(imageLine.charAt(i) === "2")
{
ctx.fillStyle='#FF0000';
ctx.fillRect(i,imageLineNumber ,1,1);
}
//TODO: Add a else if statement for each one of the 10 colors
}
imageLineNumber++;
})
The image my image renderer produces looks like this (it’s a bit enhanced, so that you can see it).
Interesting that the image size for the PNG now got smaller after that I added one more color. I have used the auto detect bit depth in the application Paint.net, when I have saved the image.
16 million colors
Let’s take the next step with colors, to my Image format. The next step is to give the image format a potential to select one of 16 million colors for each pixel. One way of representing one of the 16 million colors is to use 6 digit hexadecimal numbers like this one for instance #FFFFFF (the color white).
Then the storage format of my small image will look like this:
#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF; #FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;\n
#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#000000;#000000;#000000;#000000;#000000; #FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;\n
#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#000000;#000000;#000000;#000000;#000000; #FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;\n
#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FF0000;#FF0000;#FF0000;#FF0000;#FF0000; #FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;\n
#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FF0000;#FF0000;#FF0000;#FF0000;#FF0000; #FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;\n
#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF; #FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;#FFFFFF;\n
As you see now this will be a much bigger size of my image, when I have the potential of 16 million colors for each pixel. It’s also much harder to see how the image looks like in the storage format now.
Again I need to change the my image renderer to handle the new color format of my picture, but this actually makes my image renderer easier, since I’m actually storing the color information in a format that canvas already understands.
var res = image.split("\n");
var imageLineNumber = 0;
res.forEach(function(imageLine){
if(imageLineNumber === 0)
{
canvas.width = imageLine.length;
canvas.height = res.length;
}
var imageLineArray = imageLine.split(";")
for(i=0; i < imageLineArray.length; i++)
{
ctx.fillStyle = imageLineArray[i];
ctx.fillRect(i,imageLineNumber ,1,1);
}
imageLineNumber++;
})
The image my image renderer produces looks like this (it’s a bit enhanced, so that you can see it).
Interesting that the PNG image format still is smaller than my black and white example. I saved the image with the bit depth 32 in the application Paint.net. The size on my image format grow a lot.
Compression to make the image smaller
I think it was a bit boring that my image format now got so dramatically bigger then then PNG image format. The reason for this is that the PNG image format has some built in compression. Then I need to build compression to my format also.
One way of building compression to an image format is to take advantage of the fact that a lot of pixels have the same color. So, instead of storing the color information #FFFFFF twenty times on the first row of the image, I can store that the same color should be repeated twenty times like this #FFFFFFx20.
Then storage of the image would look like this:
#FFFFFFx20;\n
#FFFFFFx6;#000000x5;#FFFFFFx9;\n
#FFFFFFx6;#000000x5;#FFFFFFx9;\n
#FFFFFFx6;#FF0000x5;#FFFFFFx9;\n
#FFFFFFx6;#FF0000x5;#FFFFFFx9;\n
#FFFFFFx20;\n
Big changes to my image renderer is now needed, to be able unpack compressed image format.
var res = image.split("\n");
var imageLineNumber = 0;
res.forEach(function (imageLine) {
var linePixelCounter = 0;
if (imageLineNumber === 0) {
var rowLenght = 0;
var firstRow = imageLine.split(";");
for (firstRowPixeCount = 0; firstRowPixeCount < firstRow.length - 1; firstRowPixeCount++) {
rowLenght = rowLenght + parseInt(firstRow[firstRowPixeCount].split("x")[1]);
}
canvas.width = rowLenght;
canvas.height = res.length;
}
var imageLineArray = imageLine.split(";");
for (i = 0; i < imageLineArray.length; i++) {
var imageCompressedLineArray = imageLineArray[i].split("x");
for (x = 0; x < imageCompressedLineArray[1]; x++) {
ctx.fillStyle = imageCompressedLineArray[0];
ctx.fillRect(linePixelCounter, imageLineNumber, 1, 1);
linePixelCounter = linePixelCounter + 1;
}
}
imageLineNumber++;
})
The image my image renderer produces looks like this (it’s a bit enhanced, so that you can see it).
Now the image renderer got a bit more complicated than earlier. But still it’s fairly simple code. I also got a dramatically smaller image size, I even got it smaller than what the PNG compression could do. Nice!
I will stop here on my explanation about digital images and my own image format for the moment. Of course there are much more things to explore when it comes to digital image formats, like how to resize an image and what is the differences between compressing a digital photo and simple image with a small amount of colors like my example. But these other questions have to answered in some other blog post.
Interesting Links: