Behold Your Vector Nightmare, Amiga Fans

2021-03-06 

Over on Hacker News there was an entry about the “awful” Amiga Kickstart 1.x icon and why it looked the way it did. This led to a link over on Stack Overflow where it was revealed that this was graphic actually drawn in a vector style, as opposed to raster pixels.

They also provided the actual bytes used to render the graphic. I thought it’d be fun to write a little parser to render these bytes.

And so did several other people, apparently, as I’m discovering now. 😐

Anyway, here’s my humble Codepen. And if that goes down, the original code is below the embed. 😎

(I skipped flood fill because I used two.js for this, and didn’t realize until it was too late that I’d chosen poorly. Easy enough to swap out the graphics library, but it’s time to move on.)

See the Pen Amiga Kickstart vector parser by Toby D (@Fortyseven) on CodePen.

/*
    Inspired by:
    https://retrocomputing.stackexchange.com/questions/13897/why-was-the-kickstart-1-x-insert-floppy-graphic-so-bad/13901

    2021-03-06
    \*/

const floppy = [
  0xff, 0x01, 0x23, 0x0b, 0x3a, 0x0b, 0x3a, 0x21, 0x71, 0x21, 0x71, 0x0b, 0x7d,
  0x0b, 0x88, 0x16, 0x88, 0x5e, 0x7f, 0x5e, 0x7f, 0x38, 0x40, 0x38, 0x3e, 0x36,
  0x35, 0x36, 0x34, 0x38, 0x2d, 0x38, 0x2d, 0x41, 0x23, 0x48, 0x23, 0x0b, 0xfe,
  0x02, 0x25, 0x45, 0xff, 0x01, 0x21, 0x48, 0x21, 0x0a, 0x7e, 0x0a, 0x8a, 0x16,
  0x8a, 0x5f, 0x56, 0x5f, 0x56, 0x64, 0x52, 0x6c, 0x4e, 0x71, 0x4a, 0x74, 0x44,
  0x7d, 0x3c, 0x81, 0x3c, 0x8c, 0x0a, 0x8c, 0x0a, 0x6d, 0x09, 0x6d, 0x09, 0x51,
  0x0d, 0x4b, 0x14, 0x45, 0x15, 0x41, 0x19, 0x3a, 0x1e, 0x37, 0x21, 0x36, 0x21,
  0x36, 0x1e, 0x38, 0x1a, 0x3a, 0x16, 0x41, 0x15, 0x45, 0x0e, 0x4b, 0x0a, 0x51,
  0x0a, 0x6c, 0x0b, 0x6d, 0x0b, 0x8b, 0x28, 0x8b, 0x28, 0x76, 0x30, 0x76, 0x34,
  0x72, 0x34, 0x5f, 0x32, 0x5c, 0x32, 0x52, 0x41, 0x45, 0x41, 0x39, 0x3e, 0x37,
  0x3b, 0x37, 0x3e, 0x3a, 0x3e, 0x41, 0x3d, 0x42, 0x36, 0x42, 0x33, 0x3f, 0x2a,
  0x46, 0x1e, 0x4c, 0x12, 0x55, 0x12, 0x54, 0x1e, 0x4b, 0x1a, 0x4a, 0x17, 0x47,
  0x1a, 0x49, 0x1e, 0x4a, 0x21, 0x48, 0xff, 0x01, 0x32, 0x3d, 0x34, 0x36, 0x3c,
  0x37, 0x3d, 0x3a, 0x3d, 0x41, 0x36, 0x41, 0x32, 0x3d, 0xff, 0x01, 0x33, 0x5c,
  0x33, 0x52, 0x42, 0x45, 0x42, 0x39, 0x7d, 0x39, 0x7d, 0x5e, 0x34, 0x5e, 0x33,
  0x5a, 0xff, 0x01, 0x3c, 0x0b, 0x6f, 0x0b, 0x6f, 0x20, 0x3c, 0x20, 0x3c, 0x0b,
  0xff, 0x01, 0x60, 0x0e, 0x6b, 0x0e, 0x6b, 0x1c, 0x60, 0x1c, 0x60, 0x0e, 0xfe,
  0x03, 0x3e, 0x1f, 0xff, 0x01, 0x62, 0x0f, 0x69, 0x0f, 0x69, 0x1b, 0x62, 0x1b,
  0x62, 0x0f, 0xfe, 0x02, 0x63, 0x1a, 0xff, 0x01, 0x2f, 0x39, 0x32, 0x39, 0x32,
  0x3b, 0x2f, 0x3f, 0x2f, 0x39, 0xff, 0x01, 0x29, 0x8b, 0x29, 0x77, 0x30, 0x77,
  0x35, 0x72, 0x35, 0x69, 0x39, 0x6b, 0x41, 0x6b, 0x41, 0x6d, 0x45, 0x72, 0x49,
  0x72, 0x49, 0x74, 0x43, 0x7d, 0x3b, 0x80, 0x3b, 0x8b, 0x29, 0x8b, 0xff, 0x01,
  0x35, 0x5f, 0x35, 0x64, 0x3a, 0x61, 0x35, 0x5f, 0xff, 0x01, 0x39, 0x62, 0x35,
  0x64, 0x35, 0x5f, 0x4a, 0x5f, 0x40, 0x69, 0x3f, 0x69, 0x41, 0x67, 0x3c, 0x62,
  0x39, 0x62, 0xff, 0x01, 0x4e, 0x5f, 0x55, 0x5f, 0x55, 0x64, 0x51, 0x6c, 0x4e,
  0x70, 0x49, 0x71, 0x46, 0x71, 0x43, 0x6d, 0x43, 0x6a, 0x4e, 0x5f, 0xff, 0x01,
  0x44, 0x6a, 0x44, 0x6d, 0x46, 0x70, 0x48, 0x70, 0x4c, 0x6f, 0x4d, 0x6c, 0x49,
  0x69, 0x44, 0x6a, 0xff, 0x01, 0x36, 0x68, 0x3e, 0x6a, 0x40, 0x67, 0x3c, 0x63,
  0x39, 0x63, 0x36, 0x65, 0x36, 0x68, 0xff, 0x01, 0x7e, 0x0b, 0x89, 0x16, 0x89,
  0x5e, 0xfe, 0x01, 0x22, 0x0b, 0xfe, 0x01, 0x3b, 0x0b, 0xfe, 0x01, 0x61, 0x0f,
  0xfe, 0x01, 0x6a, 0x1b, 0xfe, 0x01, 0x70, 0x0f, 0xfe, 0x01, 0x7e, 0x5e, 0xfe,
  0x01, 0x4b, 0x60, 0xfe, 0x01, 0x2e, 0x39, 0xff, 0xff,
];

