Embedding Bitmaps

There are many techniques discussed on various threads on this forum to embed bitmaps. In my entries, I used 1-bit or 2-bit images and I packed those bits into long Unicode Strings. I never actually compared that technique to any of the others; so, I decided to run an experiment. Here’s the tile map used in the first world of Super Mario Land, one of the Game Boy launch titles from 1989 and also the basis of one of my 4K entries:

The png file is 6025 bytes. It’s composed of 8x8 pixels, 2-bit (4 colors) tiles. The colors are black, dark gray, light gray and white; however, one of those colors (usually white) is considered transparent for the sprites.

The experiment consists of programs that display the image. To begin, I created the following template:

import java.applet.Applet;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class a extends Applet implements Runnable {

  @Override
  public void start() {
    new Thread(this).start();
  }

  public void run() {

    BufferedImage image = new BufferedImage(128, 192, 1);

    Graphics2D g = null;

    while(true) {

      if (g == null) {
        g = (Graphics2D)getGraphics();
      } else {
        g.drawImage(image, 0, 0, 256, 384, null);
      }

      Thread.yield();
    }
  }
}

It creates an empty, all-black image of the same dimensions as the png and it displays the image scaled by a factor of 2. Compile 'n Shrink generates a 318 byte file from the template. I’ll use that size as an estimate of the overhead.

Next, here’s a program that generates a long Unicode String out of the image:

import java.awt.image.*;
import javax.imageio.*;
import java.io.*;

public class Converter1 {

  public static final String sourcePath = "...";

  public static void main(String... args) throws Throwable {

    StringBuffer sb = new StringBuffer();

    BufferedImage image = ImageIO.read(new File(sourcePath + "0.png"));

    for(int y = 0; y < image.getHeight(); y++) {
      for(int x = 0; x < image.getWidth(); x += 8) {
        int value = 0;
        for(int i = 0; i < 8; i++) {
          value |= getColor(0xFF & image.getRGB(x + i, y)) << 16;
          value >>= 2;
        }
        printValue(sb, value);
      }
    }

    System.out.print(sb.toString());
  }

  private static int getColor(int pixel) {
    switch(pixel) {
      case 0x00:
        return 0;
      case 0x60:
        return 1;
      case 0xA8:
        return 2;
      default:
        return 3;
    }
  }

  private static void printValue(StringBuffer sb, int value) {
    switch(value) {
      case 0x0008:
        sb.append("\\b");
        break;
      case 0x0009:
        sb.append("\\t");
        break;
      case 0x000a:
        sb.append("\\n");
        break;
      case 0x000c:
        sb.append("\\f");
        break;
      case 0x000d:
        sb.append("\\r");
        break;
      case 0x0022:
        sb.append("\\\"");
        break;
      case 0x0027:
        sb.append("\\'");
        break;
      case 0x005c:
        sb.append("\\\\");
        break;
      default: {
        String s = Integer.toHexString(value);
        while(s.length() < 4) {
          s = "0" + s;
        }
        sb.append("\\u").append(s);
        break;
      }
    }
  }
}

It processes the image one row at a time from left to right. It converts an 8 pixel segment of a row into a 16-bit Unicode character substituting each pixel with a 2-bit value. The getColor method returns 0, 1, 2 or 3 based on the color of the associated pixel. The printValue method typically generates values of the form \uXXXX, where the X’s are hexadecimal digits; however, 8 values need to be escaped before packing it into a String.

Here’s the modified template that reverses the encoding process and display the image:

