Hello there! You are currently visiting gopherspace through a
proxy. To learn more about gopher and how to browse it, read this.
______________________________________________________________________

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 <stdio.h>

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;
}
===================================================================