Writing PPM Image Files in C ------------------------------------------------------------------- < intro > PPM is probably the simplest image format that is somewhat decently supported by image viewers. While it's not efficient or useful for storing and sharing images, its simplicity makes it a quick way to produce an image from your C programs (or any other language) in like 20 lines of code. This saves a lot of time when you want to quickly put together a program that renders an image and it's also more appealing to both newbyes and experts than having to mess with an image library. It's especially neat when your write the PPM output directly to stdout and pipe it into an image viewer, such as imagemagick's display (./myprogram | display), which is how I used it in a raytracer I recently wrote. < index > /p1 Specification /p2 Creating and displaying a sample PPM image in bash /p3 Walkthrough of a C function to write PPM files /p4 Full example code using the function from ppm03 < p1 > It starts with a header plain text string that contains: - A magic string "P6" - The width of the image in pixels - The height of the image in pixels - The maximum color value (must be less or equal than 0xffff). Each element of the header is separated by whitespace (either spaces, tab, CR, LF) and the header ends with a single whitespace character. For example the header for a 640x480 image with 255 colors would be "P6 640 480 255\n". The header is followed by the binary pixel data. The order of the pixel data is as straightforward as it gets: rows are written from top to bottom, and each row contains the pixels' r, g, b values from left to right. The r, g, b values are 1 byte each if the maximum color value is less or equal than 0xff, otherwise they are 2 bytes. So, if we make a 2x2 checkerboard of white and black pixels in a 255 colors image, the data will look like this: ff ff ff 00 00 00 00 00 00 ff ff ff < p2 > Now that we know the PPM format, we can easily produce said image and display it: =================================================================== $ printf "P6 2 2 255 \xff\xff\xff\x00\x00\x00\x00\x00\x00\xff\xff\xff" > test.ppm $ od -Ax -w10 -tx1z test.ppm 000000 50 36 20 32 20 32 20 32 35 35 >P6 2 2 255< 00000a 0a ff ff ff 00 00 00 00 00 00 >..........< 000014 ff ff ff >...< 000017 $ display -sample 50x50 test.ppm =================================================================== And of course, we can pipe the file directly into display: =================================================================== $ printf "P6 2 2 255 \xff\xff\xff\x00\x00\x00\x00\x00\x00\xff\xff\xff" \ | display -sample 50x50 - =================================================================== Note that the "sample 50x50 -" parameters are purely to make this 2x2px image visible since it's so small, with a normal picture just piping into "display" will be enough. < p3 > Now that we have mastered the PPM format, writing a C function to produce a ppm file is trivial. I strongly recommend you go and try writing the program yourself, it's a good excercise and it's more fun than getting spoonfed. If you aren't a complete beginner at C you can skip to the bottom of the file (p4) to see the full code which should be self explanatory. First of all, let's define ourselves some type shortcuts: =================================================================== typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32; typedef float f32; =================================================================== Our function will take: - A file stream to write the image to - An array of floats defining the rgb values for the pixels with values between 0.0f and 1.0f. This is in the same order as the pixel data in the ppm format. - Width and height. These will be u16, so we will only accept image up to 65535x65535. It will return zero for success or non-zero values for errors. We will only use 255 colors to avoid having to specify the color depth every time. =================================================================== int fputppm(FILE* f, f32* px, u16 w, u16 h) { /* ??? */ return 0; } =================================================================== Writing the header is a simple fprintf (include stdio.h): =================================================================== fprintf(f, "P6 %u %u 255\n", w, h); =================================================================== Iterating the pixel array can be done with simple pointer math: =================================================================== for (i = 0; i < (u32)w * (u32)h; ++i) { /* do stuff */ px += 3; } =================================================================== Note how I cast w and h to u32 to prevent an integer overflow (two u16 values multiplied together can easily give results bigger than 0xffff). If you don't understand, here's a quick view of what happens in that loop: 2x2 image pixels: 1 2 3 4 Float array: pixel 1 pixel 2 pixel 3 pixel 4 .-------------. .-----------. .-----------. .-----------. r g b { 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 } First iteration (i=0): { 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 } ^ '-px points to this Second iteration (i=1): { 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 } ^ '-px points to this Third iteration (i=2): { 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0 } ^ '-px points to this ... and so on. This repeats width * height times, which is the total amount of pixels in the image. To convert from float to integers we just have to multiply by 255 since the floats we expect are in 0.0-1.0 range. Let's prepare an array with the r, g, b values as 1-byte integers. =================================================================== u8 color[3]; color[0] = (u8)(min(1.f, px[0]) * 255.f); color[1] = (u8)(min(1.f, px[1]) * 255.f); color[2] = (u8)(min(1.f, px[2]) * 255.f); =================================================================== Notice how I prevent overflows by clamping the floats to 1.f using the min macro. We could also do this for underflows but I don't think they're as common. My min macro (the brackets are for safety): =================================================================== #define min(a, b) ((a) < (b) ? (a) : (b)) =================================================================== Now we can fwrite the pixel: =================================================================== fwrite(color, 1, 3, f); =================================================================== Where 1 is the size of each element (1 byte). < p4 > Here's an example program that generates a checkerboard and writes it to stdout using our function with full error checking: =================================================================== /* gcc ppm.c && ./a.out | display */ #include typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32; typedef float f32; #define min(a, b) ((a) < (b) ? (a) : (b)) int fputppm(FILE* f, f32* px, u16 w, u16 h) { u32 i; if (fprintf(f, "P6 %u %u 255\n", w, h) <= 0) { return -1; } for (i = 0; i < (u32)w * (u32)h; ++i) { u8 color[3]; color[0] = (u8)(min(1.f, px[0]) * 255.f); color[1] = (u8)(min(1.f, px[1]) * 255.f); color[2] = (u8)(min(1.f, px[2]) * 255.f); if (fwrite(color, 1, 3, f) != 3) { return -2; } px += 3; } return 0; } #define NCELLS 8 #define CELL_W 32 #define W (NCELLS * CELL_W) int main(int argc, char* argv[]) { /* generate a checkerboard */ f32 px[W * W * 3]; f32* p = px; u16 i, j; int e; for (j = 0; j < W; ++j) { for (i = 0; i < W; ++i) { u16 xodd = (i / CELL_W) & 1; u16 yodd = (j / CELL_W) & 1; p[0] = p[2] = 1.f; if (xodd ^ yodd) { p[1] = 1.f; } p += 3; } } /* output ppm to stdout */ e = fputppm(stdout, px, W, W); if (e) { fprintf(stderr, "fputppm failed with error %d\n", e); return 1; } return 0; } ===================================================================