import java.applet.Applet;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class a extends Applet implements Runnable {

  @Override
  public void start() {
    new Thread(this).start();
  }

  public void run() {

    final String S = "\uffff\uffff\uffff\uffff\uffff\uffff\uffff\u0fff\uffff\uffff\uffff\uffff\u03ff\uffc0\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\u500f\uffff\uffff\uffff\uffff\u00ff\uff00\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\ua553\uffff\uffff\uffff\uffff\u00ff\uff20\uffff\uffff\uffff\uffff\u3fff\ufff0\u0fff\ufffc\ucfff\ufa94\uffff\ueaff\u0fff\ufffc\u003f\uffea\uffff\u0fff\u0fff\ufffc\u0fff\ufc00\u03ff\uff00\u28ff\uffa4\u0fff\ueafc\u003f\uffc0\u803f\uffa2\uffff\u03bf\u03ff\uff00\u03ff\uff8a\u80ff\uffe2\ubaff\uffe4\u03ff\uf000\ua2ff\uff00\u800f\uffea\uf03f\u88af\u80ff\uffe2\u20ff\ufea8\u083f\uffaa\ufe3f\uff94\u80ff\uf0e2\u8abf\ufe88\u800f\uffea\uc00f\ua02f\u083f\uffaa\ua0ff\ufe0a\ua83f\uff82\uf8ff\uff93\u083f\uf0aa\ua0ff\ufe8a\u000f\ufff2\u0003\u083f\ua83f\uff82\uafff\uffaa\uabff\uffea\ua83f\uff82\ua83f\ufc02\ua83f\uffd5\u500f\uffd9\u0000\ua8ff\uabff\uffea\u00ff\ufff1\u40ff\ufffc\uabff\uffea\uabff\uff2a\u003f\uff5a\ua40f\uffd5\u0003\u90ff\u10ff\ufff4\u00af\ufa81\u403f\ufff0\u00ff\ufeb1\u103f\uffc4\u003f\uff56\u6f3f\ufff5\uc00f\u15cf\u103f\uff04\u03af\ufa07\ue83f\uffdd\u00bf\ufea0\u500b\uf3d0\u40ff\uff40\ua7ff\uffd5\uf03f\u740f\u72bf\ufe0d\u55ff\ufcf5\u597f\uffd5\u55af\uffd5\ud5eb\uf075\u1fff\uffd0\u95ff\uffd6\uffff\u540f\u56bf\ufe95\u553f\ufc15\u55ff\ufff0\u553f\ufff5\u553b\uf055\u1fff\ufcc4\u957f\uff56\uffff\u57ff\uc3ff\uffc3\uf43f\ufc17\u03ff\uffc0\u350f\ufffc\u540f\ufff5\uffff\ufc01\u555f\uff55\uffff\uffff\uc0ff\uff03\uc0ff\uffff\u03ff\ufffc\u3fcf\ufff0\ud7cf\uffff\uffff\uff03\u5557\uff55\uffff\uffff\u03ff\ufff0\uffff\uffff\u03ff\ufff0\uffff\u3fa4\uffff\ueaff\u03ff\ufffc\uffff\uffff\uffff\uffff\u00ff\uff00\u0fff\uffc0\u00ff\uff00\uffff\u1e93\u03ff\ue8b0\u003f\ufe00\uffff\u03ff\u0fff\uffc0\ua0ff\uffe8\u03ff\ufc00\ua0ff\uffe8\uffff\uc54f\u00ff\uea00\u8aff\ufa82\uffff\ua8fc\u03ff\uff00\u823f\ufeaa\u83ff\uffa2\u823f\ufeaa\uffff\uf03f\ua0ff\uc028\uaabf\ufa58\uffff\ufe30\u00ff\uff20\uaa0f\ufea2\u08ff\ufaaa\uaa0f\ufea2\uffff\uffff\u823f\uc2aa\u82af\uffda\uffff\ufc03\u003f\uffea\uaa0f\uff00\ua83f\ufa8a\uaa0f\uff00\uf03f\uffff\uaa0f\uf2a2\ua0af\uff50\uffff\uc0c1\u803f\uffa2\ua8ff\uffea\ua83f\ufc02\ua8ff\uffea\uc54f\uffff\uaa0f\uf000\u2aff\ufd4a\uffff\u0e80\u800f\uffea\u04ff\ufff4\ua3ff\uffaa\u003f\ufff1\u1e53\uffff\ua0ff\ufc2a\u103f\ufd6a\uffff\uaa82\u800f\uffea\u043f\uffc4\u04ff\ufff5\u013f\ufff8\u00ff\ufa91\u400f\uff04\u003f\uff5a\u288c\u5555\u0003\ufff2\u040f\uff04\u053f\ufeb0\u017f\uffe8\u003f\ufa91\u420f\uff44\u403f\uffc1\u0448\uffff\u5c03\uffd9\u5c0f\ufc0d\u057f\ufa80\u557f\uffe9\u56af\ufcd7\u5a8f\ucf57\u54ff\ufc01\uf004\u00ff\u9fff\uffd5\u56af\ufea5\u550f\ufa80\u55ff\uffd5\u57af\ufc05\u52bf\uc155\u57ff\ufca0\u0000\u5400\u9fff\uffa6\u56bf\uffa5\u550f\uffd4\u15ff\uffd4\u543f\ufc05\u4143\uc155\u5fff\ucfa8\u08f0\u4000\u57ff\uffaa\ud57f\uff55\uf50f\uffd5\u13ff\ufff0\u543f\ufc0d\u0543\uc154\uffff\uc059\uaa3f\u0002\u55ff\uffd5\uf03f\uff03\uffcf\uff03\uc0ff\ufff3\ufc3f\uffff\ud543\uffff\uffff\uf007\u2a3f\u0ffc\u557f\uffd5\uf00f\ufc03\uffff\ufc03\u03ff\uffff\uf0ff\uffff\ufff3\uffff\uffff\ufc0f\uc03f\uffff\u555f\uff55\uffff\uffff\u823f\ufeaa\uffff\uffff\u04ff\uffc0\u03ff\ufff0\u9c03\ufbda\uffff\u3a25\u03ff\uc03f\uffff\uffff\uaa3f\ufea2\u03ff\ufff0\u15ff\ufff4\u00ff\uffc0\u570f\ufa95\uffc0\u1a23\u00ff\u3f3f\uffff\uffff\ua83f\uff00\u00ff\uff00\u557f\ufff5\u003f\uff02\u6bff\uffa5\uff28\u1628\u00ff\uea3f\uffff\uffff\ua90f\ufc2a\ua0ff\uffe8\u557f\uffd1\ua03f\uff2a\ua9ff\ufffa\ufcfc\u0515\ua03f\u0a3f\uffff\uffff\u0e0f\ufc8c\u823f\ufeaa\u5543\uffd4\u200f\uff22\u557f\ufff5\uf3fc\uc015\u203f\uf23f\u03ff\ufff0\u568f\ufe95\uaa0f\ufea2\ud543\uffd5\ua00f\uff2a\u555f\uffd5\uf0fc\uf000\ua00f\u50f0\u00ff\uff00\u503f\ufc05\uaa0f\uea00\uf5c3\uff03\ua003\uffe8\u5557\uffd5\ucc00\uff00\ua00f\b\ua0ff\uffe8\uf00f\uf00f\u28ff\ufa00\ufff3\ufc03\u0003\ufff2\u5555\uffd5\u3a2a\uffff\u000f\ua8cc\u900f\uffda\uc000\uc0c0\uc000\uc300\uf000\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\u540f\uff55\ucaa8\uc8c8\ucaa8\uc828\uc2a8\ucfcf\uffcf\ucf3f\ucfcf\ucfcc\ucf03\ucfc3\uc0c3\uc00c\u683f\uffa5\uc080\uc808\uc008\uc828\uca08\u3333\uff33\u330f\u3333\u33cc\u33f3\u333c\uc0c0\u3c0c\ua7ff\uffe9\ufc8f\ucaa8\uf2a8\uc888\uc8c8\u3333\uff33\u333f\u333f\u33cc\u33c3\u33c3\uc0c3\u3c0c\u95ff\uffda\ufc8f\uc808\uf008\uca88\uc8c8\u3333\uff33\u333f\u33cf\u3300\u333f\u330c\uc0c3\uc00c\u557f\uff55\ufc8f\uc8c8\uc008\uca08\uca08\u3333\uff33\u333f\u33f3\u33cf\u333f\u333c\u00c3\ufc0c\u555f\uff55\ufc8f\uc8c8\ucaa8\uc8c8\uc2a8\ucfcf\uffcf\ucf3f\ucf03\ucfcf\ucfc3\ucfc3\u03c3\ufc0f\u5557\uff55\ufc0f\uc0c0\uc000\uc0c0\uf000\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\u0323\ufe0a\ufe0a\uffff\uffff\u0000\uf000\uffff\uf000\uffff\uffff\uf00f\uffff\uffff\uffff\uffff\uac03\uffaa\u3faa\u03ff\uffc0\u00fc\u2300\uffc0\u2300\uf00f\uffff\uc3c3\u0fff\ufc3f\uffff\uffff\u00f3\ufc00\u3c00\u00ff\uff00\ufc00\u223f\uff00\u223f\uc0c3\ufffc\uc033\uf0ff\uf28f\uffff\uffff\uf003\uf23f\uf23f\u3cf0\uffa2\uffc0\uf000\uffa2\uf000\uc033\ufff3\uc033\uff3f\uc623\u0fff\uffc0\ufc0f\u2003\u2003\u1ccc\ufaaa\u0043\u0c54\u0aaa\ufc54\uc033\uf3cf\uc003\uffcf\uf0af\u03ff\uff00\u003f\uf050\uf050\u00c8\ufa8a\u500f\u0c15\uaa8a\ufc15\uc003\ucc3f\uf00f\uffcf\ufc7f\uf3c3\uff88\u03ff\u3c15\ufc15\u03c8\ufc02\u00ff\uaf00\u0c02\uff00\uf00f\u3fff\uffff\ufff3\uffff\u7333\ufea8\u3fff\u3f00\uff00\uaf28\uffaa\u0fff\u0ff0\u0faa\ufff0\uffff\uebff\uffff\uffff\u80ff\u382a\u80ff\u00ff\uf000\u000f\u3c00\u000f\uffff\uffff\u3fff\ufffc\uafbf\ufff3\uffff\uffff\ubcff\u3eaa\ubcff\u0f3f\ucfc0\ua230\uc0cc\ua23f\uffff\uffff\ucfff\uffff\ubebe\uffce\uffff\uffff\u00ff\u3c00\u00ff\u03cf\u3c00\uf330\u0002\uf33f\uc03f\uffff\uf3ff\ubffa\ubffa\uffca\u0fff\uf000\ua3c3\uc332\ua3ff\u83cf\u3fa2\u5130\u3a01\u513f\u0f03\uffff\ua8ff\uabaa\uafea\ufff2\uf3ff\ucfc0\uf3c3\u000b\uf3ff\u08cf\u3aaa\u510f\u3500\u510f\u0000\uffff\ua3ff\uaaaa\uaaaa\ufff2\u3cff\u3000\u533f\u3801\u533f\ua80f\u3a8a\u003f\uc000\u0030\u0003\uffff\u8fff\uaaa2\uaaa8\ufffc\u0cff\u3e28\u0fff\u3400\u0fc3\ua80f\u3c02\u00ff\uc000\u00f0\uc03f\ufffc\u3fff\u0a80\u2aa3\uffff\u80ff\u3aa0\uffff\uc000\uffc3\ua3cf\u3faa\u0fff\uf000\u0ff0\uffff\ufff3\uffff\uc00f\uc00f\uffff\uc003\uc003\uc003\uf00f\uffff\uffff\uffff\u0ff0\uf00f\u00ff\f\ufffc\ufff8\uffff\u0000\u0000\u0aa8\u3c3c\u0aa8\ucaa3\uc0c3\uffbf\uff3f\u300f\ud5e7\uaa3f\uaaa2\ufc02\ufff8\uffff\ufffc\u0fff\u0808\u33cc\u0808\u2aa8\u0a2c\uffbf\uff3f\u0ff0\u0000\ufe0f\ufffb\uf2af\uffe8\uffff\uaaac\u0aaa\u0a88\u30fc\u0a88\u2aa8\u0aac\ueaaa\uc000\u0000\u2008\uffa3\uffff\uf2ff\uffa3\uffff\ua8ec\u08ea\u0a88\u3c3c\u0a88\u0000\u0aa8\ufaab\uf003\u0000\u0000\uffe3\uffff\ucaff\uea8f\uffff\ua82c\u082a\u02a8\u3ffc\u02a8\ucff3\uc2a3\ufeaf\ufc0f\u02c0\u0000\ufff8\u3ff3\ucbff\ua54f\uaaaf\uaaac\u0aaa\u0000\u3c3c\u0000\ucff3\uf08f\ufaab\uf003\u0000\u2008\ufff8\u3ff3\u1bff\u903f\u5016\u0000\u0000\uc003\uc003\uc003\uf00f\ufc3f\uebfa\uc3f0\u0c30\u0000\ufff8\uffff\u1bff\u0fff\u0fc0\u0000\u0000\uf00f\uffff\ufc3f\u2aa8\u3ffc\u2008\uffff\uff30\uffff\uff3c\uffff\uffff\u1bff\uffff\uc3ff\uffff\uc003\uffff\uf28f\u0aa0\u3ffc\u2828\uffff\uff3c\uffff\ufec0\uffff\uffff\u1aff\uffff\ufcff\uffff\u0c30\uffff\uca23\uc283\u0ff0\ucaa3\uffff\ufec0\uffff\uc08f\uf00f\ufaaf\u16ff\ucfff\ua3cf\uffff\u0c30\uf00f\ucaa3\uf00f\u0ff0\uf00f\uffff\uc083\uffff\u308f\uc0c3\ueaeb\uc6ff\uffff\uf8cf\u0000\u0000\uc003\u2aa0\u3c3c\u23c8\u3c3c\uffff\u308f\uffc3\u0083\uc033\ueabb\uc5af\u3f3f\u2e3f\u0000\ufaaf\u0c30\u22a8\u0c30\u23c8\u0c30\uffc3\u00bf\uff3c\u82bf\uc033\ueabb\uf16a\ub3ff\ufbbc\uffff\uca83\ufaaf\u2aa8\uc003\u23c8\uc003\uff3c\u82bf\uff30\uea3f\uc003\ueaab\ufc55\u2fff\u3cb3\uffff\uc30f\u0ff0\u0a88\uf00f\u23c8\uf00f\uff30\u280f\uff30\u030f\uf00f\ufaaf\uff00\uc8cf\u0f8f\uffff\uffff\uffff\uffff\uffff\u000f\ufffc\u003f\ufff0\u3fff\ufffc\u3fff\u2028\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uba43\ufff1\ue90f\uffc6\u83ff\uffc1\u0fff\u2303\ufc0f\uffff\uffff\uffff\uff0f\uf0ff\uffff\uffff\u0003\uffc0\u000f\uff00\ua8ff\uff16\u03ff\u23cf\ufc8f\uffff\uffff\uffff\ufcf3\ucf3f\uffff\uffff\u48fc\uff2e\u23f3\ufcb9\ua80f\uf01a\u00ff\u23ff\ufc8f\uffff\uffff\uffff\uf3f3\ucfcf\uffff\uffff\u08cc\uff00\u2333\ufc00\ua8f3\ucf1a\u00ff\u080f\ufc8f\uffff\uffff\uffff\u33a3\ucacc\u3fc3\uc3fc\u4800\uff2e\u2003\ufcb9\u80fc\u3f02\uc00f\uca8f\ufc8f\uffff\uffff\uffff\u8383\uc2c2\u833c\u3cc2\u0aa8\uff00\u230f\ufc00\ua8ac\u3a1a\uffc0\u003c\ufc8f\uffff\uffff\uffff\u3c0f\uf03c\u3cfc\u3f3c\u42a8\ufff1\u23ff\uffc5\u0300\u00c0\uffff\ucc00\ufc8f\uffff\uffff\uffff\u8cff\uff32\u8ce8\u2b32\ua003\ufc01\u23ff\ufc06\u000f\ufc0f\uf03f\u0f03\ufc8f\uffff\uffff\uffff\u00f0\u0f00\u0003\uc000\ua83f\uf056\u200f\uf15a\u5ac3\uc060\uc00f\uc3ff\ufc8f\uffff\uffff\uffff\u3cc0\u0333\uccff\uff3c\uaacf\uc5aa\u8a8f\uc6aa\u0003\uc5a0\u00cf\ubc3f\ufc8f\uffff\uffff\uffff\u3c0f\uf033\ucc0f\uf03c\u0ab3\u06a8\u203f\u06a0\ucaa8\u1a86\u03cf\uabcf\uc000\uffff\uffff\uffff\u03ff\uffc0\u0303\uc0c0\ub2b3\u1aaa\ucacf\u1aaa\u0808\u1a80\u03c3\u6aa3\ucffc\uffff\uffff\uffff\u8fff\ufff2\u8fff\ufff2\u00b0\u1aa8\u02b3\u1aa8\ucaa8\u1a86\u3ff0\u12a3\uf3f3\uffff\uffff\uffff\u800f\uf002\u00ff\uff00\uaca8\u06aa\ub2b3\u06aa\u0aa8\u1a80\ufffc\uc223\ufccf\uffff\uffff\uffff\u3f03\uc0fc\uf03f\ufc0f\u0003\uc000\u000f\uc000\u0003\uc000\uffff\ufc03\uff3f\uffff\uffff\uffff\uffff\uf03f\uffff\uffff\uffff\uffff\u0000\uffff\u000f\uffff\u2aa8\u0000\uffff\u0003\ufc00\uffff\uffff\uc3cf\uc00f\uffff\uc00f\uffff\u1bf9\uffff\u2aa3\ufffc\u2003\u1bf9\uffff\uea53\uf16f\uffff\uc00f\ucff3\u23f3\uffff\u0003\ufffc\u0000\uffc0\u6aa8\ufffc\u2aa3\u0000\uffff\u0003\uf000\uffff\u23f3\ucbf3\u63c8\ufffc\u0280\uffc0\u1af9\ufc0f\u6a08\ufffc\u2a8f\u1af9\ufff0\u200f\uc6bf\uffff\u63c8\ucabc\u63c8\uc00c\u2aa0\ufc00\u8000\uf0ea\u5a08\uc000\u003c\uc000\uff0f\u2333\uc000\uffff\u63c8\uc2ac\u6828\u3ff0\u0280\uffc0\u0552\uf3a8\u16a8\u35d7\ucc00\ua542\u3cea\ua333\u1aca\uffff\u6828\uf00c\u1a80\u3fff\u0003\ufff0\uf290\u01ab\u02a8\u35d7\u0f03\u2a90\u0380\ua800\b\uffff\u1aa0\uffc3\u023f\u0ea0\uc00f\uffff\uaca4\uf1aa\u3c03\uc000\ucfff\ucaac\u053f\uaaa8\u1bca\uffff\u02a8\uf000\u0003\uf00f\uffff\uffff\uacac\uf1aa\uffff\uffff\u0fff\ub2ac\uc4aa\u000f\uf000\uc820\u0c03\uc1df\u0fff\ucdd7\uf003\uffff\ua2ab\uc1a2\uffff\uffff\u3fff\ub2ab\uc6aa\u0003\uc000\u28a8\u0fff\ucdd7\u33ff\ucdf4\u06a0\uffc0\u061a\uc6a0\uffff\uffff\ucfff\ua21a\uc6aa\ua8a0\u028a\u20a0\u33cf\ucdf4\ufccf\uf000\u5bfc\uc005\uc146\uc6a3\uffff\uffff\ub00f\u4946\uc6aa\u2aa8\u0aaa\u0323\ufcc3\uf000\ufcc3\ucff3\u6ffc\uff05\ufc01\uc128\uffff\uffff\uafe3\u0041\ucaa9\uaa28\u0a2a\ucf23\u003c\u0000\u003c\u0000\u16a3\ufff0\u3ffc\uf0a2\uffff\uffff\u6aa8\uaa0c\u1aaa\ua2a0\u02a2\ucfc3\ufcc3\uf3fc\ufcc3\ucff3\uc00f\uffff\u3fff\uff0a\uffff\uffff\u1888\u223f\u16aa\u0000\u0000\uffcf\uffcf\uffff\uffcf\uffff\uffff\uffff\u3fff\uffc0\uffff\uffff\uc000\u003f\uc000\u0000\u0000\uffcf\uc00f\uf00f\uf00f\uffff\uffff\ud55f\uc003\u5555\uaaaa\ufc3f\uc003\u0000\u0000\uff3f\uc003\uc003\u0fc3\uc6e3\uc553\uf00f\ufc0f\u7037\u1be4\u5555\uaaaa\ufc3f\u1554\u3f3f\u0aa0\uff3f\u003c\u0aa8\u0003\uc553\u1694\uc3c3\u0003\u7ff7\u0000\u5555\uaaaa\ufc0f\u1554\u3f3f\u0aa0\ufccf\u0000\u0aa8\uc00f\uf00f\u1be4\ucc33\ue280\ud55f\u1be4\u5555\uaaaa\ufc00\u1554\u3f3f\uc003\ufccf\ucae3\u02a8\ufcff\ufc3f\u1be4\ucc33\uaa08\ufdff\u0000\u5555\uaaaa\uff00\u1554\u3f3f\u0000\uf3f3\ucae3\uc003\u0cc3\u0000\u1694\uc3c3\u82a8\u5dd7\u1be4\u5555\uaaaa\uffff\u1554\u2a2a\u0aa0\uf2a3\u0000\uffff\uc00f\u16b8\uc553\uf00f\ueaab\ud55f\uc693\u5555\uaaaa\uffff\u1554\u1515\u0aa0\uf2a3\u003c\uffff\uf03f\u16b8\uf00f\uffff\uffff\uf57f\uf00f\u5555\uaaaa\uffff\uc003\u0000\uc003\uf003\uc003\uffff\u00ff\uff00\u6acf\uf154\ufc0f\uffff\uf00f\ufc3f\ufc3f\uffff\uffff\uffff\uffff\ua7da\uffff\ufc3f\uacff\uff06\u5bcf\uf100\uf2a3\uffff\uc3c3\uf2af\ufc3f\uffff\uffff\uffff\uffff\udff7\uffff\ufc3f\uacff\uff06\u3ccf\uf022\ucae8\uffff\uc0f3\uf2af\ufc3f\uc00f\ufffc\uf0fc\uffff\u7ffd\uffff\uf03f\uacff\uff06\u82cf\uf221\uc8f8\u00ff\uc0f3\uf2af\ufc3f\u7cf3\ufc3f\ucf3f\uff00\uffff\uffff\u003f\uac3f\ufc16\u6b0f\uf218\uc8f8\u003f\uc0f3\uf2af\ufc3f\u40cc\uf00c\u3cfc\ufc00\uffff\uffff\u00ff\uab0f\uf01a\u1f3f\uf1c7\uca28\uf03f\uc0f3\uf2af\ufc3f\u4000\ufc3c\ucf3c\ufc0f\ufebf\uffff\uffff\uaacf\uf05a\uc0ff\ufc30\uf2a3\ufc3f\uc3c3\uf2af\ufc3f\u4003\ufffc\uf0fc\ufc3f\ufaaf\uffff\uffff\u02cf\uf16a\uffff\uffff\ufc0f\ufc3f\uf00f\ufc3f\ufc3f\uc00f\ufffc\ufffc\ufc3f\ue96b\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uf00f\ufc3f\uf00f\uf003\uf00f\uf003\uf00f\uc003\uf00f\uf00f\uf00f\uf003\uf00f\uf003\uc003\uc003\uc3c3\ufc0f\uc0f3\uc0ff\uf0c3\uffc3\uffc3\uc3ff\uc0f3\uc0f3\uc0f3\uc3c3\uc3c3\uc0f3\uffc3\uffc3\uc3c3\ufc3f\uc0ff\uf00f\uf0f3\uf003\uf003\uf0ff\uf00f\uc0f3\uc0f3\uf003\uffc3\uc0f3\uf003\uffc3\uc3c3\ufc3f\uf00f\uc0ff\uc0f3\uc0ff\uc3c3\ufc3f\uc0f3\uc00f\uc003\uc3c3\uffc3\uc0f3\uffc3\uf003\uc3c3\ufc3f\uff03\uc0ff\uc003\uc0f3\uc3c3\ufc0f\uc0f3\uc0ff\uc0f3\uc3c3\uc3c3\uc0f3\uffc3\uffc3\uf00f\uf00f\uc003\uf003\uf0ff\uf00f\uf00f\ufc0f\uf00f\uf00f\uc0f3\uf003\uf00f\uf003\uc003\uffc3\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\u0000\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uf00f\uc3f3\uf00f\uffc0\uc3c3\uffc3\uc3f3\uc3f3\uf00f\uf003\uf00f\uf003\uf00f\uc003\uc3f3\uc3f3\uc3c3\uc3f3\ufc3f\uaaf0\uf0c3\uffc3\uc0c3\uc3c3\uc3c3\uc3c3\ucfc3\uc3c3\uffc3\ufc3f\uc3f3\uc3f3\uffc3\uc003\ufc3f\u56bc\ufc03\uffc3\uc003\uc303\uc3c3\uc3c3\ucfc3\uc3c3\uf00f\ufc3f\uc3f3\uc3f3\uc0c3\uc3f3\ufc3f\u01ac\ufc03\uffc3\uc333\uc033\uc3c3\uf003\uccc3\uf003\uc0ff\ufc3f\uc3f3\uc3f3\uc3c3\uc3f3\ufc3f\u006c\uf0c3\uffc3\uc3f3\uc0f3\uc3c3\uffc3\uf3c3\ufcc3\uc0f3\ufc3f\uc0f3\uf0cf\uc00f\uc3f3\uf00f\u006c\uc3c3\uc003\uc3f3\uc3f3\uf00f\uffc3\ucc0f\uc3c3\uf00f\ufc3f\uf00f\ufc3f\uffff\uffff\uffff\u006c\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffff\u0000\uffff\uffff\u006c\uffff\uffff\uffff\uffff\uffff\ufc0f\uffff\uffff\u00c0\ue003\u8000\uc3f3\u01ff\uc3c3\uffff\u006c\uffff\uffff\uc003\uf03f\uffff\uf2a3\uffff\uffff\uff3f\u8000\u8000\uc3f3\u06aa\uc3c3\uffff\u006c\uff0f\uffff\uc0ff\uf03f\uffff\ucae8\uf3f3\uffff\u5715\u8aa8\u8ff8\uc333\u1a55\uf00f\uffff\u006c\uff0f\uffff\uf03f\uf03f\uf00f\uc8f8\ufccf\uffff\u5715\u8ff8\u8ff8\uc003\u1800\ufc3f\uffff\u006c\uffff\uff0f\ufc0f\uf03f\uf00f\uc8f8\uff3f\uffff\u0000\u8000\u8000\uc0c3\u1800\ufc3f\uff0f\u006c\uff0f\uff0f\uff03\uffff\uffff\uca28\ufccf\uffff\u0000\u8000\u8000\uc3f3\u1800\ufc3f\uff0f\u006c\uff0f\uff3f\uc003\uf03f\uffff\uf2a3\uf3f3\uffff\uffff\u8ff8\u8ff8\uffff\u1800\uffff\uffff\uaaac\uffff\uffcf\uffff\uffff\uffff\ufc0f\uffff\uffff\uffff\u8ff8\u8ff8\u00c0\u3fff\uf03f\uff03\u3fff\uf557\u3fff\ud557\uffff\u1800\u3fff\uffff\u3ffc\u0000\ufffc\u000f\uff3f\u3fff\ucfcf\ufcfc\u0fff\uf7f7\ucfff\u56a5\uff3f\u1800\uc3ff\uffff\u3ff3\u3fff\ufffc\u0003\u5715\u3fff\u00f3\uf3f0\u33ff\uf777\uf3ff\u5aa9\ufc0f\u1800\uccff\ufeff\u3fcf\u3fff\ufffc\ua8a0\u5715\u3fff\u0cf3\uf3cf\u3cff\ufddf\ufcff\u5aa9\uf003\u1800\uf0ff\ufebf\u3f3f\u3fff\ufffc\u2aa8\u0000\u3fff\u3f3f\uff3f\u3f3f\ufddf\uff3f\u5aa9\uffff\u1800\uff0f\ufbef\u3cff\u3fff\ufffc\uaa28\u0000\u3fff\u3f3f\uff3f\u3fcf\ufddf\uffcf\u5aa9\uff3f\u1800\uff33\ufbfb\u33ff\u3fff\ufffc\ua2a0\uffff\u3fff\u3fff\uffff\u3ff3\ufd5f\ufff3\u56a5\ufc0f\u1800\uffc3\uefbe\u0fff\u3fff\ufffc\u0000\uffff\u3fff\u3fff\uffff\u3ffc\uffff\ufffc\ud557\uf003\u1aaa\ufffc\ubeaf\u3fff\u3fff\u0000\u0000\uaaaa\uffff\uffff\uffff\u3fff\ufffc\uafbf\ufff3\u00ff\ufff0\uc00f\ufcff\uffff\u0000\u0000\u0000\uffff\uffff\uffff\uffff\ucfff\uffff\ubebe\uffce\ucf3f\uffc3\uf3ff\u03ff\uff00\u0000\u0000\ufffc\uaaaa\u0fff\ufffc\uffff\uf3ff\ubffa\ubffa\uffca\uc00f\uffcf\uc0ff\uffff\uf0ff\ua8a0\u028a\ufffc\uffff\uf0ff\ufff3\uffff\ua8ff\uabaa\uafea\ufff2\ucfcf\uff0f\uff3f\uffff\ucfff\u2aa8\u0aaa\ufffc\uaaaa\uff3f\uf3cf\uffff\ua3ff\uaaaa\uaaaa\ufff2\u0c33\uff3f\uc00f\u03ff\u3ffc\uaa28\u0a2a\ufffc\uffff\uffcf\ucc3f\uffff\u8fff\uaaa2\uaaa8\ufffc\u3ff3\uff3f\ufff0\ufc3f\u3fff\ua2a0\u02a2\ufffc\uaaaa\uffcf\u3fff\ufffc\u3fff\u0a80\u2aa3\uffff\u3fc3\uff3f\ufccc\ucf3f\u3ff3\u0000\u0000\ufffc\uffff\ufff3\uebff\ufff3\uffff\uc00f\uc00f\uffff\u0fcf\uff00\u0000\u0000\uc000\u0000\u0000\ufffc\ufffc\u0000\u3fff\uaaaa\uf000\uffff\uffff\ucfc3\uc300\uffff\uffff\ucff3\uffcf\uffff\ufffc\uffff\ufffc\uffff\u3fff\uaaaa\uc000\ufc3f\u0000\u003c\u333c\u0ff3\ucfc0\ucff3\uffcf\uff7f\ufff3\uffff\ufffc\uffff\u3fff\uaaaa\u028a\uf3cf\u3ffc\ucfc3\uc300\u33f3\ucf3f\ucff3\u00c3\ufd5f\uffcf\uffff\ufffc\uffff\u3fff\uaaaa\u0aaa\ucc33\ucff3\uffff\uffff\u00f3\u033c\u0ff3\u3cc0\ufd5f\uff3f\uffff\ufffc\uffff\u3fff\uaaaa\u0a2a\ucc33\uf00f\uc003\uf003\u3cf3\u333c\u3ff3\uc3f3\uf557\ufcff\uffff\ufffc\uffff\u3fff\uaaaa\u02a2\uf3cf\uffff\u3ffc\ucf0c\u3ff3\u3300\u3ff3\uc3f3\uf5d7\uf3ff\uffff\ufffc\uffff\u3fff\uaaaa\u0000\ufc3f\ucccc\uc003\u030c\u0003\u33cf\u3ff3\u3cf3\uf5d7\ucfff\uffff\ufffc\uffff\u3fff\uaaaa\u0000\uffff\u3333\uffff\uf003\u0033\u03cc\u0ff3\u00f0\ufd5f\u3fff\uffff\u0000\uaaaa\uc003\uc003\u0000\u000f\uf000\uc00f\u000f\u0000\uf000\u0000\uffff\u0000\u0000\uffff\uffff\uaaaa\u0aa8\u0ffc\u8a8a\u0003\uc000\u0fc3\u0003\u0000\uc000\u2828\uffff\u0000\u0000\uffff\u0000\u0000\u0808\u003c\u8a8a\ua8a0\u028a\u3ff0\u0000\u0000\u0000\u2828\uffff\ua8a0\u028a\uffff\u0000\u5555\u0a88\u003c\u8a8a\u2aa8\u0aaa\u3ffc\u0000\u0000\u0000\u2828\uffff\u2aa8\u0aaa\uffff\u0000\u0000\u0a88\u003c\u0000\uaa28\u0a2a\u0ffc\u0300\u0303\u0303\u0000\uffff\uaa28\u0a2a\uffff\ua8aa\u5555\u02a8\u0000\ua8a8\ua2a0\u02a2\u0ffc\u3330\u3333\u3333\uffff\uffff\ua2a0\u02a2\uffff\uaaa2\u0000\u0000\u0000\ua8a8\u0000\u0000\u00c0\u3333\u3333\u3333\uffff\uffff\u0000\u0000\uffff\u2a2a\u0000\uc003\uc003\ua8a8\u0000\u0000\u0003\u3f33\u3f3f\uf33f\uffff\uffff\u0000\u0000\uffff\u0000\u0000\uaaa3\uc16a\u3000\u0300\uaaa0\ua2a8\ua8aa\ua82a\f\u2a8a\uc3c3\u0000\u0000\uc003\uaaa8\u015a\uaaa3\uc16a\u82a8\ua8aa\uaa82\ua2a8\ua8aa\ua82a\u2a82\u2a8a\u2828\uffff\u3f3f\u1554\uaaa8\u015a\uaaa3\uc16a\ua2a8\ua8aa\uaa0a\ua2a8\ua8aa\ua82a\u2a8a\u2a8a\u2828\u0000\u3f3f\u1554\uaaa8\u015a\uaaa3\uc16a\ua2a8\ua8aa\uaa2a\ua154\ua8aa\ua82a\u2a8a\u154a\u2828\uaaaa\u3f3f\u1554\uaaa8\u015a\u000f\uf000\ua2a8\ua8aa\u002a\u5154\u5455\u0005\u2a8a\u1545\u2828\u5555\u3f3f\u1554\u0000\u0000\uaaa3\uc16a\ua2a8\ua8aa\ua82a\u5000\u5455\uaa01\u2a8a\u0005\u2828\u0000\u2a2a\u1554\uaaa3\uc16a\uaaa3\uc16a\ua2a8\ua8aa\ua82a\u0000\u0000\uaa80\u2a8a\u0000\u2828\u0000\u1515\u1554\uaaa3\uc16a\uaaa3\uc16a\ua2a8\ua8aa\ua82a\u3000\u0300\uaaa0\u2a8a\f\uc3c3\u0000\u0000\uc003";

    int i;
    int j;
    int x;
    int y;

    // decompress sprite
    BufferedImage image = new BufferedImage(128, 192, 1);
    for(y = 0; y < 192; y++) {
      for(x = 0; x < 128; x += 8) {
        for(i = 0; i < 8; i++) {
          j = ((S.charAt((y << 4) + (x >> 3)) >> (i << 1)) & 3);
          image.setRGB(x + i, y, j == 0 ? 0 : j == 1 ? 0x606060
              : j == 2 ? 0xA8A8A8 : 0xF8F8F8);
        }
      }
    }

    Graphics2D g = null;

    while(true) {

      if (g == null) {
        g = (Graphics2D)getGraphics();
      } else {
        g.drawImage(image, 0, 0, 256, 384, null);
      }

      Thread.yield();
    }
  }
}

