47

I am working in C on a physics experiment, Young's interference experiment and I made a program who prints to file a huge bunch of pixels:

for (i=0; i < width*width; i++)
{
    fwrite(hue(raster_matrix[i]), 1, 3, file);
}

Where hue, when given a value [0..255], gives back a char * with 3 bytes, R,G,B.

I would like to put a minimal header in my image file in order to make this raw file a valid image file.

More concise, switching from:

offset
0000 : height * width : data } my data, 24bit RGB pixels

to:

offset
0000 : dword : magic        \
     : /* ?? */              \
0012 : dword : height         } Header <--> common image file
0016 : dword : width         /
     : /* ?? */             /
0040 : height * width : data  } my data, 24bit RGB pixels
Cristik
  • 30,989
  • 25
  • 91
  • 127
Nope
  • 897
  • 1
  • 8
  • 15

4 Answers4

51

You probably want to use the PPM format which is what you're looking for: a minimal header followed by raw RGB.

Patrice Levesque
  • 2,079
  • 17
  • 16
14

TARGA (file name extension .tga) may be the simplest widely supported binary image file format if you don't use compression and don't use any of its extensions. It's even simpler than Windows .bmp files and is supported by ImageMagick and many paint programs. It has been my go-to format when I just need to output some pixels from a throwaway program.

Here's a minimal C program to generate an image to standard output:

#include <stdio.h>
#include <string.h>

enum { width = 550, height = 400 };

int main(void) {
  static unsigned char pixels[width * height * 3];
  static unsigned char tga[18];
  unsigned char *p;
  size_t x, y;

  p = pixels;
  for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
      *p++ = 255 * ((float)y / height);
      *p++ = 255 * ((float)x / width);
      *p++ = 255 * ((float)y / height);
    }
  }
  tga[2] = 2;
  tga[12] = 255 & width;
  tga[13] = 255 & (width >> 8);
  tga[14] = 255 & height;
  tga[15] = 255 & (height >> 8);
  tga[16] = 24;
  tga[17] = 32;
  return !((1 == fwrite(tga, sizeof(tga), 1, stdout)) &&
           (1 == fwrite(pixels, sizeof(pixels), 1, stdout)));
}
Lassi
  • 3,522
  • 3
  • 22
  • 34
  • 2
    When do you initialise the bytes at offsets 0, 1, and 3 to 11 in the tga array? Is it guaranteed to be zeros thanks to the BSS linker section or something? – Johan Boulé Sep 08 '19 at 01:44
  • 2
    Good question. Indeed, global and static variables are guaranteed to be zero-initialized by C. If you got the array from `malloc()` or allocated it on the stack, you'd have to `memset()` it to zero manually (or use `calloc()` which does that by itself). – Lassi Sep 08 '19 at 09:31
11

The recently created farbfeld format is quite minimal, though there is not much software supporting it (at least so far).

Bytes                  │ Description
8                      │ "farbfeld" magic value
4                      │ 32-Bit BE unsigned integer (width)
4                      │ 32-Bit BE unsigned integer (height)
(2+2+2+2)*width*height │ 4*16-Bit BE unsigned integers [RGBA] / pixel, row-major
mwfearnley
  • 3,303
  • 2
  • 34
  • 35
Ian D. Scott
  • 463
  • 3
  • 11
4

Here's a minimal example that writes your image file with a minimal PPM header. Happily, I was able to get it to work with the exact for loop you've provided:

#include <math.h> // compile with gcc young.c -lm
#include <stdio.h>
#include <stdlib.h>

#define width 256

int main(){
    int x, y, i; unsigned char raster_matrix[width*width], h[256][3];
    #define WAVE(x,y) sin(sqrt( (x)*(x)+(y)*(y) ) * 30.0 / width)
    #define hue(i) h[i]

    /* Setup nice hue palette */
    for (i = 0; i <= 85; i++){
        h[i][0] = h[i+85][1] = h[i+170][2] = (i <= 42)? 255:    40+(85-i)*5;
        h[i][1] = h[i+85][2] = h[i+170][0] = (i <= 42)? 40+i*5: 255;
        h[i][2] = h[i+85][0] = h[i+170][1] = 40;
    }

    /* Setup Young's Interference image */
    for (i = y = 0; y < width; y++) for (x = 0; x < width; x++)
        raster_matrix[i++] = 128 + 64*(WAVE(x,y) + WAVE(x,width-y));


    /* Open PPM File */
    FILE *file = fopen("young.ppm", "wb"); if (!file) return -1;

    /* Write PPM Header */
    fprintf(file, "P6 %d %d %d\n", width, width, 255); /* width, height, maxval */

    /* Write Image Data */
    for (i=0; i < width*width; i++)
        fwrite(hue(raster_matrix[i]), 1, 3, file);

    /* Close PPM File */
    fclose(file);


    /* All done */
    return 0;
}

The header code is based on the specs at http://netpbm.sourceforge.net/doc/ppm.html. For this image, the header is just a string of fifteen bytes: "P6 256 256 255\n".

mwfearnley
  • 3,303
  • 2
  • 34
  • 35