Kjetil's Information Center: A Blog About My Projects

Motivational Poster Generator

Here is a short program that will take an input image, combine it with some input text and produce a motivational poster. The size and aspect of the original input image will be kept, and the frames, text and everything else will be scaled according to it. Unfortunately, text is not automatically wrapped, so this must be handled by the caller if using multiple lines.

The program is written in C and uses the GD library to produce the graphics. This library is already available on most Unix platforms by default. There are some hard-coded paths to font files that may need to be adjusted on some platforms. (I tested this on Slackware 12.2.)

Here's an example of an output motivational poster:

An example output image.


And here is the code to make it happen:

#include <gd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define UPPER_LOWER_MARGIN_FACTOR 0.12
#define LEFT_RIGHT_MARGIN_FACTOR 0.18
#define TITLE_FONT_SIZE_DIVISOR 16
#define MESSAGE_FONT_SIZE_DIVISOR 48
#define TEXT_PADDING_DIVISOR 50
#define FRAME_PADDING 3

#define TITLE_FONT_PATH "/usr/share/fonts/TTF/DejaVuSerif.ttf"
#define MESSAGE_FONT_PATH  "/usr/share/fonts/TTF/DejaVuSans.ttf"

#define PNG_MAGIC "\x89PNG"
#define JPEG_MAGIC "\xff\xd8"

int main(int argc, char *argv[])
{
  gdImagePtr poster, image;
  FILE *fh;
  char magic[4];
  int is_jpeg, white, black;
  int title_rect[8], message_rect[8];
  int upper_lower_margin, left_right_margin;
  int title_font_size, message_font_size;
  int poster_height, poster_width;
  int text_padding, text_height;
  int title_pos_x, title_pos_y, message_pos_x, message_pos_y;

  if (argc != 5) {
    fprintf(stderr,
      "Usage: %s <input image> <title> <message> <output image>\n", argv[0]);
    return 1;
  }

  fh = fopen(argv[1], "rb");
  if (fh == NULL) {
    fprintf(stderr, "%s: error: cannot open image file: %s\n",
      argv[0], strerror(errno));
    return 1;
  }

  fread(magic, sizeof(char), 4, fh);
  if (memcmp(magic, PNG_MAGIC, 4) == 0) {
    is_jpeg = 0;
  } else if (memcmp(magic, JPEG_MAGIC, 2) == 0) {
    is_jpeg = 1;
  } else {
    fprintf(stderr, "%s: error: image file must be in PNG or JPEG format\n",
      argv[0]);
    fclose(fh);
    return 1;
  }
    
  rewind(fh);
  if (is_jpeg) {
    image = gdImageCreateFromJpeg(fh);
  } else {
    image = gdImageCreateFromPng(fh);
  }
  fclose(fh);

  if (image == NULL) {
    fprintf(stderr, "%s: error: unable to parse image file\n", argv[0]);
    return 1;
  }

  upper_lower_margin = image->sy * UPPER_LOWER_MARGIN_FACTOR;
  left_right_margin  = image->sx * LEFT_RIGHT_MARGIN_FACTOR;
  title_font_size    = image->sx / TITLE_FONT_SIZE_DIVISOR;
  message_font_size  = image->sx / MESSAGE_FONT_SIZE_DIVISOR;

  gdImageStringFT(NULL, title_rect, white, TITLE_FONT_PATH,
    title_font_size, 0, 0, 0, argv[2]);
  gdImageStringFT(NULL, message_rect, white, MESSAGE_FONT_PATH,
    message_font_size, 0, 0, 0, argv[3]);

  text_padding = image->sy / TEXT_PADDING_DIVISOR;
  text_height = abs(title_rect[7]) + abs(message_rect[7]) + (text_padding * 3);
  poster_width = image->sx + (left_right_margin * 2);
  poster_height = image->sy + (upper_lower_margin * 2) + text_height;

  poster = gdImageCreateTrueColor(poster_width, poster_height);

  black = gdImageColorAllocate(poster, 0, 0, 0); /* First is also BG color. */
  white = gdImageColorAllocate(poster, 255, 255, 255);

  gdImageCopy(poster, image, left_right_margin, upper_lower_margin,
              0, 0, image->sx, image->sy);

  gdImageLine(poster,
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin - (FRAME_PADDING + 1),
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin - (FRAME_PADDING + 1),
              white);

  gdImageLine(poster,
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin - (FRAME_PADDING + 1),
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin + image->sy + FRAME_PADDING,
              white); 

  gdImageLine(poster,
              left_right_margin - (FRAME_PADDING + 1),
              upper_lower_margin + image->sy + FRAME_PADDING,
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin + image->sy + FRAME_PADDING,
              white); 

  gdImageLine(poster,
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin - (FRAME_PADDING + 1),
              left_right_margin + image->sx + FRAME_PADDING,
              upper_lower_margin + image->sy + FRAME_PADDING,
              white); 

  title_pos_x   = (poster_width / 2) - (abs(title_rect[2]) / 2);
  message_pos_x = (poster_width / 2) - (abs(message_rect[2]) / 2);
  title_pos_y = upper_lower_margin + image->sy +
    text_padding + abs(title_rect[7]);
  message_pos_y = title_pos_y + (text_padding * 2) + abs(message_rect[7]);

  gdImageStringFT(poster, NULL, white, TITLE_FONT_PATH,
    title_font_size, 0, title_pos_x, title_pos_y, argv[2]);
  gdImageStringFT(poster, NULL, white, MESSAGE_FONT_PATH,
    message_font_size, 0, message_pos_x, message_pos_y, argv[3]);

  fh = fopen(argv[4], "wbx");

  if (fh == NULL) {
    fprintf(stderr, "%s: error: cannot open output file: %s\n",
      argv[0], strerror(errno));
    gdImageDestroy(image);
    gdImageDestroy(poster);
    return 1;
  }

  if (is_jpeg) {
    gdImageJpeg(poster, fh, -1);
  } else {
    gdImagePng(poster, fh);
  }

  fclose(fh);

  gdImageDestroy(image);
  gdImageDestroy(poster);

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 03/04-2011, Article Link