Compile 'n Shrink now produces a 3935 byte file.

The other common technique is to store each colored pixel value as a distinct character in a String. The following program scans across each row, from left to right, converting each pixel value into the characters ‘0’, ‘1’, ‘2’ or ‘3’.

import java.awt.image.*;
import javax.imageio.*;
import java.io.*;

public class Converter2 {

  public static final String sourcePath = "...";

  public static void main(String... args) throws Throwable {

    StringBuffer sb = new StringBuffer();

    BufferedImage image = ImageIO.read(new File(sourcePath + "0.png"));

    for(int y = 0; y < image.getHeight(); y++) {
      for(int x = 0; x < image.getWidth(); x++) {
        sb.append(getColor(0xFF & image.getRGB(x, y)));
      }
    }

    System.out.print(sb.toString());
  }

  private static int getColor(int pixel) {
    switch(pixel) {
      case 0x00:
        return 0;
      case 0x60:
        return 1;
      case 0xA8:
        return 2;
      default:
        return 3;
    }
  }
}

Below is the program to decode the String, but due to forum message size limitations, I trimmed the long String. If you actually want to run this code, restore the String using the above encoder.

import java.applet.Applet;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class a extends Applet implements Runnable {

  @Override
  public void start() {
    new Thread(this).start();
  }

  public void run() {

    final String S = "33333333...";

    int j;
    int x;
    int y;

    // decompress sprite
    BufferedImage image = new BufferedImage(128, 192, 1);
    for(y = 0; y < 192; y++) {
      for(x = 0; x < 128; x++) {
        j = S.charAt((y << 7) + x) - '0';
        image.setRGB(x, y, j == 0 ? 0 : j == 1 ? 0x606060
            : j == 2 ? 0xA8A8A8 : 0xF8F8F8);
      }
    }

    Graphics2D g = null;

    while(true) {

      if (g == null) {
        g = (Graphics2D)getGraphics();
      } else {
        g.drawImage(image, 0, 0, 256, 384, null);
      }

      Thread.yield();
    }
  }
}

