Sunday, May 10, 2009

Simple image output

Sometimes, it can be nice to have a dead-simple format for bitmap images. If I'm writing some low level code (say, in C) dealing with graphics, at times I will want a quick way to quickly output an image file. Let's say that I'm too lazy to bring in a graphics library that can write actual .pngs . The .bmp format, while simple, is a bit more complicated than a list of rgb values, because of alignment.

I wrote a little program that converts ".simple" images (which are a list of R,G,B bytes) into 24 bit .bmp files. So, your program simply writes a tiny header and three bytes per pixel, and simpletobmp.exe turns this into a .bmp.

This works well from Python, too, when the PIL isn't around.

Here is an example of how to draw a ".simple" image:
FILE * fout;
fout = fopen("test.simple","wb");
fputc('S', fout);
fputc('2', fout);
fputc('4', fout);
int x,y,width,height;
width = 512; height = 256;
fwrite(&width,sizeof(int), 1, fout); 
fwrite(&height,sizeof(int), 1, fout); 

for (y=0; y<height; y++)
{
  for (x=0; x<width; x++)
  {
    fputc( y%256 , fout); //Red
    fputc( x%256 , fout); //Green
    fputc( 0 , fout);          //Blue
  }
}
fclose(fout);


Or, in Python,

import array
fout = open('pyout.simple', 'wb')

chars = array.array('c') #char
chars.append('S')
chars.append('2')
chars.append('4')
chars.tofile(fout)

WIDTH=512; HEIGHT=256
ints = array.array('l') #signed long
ints.append(WIDTH)
ints.append(HEIGHT)
ints.tofile(fout)

bytes = array.array('B') #unsigned char
for y in range(HEIGHT):
 for x in range(WIDTH):
  bytes.append(y%256)
  bytes.append(x%256)
  bytes.append(0)

bytes.tofile(fout)
fout.close()
(This one draws a gradient in red and green. Change the fputc lines in the inner loop to draw the image you want. A common usage is to set up a 2d array of pixels, draw the picture into that array, and then output everything to a file).

Now, one can run
simpletobmp.exe o test.simple test.bmp
to get the image.

This is very similar to how in Linux, one can write a .ppm file, which is, literally, a brief header and list of rgb values. The .ppm format even accepts pixel information in human-readable ascii digits! Sounds ridiculous, but this type of thing can be useful.

Download:
Windows binary
Source LGPL license.

Simpletobmp uses the LGPL bmp_io library by John Burkardt.

Bit packing preprocessor

When working at lower-levels, sometimes it pays to conserve bits. Binary formats sometimes use all of the bits they are given - when I was working with MIDI, I came across this a lot. (If you have a full int, you might as well use all 32 of those guys.)

On a more practical note, 16bit color is often 5bits red, 6bits green, and 5bits blue. A color is stored as a 16 bit integer in the format rrrrrggggggbbbbb, where each letter represents a bit. It isn't difficult to write code to extract these bit fields, but to make it faster and more readable, I wrote some Python to write the C for me. The Python script takes a string like "00rrr000" and outputs C code with the appropriate shifts and masks.

Bit tools. Example usage:
> necessarybits(640)
10 bits are required to store values up to 640
2 ** 10 = 1024

> print tobinary(46)
00101110

> print frombinary('1100_1100') 
204

> pattern('00rrr000', True)
  r is a 3bit number
  Packing:
  assert(r<8);
  unsigned char packed |= r<<3;
  Unpacking:
  unsigned char r = (packed & 0x3f)>>3; //packed & 0b00111111

> pattern('00000bbb')
  b is a 3bit number
  Packing:
  unsigned char packed |= b;
  Unpacking:
  unsigned char b = packed & 0x7; //packed & 0b00000111


I find "00rrr000" to be a lot clearer than '(packed & 0x3f)>>3' .

Python source
(34 lines of quick/unpretty code)