Home

Algorithmic Advent: 15 – C64 Koala Painter file converter

Published on 15 Dec 2010. Tagged with php, algorithmicadvent.

This class is meant to convert images created or edited with the C64 Koala Painter application to a more modern file format. It is basically a port of Peter Krefting's koalatoppm project. The class is also part of his repository (see last link), so you might want to use that version rather than the following one which might be dated.

/**
 * Koala Painter image converter
 *
 * The Commodore 64 version of Koala Painter used a fairly simple file format
 * corresponding directly to the way bitmapped graphics are handled on the
 * computer: A two-byte load address, followed immediately by 8000 bytes of raw
 * bitmap data, 1000 bytes of raw "Video Matrix" data, 1000 bytes of raw "Color
 * RAM" data, and a one-byte Background Color field.
 *
 *   (Source: http://en.wikipedia.org/wiki/KoalaPad#File_Format)
 *
 * This class is based on the koalatoppm application written by Peter Karlsson.
 * <http://git.debian.org/?p=users/peterk/koalatoppm.git>
 *
 * @version 2010-Dec-11
 *
 * @author Marc Ermshaus <http://www.ermshaus.org/>
 * @license GNU General Public License <http://www.gnu.org/licenses/gpl.html>
 */
class KoalaConverter
{
    protected static $pixelmask = array(0xC0, 0x30, 0x0C, 0x03);

    protected static $pixeldisplacement = array(6, 4, 2, 0);

    /**
     * @var array Mappings: C64 colour index -> RGB
     */
    protected static $c64colours = array(
        array(  0,   0,   0), // Black
        array(255, 255, 255), // White
        array(189,  24,  33), // Red
        array( 49, 231, 198), // Cyan
        array(181,  24, 231), // Purple
        array( 24, 214,  24), // Green
        array( 33,  24, 173), // Blue
        array(222, 247,   8), // Yellow
        array(189,  66,   0), // Orange
        array(107,  49,   0), // Brown
        array(255,  74,  82), // Light red
        array( 66,  66,  66), // Gray 1
        array(115, 115, 107), // Gray 2
        array( 90, 255,  90), // Light green
        array( 90,  82, 255), // Light blue
        array(165, 165, 165)  // Gray 3
    );

    /**
     * @var array Stores the image's data as an array of RGB triples
     */
    protected $triples = array();

    /**
     * Returns the RGB value of a colour index
     *
     * @param int $index The colour's index
     * @return array RGB colour triplet
     */
    protected function getColour($index)
    {
        if ($index >= 0 && $index <= 15) {
            return self::$c64colours[$index];
        } else {
            throw new Exception('Unknown colour index: ' . $index);
        }
    }

    /**
     * Loads a Koala Painter (.koa) image
     *
     * @param string $file Path to image file
     */
    public function load($file)
    {
        $tmp = file_get_contents($file);
        $l   = strlen($tmp);
        $ret = array();

        if ($l !== 10003) {
            throw new Exception('Input data of wrong length');
        }

        $loadaddress = substr($tmp,     0,    2);
        $image       = substr($tmp,     2, 8000);
        $colour1     = substr($tmp,  8002, 1000);
        $colour2     = substr($tmp,  9002, 1000);
        $background  = substr($tmp, 10002,    1);

        if ("\x00\x60" !== $loadaddress) {
            throw new Exception('Input data is not a .koa file');
        }

        // Image
        for ($y = 0; $y < 200; $y++) {
            for ($x = 0; $x < 160; $x++) {
                
                // Get value of pixel at (x,y)
                /** @todo not sure how this is supposed to work */
                $index = (int) (floor($x / 4) * 8
                                + ($y % 8)
                                + floor($y / 8) * 320);
                
                $pixel = (ord($image[$index]) & self::$pixelmask[$x % 4])
                         >> self::$pixeldisplacement[$x % 4];

                // Colour index
                /** @todo not sure how this is supposed to work */
                $ci = (int) (floor($x / 4) + floor($y / 8) * 40);
                $k = 0;

                switch ($pixel) {
                    /** @todo I am not sure whether the bitwise operation for
                     *    background is correct (see: C original) */
                    case 0: $k = ord($background) & 0x0F;   break;
                    case 1: $k = ord($colour1[$ci]) >> 4;   break;
                    case 2: $k = ord($colour1[$ci]) & 0x0F; break;
                    case 3: $k = ord($colour2[$ci]) & 0x0F; break;
                    default:
                        throw new Exception('Internal error');
                        break;
                };

                $this->triples[] = $this->getColour($k);
            }
        }
    }

    /**
     * Returns a PNG version of the image
     *
     * @param bool $expand Double every pixel in horizontal direction?
     * @return resource Image resource identifier
     */
    public function exportPng($expand = true)
    {
        $width  = ($expand) ? 320 : 160;
        $height = 200;

        $gd = imagecreatetruecolor($width, $height);

        for ($i = 0; $i < $width * $height; $i++) {
            if ($expand) {
                $rgb = $this->triples[(int) floor($i / 2)];
            } else {
                $rgb = $this->triples[$i];
            }

            $x = $i % $width;
            $y = (int) floor($i / $width);

            $colour = imagecolorallocate($gd, $rgb[0], $rgb[1], $rgb[2]);
            imagesetpixel($gd, $x, $y, $colour);
        }

        return $gd;
    }
}



$koa = new KoalaConverter();
$koa->load('./pic_b_turrican.koa');

header('Content-Type: image/png');

imagepng($koa->exportPng());