Compile 'n Shrink generates a 4023 byte file from this template, which is slightly larger than the Unicode technique.

3935 - 318 = 3617
4023 - 318 = 3705

100 * (3705/3617 - 1) = 2.43%

3705 - 3617 = 88

The second technique used an additional 88 bytes. It’s 2.43% larger. That’s pretty insignificant, but it may make the difference between fitting under 4K or not. Still it’s good to know that Compile 'n Shrink does an amazing job on both techniques.

I would recommend using the first technique for bitmaps and the second for level maps. Levels are sparse; they mostly contain empty space. But, the non-empty regions may consist of a large number of tile types. The second technique probably compresses well regardless of the number of distinct characters used as long as it is relatively sparse.

I only tested these 2 techniques. If you are familiar with some others, I encourage you to test it out and share your findings.

RLE might help, unless you lose the savings in the decoding code.

I tried storing everything as 2 colour bitmaps, followed by a list of 6 bit (RRGGBB) colour fills. All images must be line drawings, otherwise the fills bleed into adjoining spaces. At run-time the game does a line scan and floodfills the first white pixel it finds with the first colour fill. The line scan then continues until it finds another white pixel and does another flood fill. The flood-fill code does repeated scans of the entire image. On each scan any white pixel with an orthogonally adjacent pixel in any colour than black, gets filled. The scan also has to be able to distinguish between original black and white pixels, and one’s that have subsequently been filled.

