/* Inverted text in 11 bytes of RAM, with size-optimized code.

To compile and run:

    gcc -std=c99 invertext.c && ./a.out

This program generates an image of text one pixel line at a time,
starting from the bottom, without using a framebuffer.  Indeed, the
only arrays it uses at all are the multiline input character string to
be printed (backwards) and the constant font data itself.  As an
example it uses “##” and ” ” for the pixels, but in the actual
program, it would use some interface for sending pixel data one pixel
or line of pixels at a time.

How much resources does this need?  The font data is 288 bytes.
avr-gcc 4.3.5 invoked as `avr-gcc -g -Os -std=c99 -mmcu=atmega168 -c
-Wall invertext.c` generates a .o file with 288 bytes of .progmem.data and
about 288 bytes of .text, i.e. machine code.

This is small enough to run on an ATTiny9 or ATTiny10, although it
omits fputs/fputc and whatever you’d actually use to replace them in a
real system, plus the avrlibc startup code.  It doesn’t quite fit in
an ATTiny4 or ATTiny5.  (And GCC is generating ATMega code, which uses
instructions the tiny brain-dead AVRs don’t support.)

The call graph is:

    main -> print_pixels -> print_line -> get_pixels
                                       -> prev_line

But it inlines everything into the single print_pixels function, which doesn’t
use any RAM internally, but pushes a few registers on the stack, using a few
more bytes of RAM.  The 6 bytes of `.data` are the two strings passed to fputs,
which are really just a placeholder for a real pixel output function.

 */

#include <stdio.h>
#ifdef __AVR
/* To compile for AVR:
       avr-gcc -Os -std=c99 -mmcu=atmega168 -c -Wall -Wa,-adhlns=invertext.lst \
         invertext.c 
*/
#include <avr/pgmspace.h>
#else
#define PROGMEM
#define pgm_read_byte(x) (*(x))
#endif

/* The font; slightly modified XBM format.  Six rows of 16 fixed-size
 * glyphs. */
enum { 
  font_width = 64,
  font_height = 36,
  chars_per_line = 16,
  lines = 6,
  char_width = font_width / chars_per_line,
  char_height = font_height / lines,
};

static const char font_bits[] PROGMEM = {
  0xDF, 0xAA, 0xED, 0xDD, 0xEB, 0xDA, 0xFF, 0xBF, 0xDF, 0x0A, 0xB8, 0xDA, 
  0xDD, 0xDD, 0xFF, 0xBF, 0xDF, 0xAF, 0xDC, 0xFD, 0xDD, 0x88, 0x8F, 0xDF, 
  0xFF, 0x0F, 0xEB, 0xFA, 0xDD, 0xDD, 0xFF, 0xEF, 0xDF, 0xAF, 0xB8, 0xF5, 
  0xDD, 0xDA, 0xFD, 0xED, 0xFF, 0xFF, 0xFD, 0xFF, 0xEB, 0xFF, 0xFE, 0xFF, 
  0xDD, 0x8D, 0x8A, 0x89, 0xD9, 0xFF, 0xFB, 0xCE, 0xCA, 0xBA, 0xEA, 0xBE, 
  0xAA, 0xDD, 0x8D, 0xBD, 0xD8, 0xDB, 0xC8, 0xDC, 0x9D, 0xFF, 0xFE, 0xDB, 
  0xDA, 0xBD, 0xBB, 0xEA, 0xBA, 0xFF, 0x8D, 0xFD, 0x8D, 0xC8, 0xCB, 0xED, 
  0xCC, 0xDD, 0xFB, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 
  0xD9, 0xDC, 0x8C, 0x98, 0x8A, 0xAB, 0xAE, 0xDB, 0xAA, 0xAA, 0xEA, 0xEE, 
  0xDA, 0xAB, 0x8E, 0xAA, 0x8A, 0xEC, 0xCA, 0xAC, 0xD8, 0xCB, 0x8E, 0xA8, 
  0xAE, 0xAA, 0xEA, 0xAE, 0xDA, 0xAA, 0xAE, 0xAA, 0xA9, 0xDC, 0x8C, 0x9E, 
  0x8A, 0xAD, 0xA8, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
  0xDC, 0x9C, 0xA8, 0xAA, 0xAA, 0x98, 0xCE, 0xFD, 0xAA, 0xEA, 0xAD, 0xAA, 
  0xAA, 0xDB, 0xDE, 0xFA, 0xAC, 0xDC, 0xAD, 0x8A, 0xDD, 0xDD, 0xDD, 0xFF, 
  0xAE, 0xBA, 0xAD, 0x88, 0xDA, 0xDE, 0xDB, 0xFF, 0xDE, 0xCA, 0x8D, 0xAD, 
  0xDA, 0x98, 0xCB, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 
  0xFD, 0xFE, 0xFB, 0xFB, 0xDE, 0xEB, 0xFC, 0xFF, 0x9B, 0x9C, 0xD9, 0x9D, 
  0xFC, 0xAF, 0xAD, 0xDC, 0xAF, 0xEA, 0xAA, 0xA8, 0xCA, 0xCB, 0x8D, 0xAA, 
  0xAF, 0xEA, 0xCA, 0x9D, 0xDA, 0xAB, 0xAD, 0xAA, 0x9F, 0x9C, 0x99, 0xBD, 
  0x8A, 0xAB, 0xA8, 0xDA, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFC, 0xFF, 0xFF, 
  0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xBF, 0xED, 0xF5, 0x9C, 0x9A, 0xA8, 0xAA, 
  0xAA, 0xD8, 0xDD, 0xFA, 0xAA, 0xCC, 0xAD, 0xAA, 0xAD, 0xCB, 0x9D, 0xFF, 
  0xAC, 0xBE, 0xAD, 0x8A, 0x9D, 0xDD, 0xDD, 0xFF, 0x9E, 0xCE, 0x9B, 0xAD, 
  0xBA, 0xD8, 0xDD, 0xFF, 0xBE, 0xFF, 0xFF, 0xFF, 0xCF, 0xBF, 0xEF, 0xFF, 
};

