The PBM (Portable BitMap) format is a very simple ASCII black and white bitmap format.

Here's an example for the letter 'J' (copy-pasted from the wikipedia link):

# This is an example bitmap of the letter "J"
6 10
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
1 0 0 0 1 0
0 1 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0

It's time we build a small tool to generate files in this nifty little format!

Your goal is to write the shortest program (in any language) that conforms to the following rules:

  1. Your program takes one string from stdin (for example CODEGOLF.STACKEXCHANGE.COM!)
  2. It generates a PBM file with a bitmapped (readable) representation of the string.
  3. Each character is constructed as a 8x8 grid.
  4. You must support the characters [A-Z] (all uppercase), space, a point ('.') and an exclamation mark ('!').
  5. No external libraries allowed (certainly no PBM related ones)!
  6. The used character set must not simply be external to your program. Part of the challenge is storing the characters efficiently...

Testing for validness of the PBM format can be done with the GIMP (or others). Do show off sample input and output!

The shortest solution will be awarded the answer points on 2012-01-31.

Have fun golfing!

PS: I've added a bounty (percentage-wise a huge part of my codegolf reputation) to (hopefully) draw in more competitors.

  • \$\begingroup\$ By "its bitmapped representation" do you mean a character representation which is close to something which looks like letters? How close? Would something like a bitmap representation of a binary code or of braille or morse code be allowed? \$\endgroup\$
    – Howard
    Commented Jan 14, 2012 at 15:53
  • \$\begingroup\$ @Howard: the idea is to generate a pbm image which contains the original input text in 'rendered' (bitmapped) but still human-readable form (with letters in other words). Not unlike the example linked to. \$\endgroup\$ Commented Jan 14, 2012 at 16:24
  • \$\begingroup\$ I added the tag kolmogorov-complexity because the bulk of the program is going to be the 30 bitmaps. \$\endgroup\$ Commented Jan 14, 2012 at 18:01
  • \$\begingroup\$ @Peter Taylor: Good point, thanks! \$\endgroup\$ Commented Jan 14, 2012 at 18:04
  • 2
    \$\begingroup\$ I don't think this is technically kolmogorov complexity since the output isn't constant. \$\endgroup\$
    – 12Me21
    Commented Mar 15, 2018 at 21:50

9 Answers 9


Perl, 164 bytes, no zlib/gzip compression

After sleeping on the problem, I managed to figure out a much shorter solution than my first one. The trick is to take advantage of a minor loophole in the rules: the characters need to fit in 8 by 8 pixels each, but nothing says they have to fill all that space. So I drew my own 4 by 5 pixel font, allowing me to pack two characters into 5 bytes.

The output looks like this:

"HELLO WORLD!" (scaled x 4)


Before giving the actual code with the embedded font data, let me show a de-golfed version:

y/A-Z!./\0-\033/ for @a = <> =~ /./g;
say "P4 " . 8*@a . " 8";
for $p (qw'PACKED FONT DATA') {
    print chr vec $p, ord, 4 for @a;

In the actual code, the PACKED FONT DATA is replaced by a binary string consisting of eight whitespace-delimited rows (four 14-byte rows and one 13-byte one, plus three single null bytes for the blank rows). I deliberately designed my font so that the packed data contains no whitespace, single quotes or backslashes, so that it could be encoded in qw'...'.

Since the packed font string contains unprintable characters, I've provided the actual script as a hex dump. Use xxd -r to turn it back into executable Perl code:

0000000: 792f 412d 5a21 2e2f 002d 1b2f 666f 7240  y/A-Z!./.-./for@
0000010: 613d 3c3e 3d7e 2f2e 2f67 3b73 6179 2250  a=<>=~/./g;say"P
0000020: 3420 222e 382a 4061 2e22 2038 223b 666f  4 ".8*@a." 8";fo
0000030: 7224 7028 7177 2700 20e6 e6ff 9612 8999  r$p(qw'. .......
0000040: e6e6 7759 99f5 0420 9999 8898 128a df99  ..wY... ........
0000050: 9928 5999 1504 20ef 98ee fb12 8cb9 e9e9  .(Y... .........
0000060: 2659 6965 0420 9999 8899 928a 9989 ab21  &Yie. .........!
0000070: 599f 8220 e9e6 8f96 62f9 9986 972e 2699  Y.. ....b.....&.
0000080: f284 2000 2000 2729 7b70 7269 6e74 2063  .. . .'){print c
0000090: 6872 2076 6563 2470 2c6f 7264 2c34 666f  hr vec$p,ord,4fo
00000a0: 7240 617d                                r@a}

Here's how it works:

  • The first line (in the de-golfed version) reads a single line of input, splits it into an array of characters (conveniently omitting any trailing newlines) and maps the letters A to Z and the characters ! and . to the character codes 0 to 28, which normally correspond to unprintable control characters in ASCII / Unicode. (A minor side effect of this is that any tabs in the input get printed as Js.) The space character is left unmapped, since the output loop turns any codes above 28 into blanks anyway.

  • The second line just prints the PBM header. It uses the Perl 5.10 say feature, so you need to run this script with perl -M5.010 for it to work.

  • The output loop takes a whitespace-delimited list of packed image rows and assigns each of them into $p in turn. (I designed the font so that the packed data wouldn't contain any whitespace or ' characters.) It then loops over the input characters in @a, using Perl's vec command to extract the 4-bit nibble corresponding to the mapped character code from the image row, pads it to an 8-bit byte and prints it.

Old answer, 268 bytes:

This is a quick and dirty first attempt. I stole PleaseStand's font and compressed it along with my source code. Since the resulting script is mostly unprintable, here's a hexdump; use xxd -r to turn it into executable Perl code:

0000000: 7573 6520 436f 6d70 7265 7373 275a 6c69  use Compress'Zli
0000010: 623b 6576 616c 2075 6e63 6f6d 7072 6573  b;eval uncompres
0000020: 7320 2778 da85 d03d 4b03 4118 85d1 452c  s 'x...=K.A...E,
0000030: b69c 72cb 7519 4894 552c 2c02 3319 ee5c  ..r.u.H.U,,.3..\
0000040: d7b8 5a89 6093 4634 7e82 c490 6c91 8597  ..Z.`.F4~...l...
0000050: 80fe 7267 d660 23ae e52d 0e0f dcd6 f8c3  ..rg.`#..-......
0000060: e9d1 5e6e ccec a15c ddb5 c5d5 495e 94a3  ..^n...\....I^..
0000070: 83b7 c7f9 73f3 5216 f9a8 787a 5fea 666c  ....s.R...xz_.fl
0000080: 9dd1 b763 dd98 76f8 2df6 0799 5811 7144  ...c..v.-...X.qD
0000090: 4acc ee9d b8b0 c90f 7e4a 8264 6016 cbd7  J.......~J.d`...
00000a0: 79f3 1b91 047c 4055 409e 9e54 1dda ed41  y....|@[email protected]
00000b0: 9a20 8080 6adc 5c47 8488 7495 f621 01d7  . ..j.\G..t..!..
00000c0: 6b6c 902e b6c8 2a6a 6643 f56f e99c 115d  kl....*jfC.o...]
00000d0: 5c7a f1b2 13d0 3453 790f da74 c813 751d  \z....4Sy..t..u.
00000e0: 11ce d821 ad90 247f 2292 5b54 c14f 3c4e  ...!..$.".[T.O<N
00000f0: 49c5 4c53 a1a7 c478 391c 714c f113 0747  I.LS...x9.qL...G
0000100: ab6c 4482 9fd2 177a 5677 6327            .lD....zVwc'

The decompressed Perl code consists of the following preamble:

y;A-Z.! ;;cd,say"P4 ",8*length," 8"for$t=<>

followed by eight repetitions of the following code:

;$_=$t;y(A-Z.! )'BITMAP DATA HERE';print

with BITMAP DATA HERE replaced with 29 bytes encoding one row of the font.

  • \$\begingroup\$ The newer solution is extremely nice. Never imagined this could be done in 165 chars. \$\endgroup\$ Commented Jan 19, 2012 at 9:00
  • \$\begingroup\$ I'm having a lot of trouble reversing the original with xxd -r. Curious if anyone had success / tips for doing so? \$\endgroup\$ Commented Oct 14, 2023 at 23:26
  • \$\begingroup\$ @Beefy_Swain: What's going wrong? Just copy-pasting the hex dump from the answer into a file (e.g. prog.hex) and running xxd -r < prog.hex > prog.pl should work. \$\endgroup\$ Commented Oct 15, 2023 at 16:19

GolfScript, 133 bytes

This is based on my 164-byte Perl solution and uses the same nibble-packed 4 by 5 pixel font. Again, I'll give the readable version first:

{91,65>"!. "+?}%:s"P4"\,8*8'FONT DATA HERE'{16base}%[3]/{:p;{[p=0]0=}s%}%]n*

Here, FONT DATA HERE stands for 71 bytes of binary packed font data. The encoding is slightly different than in the Perl version: instead of splitting the packed string on whitespace, I expand it first and then split it on the nibble 3 (chosen because it just happens not to occur anywhere in the font).

Since the font data in the actual script contains unprintable characters, I give it as a hex dump below. Use xxd -r to turn the hex dump back into executable GolfScript code:

0000000: 7b39 312c 3635 3e22 212e 2022 2b3f 7d25  {91,65>"!. "+?}%
0000010: 3a73 2250 3422 5c2c 382a 3827 36e6 eff6  :s"P4"\,8*8'6...
0000020: 9219 8996 e6e7 7959 95f4 3999 9888 921a  ......yY..9.....
0000030: 8fd9 9998 2959 9514 3fe8 9eeb f21c 89b9  ....)Y..?.......
0000040: e9e6 2959 6564 3999 9889 929a 8999 8ba1  ..)Yed9.........
0000050: 295f 9283 9e6e f869 269f 9968 79e2 6299  )_...n.i&..hy.b.
0000060: 2f48 3327 7b31 3662 6173 657d 255b 335d  /H3'{16base}%[3]
0000070: 2f7b 3a70 3b7b 5b70 3d30 5d30 3d7d 7325  /{:p;{[p=0]0=}s%
0000080: 7d25 5d6e 2a                             }%]n*

Unlike the Perl script, this code prints any characters outside the set AZ, !, ., space as funny-looking little squiggles. Replacing the squiggles with blanks would cost 2 extra chars; removing them entirely would cost 4.

This is my first GolfScript program ever, so I wouldn't be surprised if there's some room left for optimization. Here's how it works:

  • {91,65>"!. "+?}%:s maps the valid input characters (AZ, !, ., space) to the numbers 0 – 28 and assigns the result to s. Any characters outside the valid set get mapped to -1, which is what produces the squiggles when printed.

  • "P4"\,8*8 pushes the values "P4", 8 times the length of the input, and 8 onto the stack. When printed at the end, these will form the PBM header.

  • {16base}%[3]/ takes the preceding string of font data, splits each byte of it into two nibbles, and splits the result into blocks delimited by the value 3. {:p;{[p=0]0=}s%}% then loops over these blocks, first assigning each block to the variable p and then looping over the remapped input string s, replacing each character with the value at the corresponding offset in p. The funny-looking construct [p=0]0= does the same as p=, except that it returns 0 for any offsets past the end of p; I don't really like it, but I haven't been able to figure out any shorter way to handle that.

  • Finally, ]n* takes everything on the stack (the three header values and the image data array) and joins them together with newlines for printing.

  • \$\begingroup\$ Seriously short (by any metric). Nice! \$\endgroup\$ Commented Jan 19, 2012 at 22:58

8086 Machine Code

190 Bytes (122 Bytes using BIOS)

Here's Base64 encoded WinXP/MSDos .COM file:


(Use something like this) to decode the text and save as "pbm.com". Then, at the command prompt, type:

pbm text to encode>outputfilename.pbm

I've tested this on my WinXP machine using both the standard command prompt and DosBox V0.74.


This version is 190 bytes and uses Ilmari Karonen's tiny font (no bios access here!):-

  • \$\begingroup\$ Extremly nice solution. At the moment this is the contender for the bounty which shall be awarded in about 20 hours. Well done! \$\endgroup\$ Commented Jan 23, 2012 at 23:11
  • \$\begingroup\$ Can you post your assembly code for this as well? \$\endgroup\$ Commented Jan 24, 2012 at 16:42
  • 1
    \$\begingroup\$ After looking at the dissassembly and testing the code, it appears you are just using a bitmap font provided by the bios. This can be confirmed by the fact that your program can output lowercase letters, symbols, and punctuation not required by the challenge. Thus, the font is external to your program and not stored by it (at least in my opinion). \$\endgroup\$ Commented Jan 24, 2012 at 17:06
  • \$\begingroup\$ @Skizz: can you confirm this? It still makes for an extremly nice solution but it a little bit against the spec. \$\endgroup\$ Commented Jan 24, 2012 at 17:20
  • 1
    \$\begingroup\$ @ChristopheD: Well, J B did comment "I feel we're going to have long and painful debate on what constitutes an external library." - one could argue that puts in Ruby is an external library. Yes, it does use the bios fonts, accessed through a pointer derefence (there is no load operation to get the fonts into RAM). Bending the rules too far perhaps. I'd have gotten away with it if it hadn't have been for those pesky kids ;-) \$\endgroup\$
    – Skizz
    Commented Jan 24, 2012 at 19:17

Shell script (code+data = 295 chars)

I hope tail, gzip, and dd do not count as "external libraries." Run as echo -n 'YOUR TEXT HERE' | ./text.sh > out.pbm. The font I used is Small Fonts size 7.5, although I did have to clip the descender off the Q.

Example output


Code (137 chars)

i=`od -tu1|cut -c9-`
echo P4
for a in {0..7}
do for b in $i
do tail -2 $0|zcat|dd bs=1 count=1 skip=$((8*b+a))
wc -c 8
cat 8

Complete script

(use xxd -r to recreate original file)

0000000: 693d 606f 6420 2d74 7531 7c63 7574 202d  i=`od -tu1|cut -
0000010: 6339 2d60 0a65 6368 6f20 5034 0a66 6f72  c9-`.echo P4.for
0000020: 2061 2069 6e20 7b30 2e2e 377d 0a64 6f20   a in {0..7}.do 
0000030: 666f 7220 6220 696e 2024 690a 646f 2074  for b in $i.do t
0000040: 6169 6c20 2d32 2024 307c 7a63 6174 7c64  ail -2 $0|zcat|d
0000050: 6420 6273 3d31 2063 6f75 6e74 3d31 2073  d bs=1 count=1 s
0000060: 6b69 703d 2428 2838 2a62 2b61 2929 0a64  kip=$((8*b+a)).d
0000070: 6f6e 650a 646f 6e65 3e38 0a77 6320 2d63  one.done>8.wc -c
0000080: 2038 0a63 6174 2038 0a1f 8b08 0000 0000   8.cat 8........
0000090: 0000 ffed cdb1 0a83 3014 8561 910e 8e8e  ........0..a....
00000a0: 193b dca1 631f 2084 9353 6ba3 a3e0 e2a8  .;..c. ..Sk.....
00000b0: 2fe0 d8e1 22d8 276f 9a50 e813 940e fdb8  /...".'o.P......
00000c0: 70f9 a753 247f 7829 f0b5 b9e2 c718 2322  p..S$.x)......#"
00000d0: 1ba9 e9a8 9688 6895 892a 7007 f0fe 701e  ......h..*p...p.
00000e0: b879 ef48 6e8c aa4f 219c d984 750d 0d91  .y.Hn..O!...u...
00000f0: e9b2 8c63 d779 3fcf c3d0 f76d eb7c e2d2  ...c.y?....m.|..
0000100: 1880 d4d7 4b6e 9296 b065 49ab 75c6 cc92  ....Kn...eI.u...
0000110: 1411 63f6 7de7 3489 9031 847c 3c9a 531d  ..c.}.4..1.|<.S.
0000120: e9a1 aa8f 803e 01                        .....>.


  • od is the standard "octal dump" utility program. The -tu1 option tells it to produce a decimal dump of individual bytes instead (a sufficient workaround for bash's lack of asc(), ord(), .charCodeAt(), etc.)
  • P4 is the magic number for a binary format PBM file, which packs eight pixels into each byte (versus P1 for the ASCII format PBM file). You will see how this proves useful.
  • Per row of final output, the program pulls an eight-pixel byte (corresponding to the ASCII code and line number) from the gzip-compressed data section at the end using dd. (tail -2 $0 extracts the script's last two lines; the compressed data includes one 0x0a linefeed byte.) It so happens that eight pixels is the width of a single character. The null bytes that fill the gaps between supported characters are easily compressible because they are all the same.
  • All this is written to a file named "8". Because there are exactly eight rows (and also eight pixels per byte), the number of bytes is the width of the output in pixels. The output's height is also included in that wc -c prints the input filename "8" after its byte count.
  • Now that the header is complete, the image data is printed. Bash only notices that the last two lines are not valid commands (the last one actually invalid UTF-8) after it has executed everything coming before.
  • I used KZIP only to compress the data section, as Ilmari Karonen did for an entire submission to the 12 Days of Christmas challenge. As described there, it is essentially necessary to use a hex editor to replace the ZIP header format with a gzip header. Including the CRC-32 and file size from the original ZIP header seems to be unnecessary.
  • 2
    \$\begingroup\$ Really nice (and short) solution! In shell scripting the usage of dd, tail and gzip shouldn't be regarded as external imho. \$\endgroup\$ Commented Jan 15, 2012 at 8:16
  • 1
    \$\begingroup\$ Mind adding an explanation of how this works? Would be much appreciated. \$\endgroup\$
    – Mr. Llama
    Commented Jan 17, 2012 at 23:24
  • 2
    \$\begingroup\$ Very nice, thanks a lot for the explanation. However using the 'P4' version is a not really respecting what the OP said "a very simple ASCII black and white bitmap format". \$\endgroup\$
    – eregon
    Commented Jan 21, 2012 at 20:23

Python 2, 248 247 bytes

for i in range(k*24):a=s[i/3%k];j=max(".!".find(a)+1,ord(a)-62)*3;print int("00080084IMVAENBSIFERBSUF4UFQQEMVDT4NAP4MNDSI9MRTMRBARA4NBQRAMNBE4E94NURDARDNRDMLD95DSL7"[j:j+3],32)>>(i/3/k*3+i%3)&1," 0"*(i%3/2*5)

Uses a 3x5 font, packed into a printable string, 3 bytes per character. The font is clearly legible, although the n is lowercase and the v might be mistaken for a u if it isn't seen in context.

Actual Size:
actual size

Zoomed x3:
zoomed x3

The output is a P1 type PBM, as per the example in the challenge. It was a fun challenge.


Ruby 1.9, 346 bytes (122 code + 224 bytes data)

Here is the result:


(It's nice, isn't it?)

z=0..7;puts"P1\n#{(s=gets).size*8} 8",z.map{|i|s.bytes.flat_map{|o|z.map{|j|'DATA'.unpack('Q<*')[o>64?o-63:o/46][i*8+j]}}*' '}

The font was generated by figlet -f banner -w 1000 $LETTERS and this script.

Run with echo -n 'CODEGOLF.STACKEXCHANGE.COM!' | ruby script.rb > image.pbm.

The script generate all the rows and simply print them.

Here is an hexdump (use xxd -r):

0000000: 7a3d 302e 2e37 3b70 7574 7322 5031 5c6e  z=0..7;puts"P1\n
0000010: 237b 2873 3d67 6574 7329 2e73 697a 652a  #{(s=gets).size*
0000020: 387d 2038 222c 7a2e 6d61 707b 7c69 7c73  8} 8",z.map{|i|s
0000030: 2e62 7974 6573 2e66 6c61 745f 6d61 707b  .bytes.flat_map{
0000040: 7c6f 7c7a 2e6d 6170 7b7c 6a7c 271c 1c1c  |o|z.map{|j|'...
0000050: 0800 1c1c 0000 0000 001c 1c1c 0008 1422  ..............."
0000060: 417f 4141 003f 4141 3f41 413f 003e 4101  A.AA.?AA?AA?.>A.
0000070: 0101 413e 003f 4141 4141 413f 007f 0101  ..A>.?AAAAA?....
0000080: 1f01 017f 007f 0101 1f01 0101 003e 4101  .............>A.
0000090: 7941 413e 0041 4141 7f41 4141 001c 0808  yAA>.AAA.AAA....
00000a0: 0808 081c 0040 4040 4041 413e 0042 2212  .....@@@@AA>.B".
00000b0: 0e12 2242 0001 0101 0101 017f 0041 6355  .."B.........AcU
00000c0: 4941 4141 0041 4345 4951 6141 007f 4141  IAAA.ACEIQaA..AA
00000d0: 4141 417f 003f 4141 3f01 0101 003e 4141  AAA..?AA?....>AA
00000e0: 4151 215e 003f 4141 3f11 2141 003e 4101  AQ!^.?AA?.!A.>A.
00000f0: 3e40 413e 007f 0808 0808 0808 0041 4141  >@A>.........AAA
0000100: 4141 413e 0041 4141 4122 1408 0041 4949  AAA>.AAAA"...AII
0000110: 4949 4936 0041 2214 0814 2241 0041 2214  III6.A"..."A.A".
0000120: 0808 0808 007f 2010 0804 027f 0027 2e75  ...... ......'.u
0000130: 6e70 6163 6b28 2751 3c2a 2729 5b6f 3e36  npack('Q<*')[o>6
0000140: 343f 6f2d 3633 3a6f 2f34 365d 5b69 2a38  4?o-63:o/46][i*8
0000150: 2b6a 5d7d 7d2a 2720 277d                 +j]}}*' '}

It takes 93 bytes of code when using goruby:

ps"P1\n#{(s=gt).sz*8} 8",8.mp{|i|s.y.fl{|o|8.mp{|j|'DATA'.ua('Q<*')[o>64?o-63:o/46][i*8+j]}}*' '}

Using ZLib trim the data size to 142 bytes instead of 224, but adds 43 bytes in the code, so 307 bytes:

require'zlib';z=0..7;puts"P1\n#{(s=gets).size*8} 8",z.map{|i|s.bytes.flat_map{|o|z.map{|j|Zlib.inflate("DATA").unpack('Q<*')[o>64?o-63:o/46][i*8+j]}}*' '}

Which gives a total of 268 when using goruby:

rq'zlib';ps"P1\n#{(s=gt).sz*8} 8",8.mp{|i|s.y.fl{|o|8.mp{|j|Zlib.if("DATA").ua('Q<*')[o>64?o-63:o/46][i*8+j]}}*' '}

Java 862 826:

Here is a different approach. I think 'awt' does not count as external lib.

import java.awt.*;
class B extends Frame{String s="ABCDEFGHIJKLMNOPQRSTUVWXYZ .!";int l=29;static Robot r;int[][][]m=new int[l][8][8];
public void paint(Graphics g){for(int y=0;y<8;++y){int py=(y<3)?y:y+1;for(int a=0;a<l;++a)
for(int x=0;x<8;++x)
System.out.println("P1\n"+(getTitle().length()*8)+" 8");
for(int y=0;y<8;++y){for(char c:getTitle().toCharArray()){int a=s.indexOf(c);
for(int x=0;x<8;++x)System.out.print(m[a][x][y]);}
public B(String p){super(p);
Label l=new Label(s);
l.setFont(new Font("Monospaced",Font.PLAIN,13));
public static void main(String a[])throws Exception{r=new Robot();    
new B(a[0]);}}

And ungolfed:

import java.awt.*;

class PBM extends Frame
    int l=29;
    Robot robot;
    int[][][] map = new int[l][8][8];

    static boolean init = false;

    public void paint (Graphics g)
        for (int y = 0; y < 8; ++y)    
            int py = (y < 3) ? y : y +1;    
            for (int a = 0; a < l; ++a)
                for (int x = 0; x < 8; ++x)    
                    map[a][x][y] = (robot.getPixelColor (8*a+x+17+x/4, py+81)).getRGB () < -1 ? 1 : 0;    

        System.out.println("P1\n"+(getTitle().length()*8)+" 8");

        for (int y = 0; y < 8; ++y) {    
            for (char c : getTitle ().toCharArray ()) {    
                int a = s.indexOf (c);    
                for (int x = 0; x < 8; ++x) {    
                    System.out.print (map[a][x][y]);    
            System.out.println ();
        System.exit (0);

    public PBM (String p) throws Exception    
        super (p);    
        robot = new Robot ();    
        setBackground (Color.WHITE);    
        setSize (400, 60);    
        Label l=new Label(s);    
        l.setFont (new Font ("Monospaced", Font.PLAIN, 13));
        setLocation (9,49);
        setVisible (true);
    public static void main (String args[]) throws Exception
        new PBM (args[0]);

Robot is the somehow curious way of Java to call getPixel. I create a Label with the alphabet, and measure where a pixel is for each letter.

In the paint method, int py = (y < 3) ? y : y +1; and (8*a+x+17+x/4, py+81) is the complicated way, to adjust the position in the font. Huuuh! else it would need 9 lines, and every 4th letter, there is an additional pixel horizontally. Trial and error brought me to this solution.

Then the header of the PBM is written, and each line of the message. The message is passed as title of the frame.

That's it. Not the shortest code, but no manual font painting was necessary.

Maybe it could be shorter in BeanShell or Scala.

And now - how does it look like?

java B "JAVA.CAFE BABE" > jcb.pbm

Multiple zooms applied: java.cafe babe PNG

Unzoomed: java.cafe babe JPG

Not that the number of chars is the number of chars from the Perl solution shuffled.

(golfed a little bit more. Made Robot static, which avoids one Exception declaration.)

  • \$\begingroup\$ Original approach, nicely done! \$\endgroup\$ Commented Jan 18, 2012 at 7:59
  • 1
    \$\begingroup\$ +1 for originality, but eww... if you're going to scale up a bitmap, use nearest neighbor interpolation. \$\endgroup\$ Commented Jan 18, 2012 at 10:40
  • \$\begingroup\$ I used eog (Eye of Gnome) and a screenshot. I'll upload an unscaled jpg-version; maybe your browser uses a nearest neighbor interpolation :) . \$\endgroup\$ Commented Jan 18, 2012 at 11:20


I wrote a full featured PPM drawing program in C++, with my own bitmapped font. Even stripping out all non necessary functions it's still huge compared to the answers here because of the definition for the font.

Anyway, here is the output for HELLO WORLD: enter image description here

And the code:


#ifndef PPMDRAW_H
#define PPMDRAW_H

#include <fstream>
#include <sstream>
#include <map>
#include <bitset>
#include <vector>

struct pixel{
    unsigned char r;
    unsigned char g;
    unsigned char b;

    bool equals(pixel p){
        return (r == p.r && g == p.g && b == p.b);

class PPMDraw
        PPMDraw(int w, int h);

        virtual ~PPMDraw();

        void fill(unsigned char r, unsigned char g, unsigned char b);

        void set_color(unsigned char r, unsigned char g, unsigned char b);

        void draw_point(int x, int y);

        void draw_char(int x, int y, char c);
        void draw_string(int x, int y, std::string text);

        bool save(std::string file);


        int width;
        int height;

        pixel * image;

        std::vector<bool> checked;

        unsigned char red;
        unsigned char green;
        unsigned char blue;

        void init_alpha();
        std::map<char, std::bitset<48> > font;


#endif // PPMDRAW_H


#include "PPMDraw.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <cmath>
#include <map>
#include <bitset>
#include <vector>

// standard constructor
PPMDraw::PPMDraw(int w, int h){
    width = w;
    height = h;

    // make an array to hold all the pixels, r, g, b for each
    image = new pixel[width * height];

    // a bitset to use for functions that have to check which pixels have been worked on
    checked = std::vector<bool>();
    for(int i = 0; i < width * height; i++){


    if(image != nullptr){
        delete[] image;

void PPMDraw::fill(unsigned char r, unsigned char g, unsigned char b){
    for(int i = 0; i < width * height; i++){
        image[i + 0] = pixel{r, g, b};

void PPMDraw::set_color(unsigned char r, unsigned char g, unsigned char b){
    red = r;
    green = g;
    blue = b;

void PPMDraw::draw_point(int x, int y){
    if(x >= 0 && x < width && y >= 0 && y < height){
        image[y * width + x] = pixel{red, green, blue};

void PPMDraw::draw_char(int x, int y, char c){
    std::bitset<48> letter = font[c];
    int n = 47;
    for(int i = 0; i < 6; i++){
        for(int j = 0; j < 8; j++){
                draw_point(x + i, y + j);
void PPMDraw::draw_string(int x, int y, std::string text){
        for(unsigned int i = 0; i < text.length(); i++){
            draw_char(x + 6 * i, y, text[i]);


bool PPMDraw::save(std::string file){
    std::ofstream save(file.c_str(), std::ios_base::out | std::ios_base::binary);
        save << "P6" << std::endl;
        save << width << " " << height << std::endl;
        save << "255" << std::endl;
        unsigned char * temp = new unsigned char[height * width * 3];
        for(int i  = 0; i < height * width; i++){
            temp[i * 3 + 0] = image[i].r;
            temp[i * 3 + 1] = image[i].g;
            temp[i * 3 + 2] = image[i].b;
        save.write(reinterpret_cast<const char *> (temp), height*width*3*sizeof(unsigned char));
        delete temp;
        return true;
        return false;


void PPMDraw::init_alpha(){
    // Define a simple font for drawing text
    font[' '] = std::bitset<48>  (std::string("000000000000000000000000000000000000000000000000"));
    font['!'] = std::bitset<48>  (std::string("000000000000000011110110000000000000000000000000"));
    font['"'] = std::bitset<48>  (std::string("000000001100000000000000110000000000000000000000"));
    font['#'] = std::bitset<48>  (std::string("001010001111111000101000111111100010100000000000"));
    font['$'] = std::bitset<48>  (std::string("001001000101010011111110010101000100100000000000"));
    font['%'] = std::bitset<48>  (std::string("000000100100110000010000011000001000010000000000"));
    font['&'] = std::bitset<48>  (std::string("000111001110001010110010110011000000001000000000"));
    font['\\'] = std::bitset<48>  (std::string("100000000110000000010000000011000000001000000000"));
    font['('] = std::bitset<48>  (std::string("000000000000000001111100100000100000000000000000"));
    font[')'] = std::bitset<48>  (std::string("000000001000001001111100000000000000000000000000"));
    font['*'] = std::bitset<48>  (std::string("010010000011000011100000001100000100100000000000"));
    font['+'] = std::bitset<48>  (std::string("000100000001000001111100000100000001000000000000"));
    font[','] = std::bitset<48>  (std::string("000000000000000000000110000000000000000000000000"));
    font['-'] = std::bitset<48>  (std::string("000100000001000000010000000100000001000000000000"));
    font['.'] = std::bitset<48>  (std::string("000000000000000000000100000000000000000000000000"));
    font['/'] = std::bitset<48>  (std::string("000000100000110000010000011000001000000000000000"));
    font['0'] = std::bitset<48>  (std::string("011111001000001010000010100000100111110000000000"));
    font['1'] = std::bitset<48>  (std::string("000000001000001011111110000000100000000000000000"));
    font['2'] = std::bitset<48>  (std::string("010011101001001010010010100100100111001000000000"));
    font['3'] = std::bitset<48>  (std::string("010001001000001010000010100100100110110000000000"));
    font['4'] = std::bitset<48>  (std::string("111100000001000000010000000100001111111000000000"));
    font['5'] = std::bitset<48>  (std::string("111001001001001010010010100100101001110000000000"));
    font['6'] = std::bitset<48>  (std::string("011111001001001010010010100100101000110000000000"));
    font['7'] = std::bitset<48>  (std::string("100000001000000010000110100110001110000000000000"));
    font['8'] = std::bitset<48>  (std::string("011011001001001010010010100100100110110000000000"));
    font['9'] = std::bitset<48>  (std::string("011000001001000010010000100100000111111000000000"));
    font[':'] = std::bitset<48>  (std::string("000000000000000001000100000000000000000000000000"));
    font[';'] = std::bitset<48>  (std::string("000000000000000001000110000000000000000000000000"));
    font['<'] = std::bitset<48>  (std::string("000000000001000000101000010001000000000000000000"));
    font['='] = std::bitset<48>  (std::string("001010000010100000101000001010000000000000000000"));
    font['>'] = std::bitset<48>  (std::string("000000000100010000101000000100000000000000000000"));
    font['?'] = std::bitset<48>  (std::string("010000001000000010001010100100000110000000000000"));
    font['@'] = std::bitset<48>  (std::string("011111001000001010111010101010100111001000000000"));
    font['A'] = std::bitset<48>  (std::string("011111101001000010010000100100000111111000000000"));
    font['B'] = std::bitset<48>  (std::string("111111101001001010010010100100100110110000000000"));
    font['C'] = std::bitset<48>  (std::string("011111001000001010000010100000100100010000000000"));
    font['D'] = std::bitset<48>  (std::string("111111101000001010000010100000100111110000000000"));
    font['E'] = std::bitset<48>  (std::string("111111101001001010010010100100101000001000000000"));
    font['F'] = std::bitset<48>  (std::string("111111101001000010010000100100001000000000000000"));
    font['G'] = std::bitset<48>  (std::string("011111001000001010000010100010100100110000000000"));
    font['H'] = std::bitset<48>  (std::string("111111100001000000010000000100001111111000000000"));
    font['I'] = std::bitset<48>  (std::string("100000101000001011111110100000101000001000000000"));
    font['J'] = std::bitset<48>  (std::string("000011000000001000000010000000101111110000000000"));
    font['K'] = std::bitset<48>  (std::string("111111100001000000010000001010001100011000000000"));
    font['L'] = std::bitset<48>  (std::string("111111100000001000000010000000100000001000000000"));
    font['M'] = std::bitset<48>  (std::string("111111101000000001100000100000001111111000000000"));
    font['N'] = std::bitset<48>  (std::string("111111100100000000100000000100001111111000000000"));
    font['O'] = std::bitset<48>  (std::string("011111001000001010000010100000100111110000000000"));
    font['P'] = std::bitset<48>  (std::string("111111101001000010010000100100001111000000000000"));
    font['Q'] = std::bitset<48>  (std::string("011111001000001010001010100001000111101000000000"));
    font['R'] = std::bitset<48>  (std::string("111111101001000010010000100110001111011000000000"));
    font['S'] = std::bitset<48>  (std::string("011000101001001010010010100100101000110000000000"));
    font['T'] = std::bitset<48>  (std::string("100000001000000011111110100000001000000000000000"));
    font['U'] = std::bitset<48>  (std::string("111111000000001000000010000000101111110000000000"));
    font['V'] = std::bitset<48>  (std::string("111110000000010000000010000001001111100000000000"));
    font['W'] = std::bitset<48>  (std::string("111111100000001000001100000000101111111000000000"));
    font['X'] = std::bitset<48>  (std::string("110001100010100000010000001010001100011000000000"));
    font['Y'] = std::bitset<48>  (std::string("110000000010000000011110001000001100000000000000"));
    font['Z'] = std::bitset<48>  (std::string("100001101000101010010010101000101100001000000000"));
    font['['] = std::bitset<48>  (std::string("000000001111111010000010100000100000000000000000"));
    font['\''] = std::bitset<48>  (std::string("100000000110000000010000000011000000001000000000"));
    font[']'] = std::bitset<48>  (std::string("000000001000001010000010111111100000000000000000"));
    font['^'] = std::bitset<48>  (std::string("001000000100000010000000010000000010000000000000"));
    font['_'] = std::bitset<48>  (std::string("000000100000001000000010000000100000001000000000"));
    font['`'] = std::bitset<48>  (std::string("000000001000000001000000000000000000000000000000"));
    font['a'] = std::bitset<48>  (std::string("000001000010101000101010001010100001111000000000"));
    font['b'] = std::bitset<48>  (std::string("111111100001001000010010000100100000110000000000"));
    font['c'] = std::bitset<48>  (std::string("000111000010001000100010001000100001010000000000"));
    font['d'] = std::bitset<48>  (std::string("000011000001001000010010000100101111111000000000"));
    font['e'] = std::bitset<48>  (std::string("000111000010101000101010001010100001101000000000"));
    font['f'] = std::bitset<48>  (std::string("000100000111111010010000100100000000000000000000"));
    font['g'] = std::bitset<48>  (std::string("001100100100100101001001010010010011111000000000"));
    font['h'] = std::bitset<48>  (std::string("111111100001000000010000000100000000111000000000"));
    font['i'] = std::bitset<48>  (std::string("000000000000000001011110000000000000000000000000"));
    font['j'] = std::bitset<48>  (std::string("000000100000000100000001010111100000000000000000"));
    font['k'] = std::bitset<48>  (std::string("111111100000100000010100001000100000000000000000"));
    font['l'] = std::bitset<48>  (std::string("000000000000000011111110000000000000000000000000"));
    font['m'] = std::bitset<48>  (std::string("000111100001000000001000000100000001111000000000"));
    font['n'] = std::bitset<48>  (std::string("001111100001000000010000000100000001111000000000"));
    font['o'] = std::bitset<48>  (std::string("000111000010001000100010001000100001110000000000"));
    font['p'] = std::bitset<48>  (std::string("001111110010010000100100001001000001100000000000"));
    font['q'] = std::bitset<48>  (std::string("000110000010010000100100001001000011111100000000"));
    font['r'] = std::bitset<48>  (std::string("000000000011111000010000000100000000000000000000"));
    font['s'] = std::bitset<48>  (std::string("000000000001001000101010001010100010010000000000"));
    font['t'] = std::bitset<48>  (std::string("000000000010000011111110001000000000000000000000"));
    font['u'] = std::bitset<48>  (std::string("000111000000001000000010000000100001110000000000"));
    font['v'] = std::bitset<48>  (std::string("000110000000010000000010000001000001100000000000"));
    font['w'] = std::bitset<48>  (std::string("000111100000001000000100000000100001111000000000"));
    font['x'] = std::bitset<48>  (std::string("001000100001010000001000000101000010001000000000"));
    font['y'] = std::bitset<48>  (std::string("001100000000100000000111000010000011000000000000"));
    font['z'] = std::bitset<48>  (std::string("010001100100101001010010011000100000000000000000"));
    font['{'] = std::bitset<48>  (std::string("000000000000000001101100100100100000000000000000"));
    font['|'] = std::bitset<48>  (std::string("000000000000000011111110000000000000000000000000"));
    font['}'] = std::bitset<48>  (std::string("000000000000000010010010011011000000000000000000"));
    font['~'] = std::bitset<48>  (std::string("000100000010000000010000000010000001000000000000"));


#include "PPMDraw.h"
#include <iostream>

int main(){
    // ask for input
    std::string input;
    std::cout << "ENTER YOUR TEXT" << std::endl;
    getline(std::cin, input);
   // get size for image
  int width = input.size() * 6;
   PPMDraw image = PPMDraw(width, 8);
   image.fill(255, 255, 255);
   image.set_color(0, 0, 0);
   image.draw_string(0, 0, input);


CC = g++
CFLAGS = -Wall -c -std=c++11
LFLAGS = -Wall
OBJS = main.o PPMDraw.o

list: $(OBJS)
    $(CC) $(LFLAGS) $(OBJS) -o text2ppm

main.o: PPMDraw.h
    $(CC) $(CFLAGS) main.cpp

PPMDraw.o: PPMDraw.h
    $(CC) $(CFLAGS) PPMDraw.cpp

    rm *.o main

If you're interested, the full PPMDraw library is here:

  • 1
    \$\begingroup\$ I found your fonts very useful! \$\endgroup\$
    – Ludwik
    Commented Nov 16, 2018 at 17:43
  • \$\begingroup\$ Your defined font contains characters that are not needed for this challenge ;-) \$\endgroup\$
    – m13r
    Commented May 10 at 19:36

SmileBASIC, 231 bytes

?8,LEN(C$)*8WHILE""<C$A=ASC(SHIFT(C$))D=ASC("*N.JZ`;O:²ÞøäüÄho"[A-65+12*(A<34)+47*(A<47)])FOR I=0TO 4B$=BIN$(VAL("7535712074617252"[D>>5<<1OR 1AND D>>I]),8)WHILE""<B$?POP(B$),

enter image description here

Each character contains only 2 different row patterns, chosen from a "palette" of 8 combinations. The data for each symbol is stored in 1 byte, with the palette stored separately.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.