The decoder is fairly compact and works well for line images with a few large areas of solid fill. Next year I might tweak it a bit to allow cel-shading.

Now comes the part that may well be non-optimum. I line scanned the 2 bit image, packing each 32 bit block of data into 5 characters for storage in a string. This only used characters in the normal ASCI range (32-126). However that might not give the best pack200 compression. It might be best for images that are a multiple of 32 bits width; but I also have many 16 bit width images.

Not so happy with my efforts on Level Data: I run length encoded the level data for Valentino, but this was probably a waste of time. At least it reduced the amount of crud in the source code.

Thanks for sharing your code,

I have tried a RLE, but in the end, the additional overhead for the unpacking code killed the effect,
AND the higher entropy (by precomressing it) reduced the effect of the better jar/pack compression.

Compressing the image with an application like Pngguantlet compresses the image to 3604 bytes. This sounds better then converting to strings?

[quote]Compressing the image with an application like Pngguantlet compresses the image to 3604 bytes. This sounds better then converting to strings?
[/quote]
Nice. But, what’s the best way to load it?

Sadly the png is already compressed, so most of those bytes will appear in the pack file. :’(

[quote]Sadly the png is already compressed, so most of those bytes will appear in the pack file.
[/quote]
How would you get the image into the pack file and how you would load it?

[quote]RLE might help, unless you lose the savings in the decoding code.
[/quote]
Before creating an RLE encoder, I did some analysis on the image. I wrote a method that computes the frequency of each color, the maximum run-length and the average run-length. Here are the results:

colors[0] = 7674
colors[1] = 1264
colors[2] = 3074
colors[3] = 12564

maximum run-length: 157
average run-length: 3

Color 1 (dark gray) appears the fewest times. 8-bits are required to capture the maximum run-length.

The RLE encoder below borrows ideas from the PCX file format. It uses the following scheme:

If the run-length is 1 and the color is not 1, then encode the color directly as a 2-bit number; otherwise, add the following bits:

01 cc xxxxxxxx

where 01 is an identifier (1 is used since it is least frequent color), cc is the color (2-bits) and xxxxxxxx is the run-length (8-bits).

The resultant bit sequence is converted into a Unicode string with some 0’s padded at the end.

import java.awt.image.*;
import javax.imageio.*;
import java.io.*;

public class Converter3 {

  public static final String sourcePath = "...";

  public static void main(String... args) throws Throwable {

    BufferedImage image = ImageIO.read(new File(sourcePath + "0.png"));

    //countColors(image);
    //measureMaxLength(image);

    int[] pixels = new int[image.getWidth() * image.getHeight() + 1];
    for(int y = 0; y < image.getHeight(); y++) {
      for(int x = 0; x < image.getWidth(); x++) {
        pixels[y * image.getWidth() + x]
            = getColor(image.getRGB(x, y) & 0xFF);
      }
    }
    pixels[pixels.length - 1] = -1;

    int color = pixels[0];
    int length = 0;

    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < pixels.length; i++) {
      if (color == pixels[i]) {
        length++;
      } else {
        if (length == 1 && color != 1) {
          sb.append(String.format("%2s",
              Integer.toBinaryString(color)).replace(" ", "0"));
        } else {
          sb.append("01");
          sb.append(String.format("%2s",
              Integer.toBinaryString(color)).replace(" ", "0"));
          sb.append(String.format("%8s",
              Integer.toBinaryString(length)).replace(" ", "0"));
        }

        color = pixels[i];
        length = 1;
      }
    }

    for(int i = (sb.length() % 16) - 1; i >= 0; i--) {
      sb.append("0");
    }

    StringBuffer sb2 = new StringBuffer();
    for(int i = 0; i < sb.length(); i += 16) {
      printValue(sb2, Integer.parseInt(new StringBuilder(
          sb.substring(i, i + 16)).reverse().toString(), 2));
    }
    
    System.out.print(sb2);
  }

  private static void measureMaxLength(BufferedImage image) {

    int[] lens = new int[158];

    int maxLength = 0;
    int lengths = 0;
    int lengthTotal = 0;

    int color = -1;
    int length = 0;

    for(int y = 0; y < image.getHeight(); y++) {
      for(int x = 0; x < image.getWidth(); x++) {
        if (color == getColor(image.getRGB(x, y) & 0xFF)) {
          length++;
          if (length > maxLength) {
            maxLength = length;
          }
        } else {

          lens[length]++;
          lengthTotal += length;
          lengths++;

          color = getColor(image.getRGB(x, y) & 0xFF);
          length = 1;
        }
      }
    }

    lens[length]++;
    lengthTotal += length;
    lengths++;

    System.out.format("max length: %d%n", maxLength);
    System.out.format("average length: %d%n", lengthTotal / lengths);

    for(int i = 0; i < 158; i++) {
      System.out.format("lens[%d] = %d%n", i, lens[i]);
    }
  }

  private static void countColors(BufferedImage image) {
    int[] colors = new int[4];

    for(int y = 0; y < image.getHeight(); y++) {
      for(int x = 0; x < image.getWidth(); x++) {
        colors[getColor(image.getRGB(x, y) & 0xFF)]++;
      }
    }

    for(int i = 0; i < 4; i++) {
      System.out.format("colors[%d] = %d%n", i, colors[i]);
    }
  }

  private static int getColor(int pixel) {
    switch(pixel) {
      case 0x00:
        return 0;
      case 0x60:
        return 1;
      case 0xA8:
        return 2;
      default:
        return 3;
    }
  }

  private static void printValue(StringBuffer sb, int value) {
    switch(value) {
      case 0x0008:
        sb.append("\\b");
        break;
      case 0x0009:
        sb.append("\\t");
        break;
      case 0x000a:
        sb.append("\\n");
        break;
      case 0x000c:
        sb.append("\\f");
        break;
      case 0x000d:
        sb.append("\\r");
        break;
      case 0x0022:
        sb.append("\\\"");
        break;
      case 0x0027:
        sb.append("\\'");
        break;
      case 0x005c:
        sb.append("\\\\");
        break;
      default: {
        String s = Integer.toHexString(value);
        while(s.length() < 4) {
          s = "0" + s;
        }
        sb.append("\\u").append(s);
        break;
      }
    }
  }
}