/* An integral type of at least char_width+1 bits. */
typedef char pixel_slice;

/* e.g. ones(5) is 11111 in binary. */
static inline int ones(int bitshift)
{
  return (1 << bitshift) - 1;
}

/* This returns a row of char_width pixels from the specified character.
 */
static pixel_slice get_pixels(char c, int pixel_row)
{
  unsigned char glyph = c - 32;
  if (glyph > chars_per_line * lines) glyph = 0; /* space character */
  unsigned char line = glyph / chars_per_line;
  unsigned char offset = glyph % chars_per_line;
  int pix_offset = (line * char_height + pixel_row) * font_width + 
    offset * char_width;

  /* XXX note that this assumes the pixels are all in the same byte!
   * This effectively limits us to 4- or 8-pixel-wide fonts. */
  return (pgm_read_byte(&font_bits[pix_offset >> 3]) 
          >> (pix_offset & ones(3)))
    & ones(char_width);
}

/* prev_line: Given a pointer t to the \0 at the end of a string s or
   to the first character after a \n in it, return a pointer to the
   first character after the previous \n or, if none, to the first
   character of the string. */
static char *prev_line(char *s, char *t)
{
  if (t == s) return t;         /* The string may be empty! */
  do {
    t--;
  } while (t != s && t[-1] != '\n');
  return t;
}

enum { output_width = 40 };

/* Takes a pointer to the byte past the \n or \0 terminating the line. 
 * Returns a pointer to the byte beginning the line.
 */
static char *print_line(char *string, char *end)
{
  char *line = prev_line(string, end);
  unsigned char width = end - line - 1; /* Assuming line lengths < 256! */
  unsigned char padding = output_width / char_width - width;

  char pixel_row = char_height;
  do {
    pixel_row--;
    char *s = end - 1;
    unsigned char padding_left = padding;

    for (unsigned char i = 0; i < output_width / char_width; i++) {
      if (padding_left) {
        padding_left--; 
      } else {
        s--;
      }

      pixel_slice p = get_pixels(*s, pixel_row);
      for (char j = char_width; j; j--) {
        p <<= 1;
        fputs((1 << char_width) & p ? "  " : "##", stdout);
      }
    }

    fputs("\n", stdout);
  } while (pixel_row);
  return line;
}

void print_pixels(char *string)
{
  char *line = string;
  while (*line) line++;
  line++;                       /* Get a pointer one past the \0. */
  while (line != string) line = print_line(string, line);
}

#ifndef __AVR
int main()
{
  print_pixels("Hello,\nWeirdos");
}
#endif
-- 
To unsubscribe: http://lists.canonical.org/mailman/listinfo/kragen-hacks

Reply via email to