The reason I asked was because of how easy the portable pixmap (ppm, one of the netpbm formats) is.
The minimal C99 code I need (and use for quick sketches in particular) for image generation is
Code:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
typedef struct {
int width;
int height;
unsigned char data[];
} ppm_t;
static inline ppm_t *ppm_create(const int width, const int height)
{
ppm_t *image;
if (width < 1 || height < 1) {
errno = EINVAL;
return NULL;
}
image = malloc(sizeof (ppm_t) + 3 * width * height);
if (!image) {
errno = EINVAL;
return NULL;
}
image->width = width;
image->height = height;
return image;
}
static inline int ppm_setpixel(ppm_t *const image, const int x, const int y, const int color)
{
if (image && x >= 0 && y >= 0 && x < image->width && y < image->height) {
unsigned char *const p = image->data + 3*(y * image->width + x);
p[0] = color;
p[1] = color >> 8;
p[2] = color >> 16;
return color & 0xFFFFFF;
} else
return -1;
}
static inline int ppm_getpixel(const ppm_t *const image, const int x, const int y)
{
if (image && x >= 0 && y >= 0 && x < image->width && y < image->height) {
const unsigned char *const p = image->data + 3*(y * image->width + x);
return (int)p[0] + ((int)p[1] << 8) + ((int)p[2] << 16);
} else
return -1;
}
static int ppm_save(const ppm_t *const image, FILE *const out)
{
if (!image || !out)
return errno = EINVAL;
if (ferror(out))
return errno = EIO;
fprintf(out, "P6\n%d %d\n255\n", image->width, image->height);
if (fwrite(image->data, 3*image->width*image->height, 1, out) != 1)
return errno = EIO;
if (fflush(out) || ferror(out))
return errno = EIO;
return 0;
}
The ppm_setpixel() and ppm_getpixel() don't mind if you go out of bounds (it checks for valid coordinates), and take and return the color in the hexadecimal web-friendly format, 0xFFFFFF (0xFF0000 for red, 0x00FF00 for green, 0xFFFFFF for white, 0x000000 for black, and so on).
If working with non-RGB color spaces, or colors generated by floating-point algorithms, you can use something like the following function to compute your color values. This one takes the RGB components as floating-point numbers in the range [0, 1]:
Code:
static inline int ppm_color(const float red, const float green, const float blue)
{
int color;
if (red <= 0.0f)
color = 0x000000;
else
if (red < 1.0f)
color = (int)(0.5f + 255.0f * red) << 16;
else
color = 0xFF0000;
if (green >= 1.0f)
color |= 0x00FF00;
else
if (green > 0.0f)
color |= (int)(0.5f + 255.0f * green) << 8;
if (blue >= 1.0f)
blue |= 0x0000FF;
else
if (blue > 0.0f)
color |= (int)(0.5f + 255.0f * blue);
return color;
}
For the cases where I need alpha or blending, I tend to use 32-bit pixels, and convert to 24-bit at save time. Using 32-bit unsigned integers for ARGB (alpha-red-green-blue) makes it very simple to e.g. blend two pixel values easily.
Reading a PPM image is a bit more complicated, because there are a number of variants you may encounter, and because there might be comment lines between the format identifier and width and height information . If I read PPM inputs, I usually support P6 (ppm, rgb color) and P5 (pgm, grayscale) variants, although there are also P4 (pbm, black-and-white) and ASCII versions of all three (P3, P2, and P1, respectively). The easiest way to do that is to have one function read the header (format, width, height, and if not P4/P1, then the maximum component value), then read the data using a format-specific helper function.
For those interested in high dynamic range imaging (HDR), note that the PPM format does support 16 bits per component (48 bits per pixel). The byte order then is Gg (for grayscale) or RrGgBb (for RGB), the most significant byte first.