Here is the decoder (the Unicode string was shortened to fit within the forum message limit):

import java.applet.Applet;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class a extends Applet implements Runnable {

  @Override
  public void start() {
    new Thread(this).start();
  }

  public void run() {

    final String S = "\u27ce\u4e40\u602a\u2f2e\u0a20\u24e4...";

    int i = 0;
    int j = 0;
    int x;
    int y;

    int length = 0;

    // decompress sprite
    StringBuilder sb = new StringBuilder();
    for(i = 0; i < S.length(); i++) {
      for(j = 0; j < 16; j++) {
        sb.append((char)('0' + ((S.charAt(i) >> j) & 1)));
      }
    }
    i = 0;

    BufferedImage image = new BufferedImage(128, 192, 1);
    for(y = 0; y < 192; y++) {
      for(x = 0; x < 128; x++) {
        if (length == 0) {
          j = Integer.parseInt(sb.substring(i, i + 2), 2);
          i += 2;
          if (j != 1) {
            length = 1;
          } else {
            j = Integer.parseInt(sb.substring(i, i + 2), 2);
            i += 2;
            length = Integer.parseInt(sb.substring(i, i + 8), 2);
            i += 8;
          }
        }
        image.setRGB(x, y, j == 0 ? 0 : j == 1 ? 0x606060
            : j == 2 ? 0xA8A8A8 : 0xF8F8F8);
        length--;
      }
    }

    Graphics2D g = null;

    while(true) {

      if (g == null) {
        g = (Graphics2D)getGraphics();
      } else {
        g.drawImage(image, 0, 0, 256, 384, null);
      }

      Thread.yield();
    }
  }
}

Unfortunately, the result is 5775 bytes!

[quote]Compressing the image with an application like Pngguantlet compresses the image to 3604 bytes. This sounds better then converting to strings?
[/quote]
I’m not so sure. After subtracting an estimate for the overhead (discussed in the opening post), the Unicode String encoding technique produced a 3617 byte file. If I come up with a scheme for loading a PNG directly, I’ll post the results.

Don’t you ever sleep? ;D