class AmigaVectParser {
  constructor(bytes, elem) {
    this.palette = ["#FFFFFF", "#000000", "#7777CC", "#BBBBBB"];
    this.offset = [0, 0];
    this.prevOffset = [0, 0];
    this.curColor = 0;
    this.isDrawing = false;
    this.buffer = bytes || [0xff, 0xff];
    this.done = false;

    this.two = new Two({ width: 640, height: 400 }).appendTo(elem);
  }

  doCmd(cmd_pair) {
    if (cmd_pair[0] === 0xff) {
      this.isDrawing = false;
      if (cmd_pair[1] === 0xff) {
        // cmd_done
        this.done = true;
        return;
      } else {
        // cmd_colorSet
        this.curColor = cmd_pair[1];
        return;
      }
    } else if (cmd_pair[0] === 0xfe) {
      // cmd_floodFill
      this.isDrawing = false;
      this.pointer += 2; //TODO FLOOD FILL
      return;
    }
    if (!this.isDrawing) {
      // first coordinate in a poly-line
      this.prevOffset[0] = cmd_pair[0];
      this.prevOffset[1] = cmd_pair[1];
      this.isDrawing = true;
      return;
    } else {
      // continuing the poly-line
      this.offset[0] = cmd_pair[0];
      this.offset[1] = cmd_pair[1];

      let line = this.two.makeLine(
        this.prevOffset[0] * 2,
        this.prevOffset[1] * 2, // doubling up X/Y to make it easier to see at 640x400
        this.offset[0] * 2,
        this.offset[1] * 2
      );

      line.stroke = this.palette[this.curColor];
      line.linewidth = 1;

      this.prevOffset[0] = this.offset[0];
      this.prevOffset[1] = this.offset[1];
    }
  }

  draw() {
    let cmd = [0, 0];
    let pointer = 0;
    this.done = false;

    while (!this.done) {
      cmd[0] = this.buffer[pointer++];
      cmd[1] = this.buffer[pointer++];
      this.doCmd(cmd);
    }
    this.two.update();
  }
}

renderer = new AmigaVectParser(floppy, document.getElementById("draw-shapes"));
renderer.draw();