
/***************************************************************************

    MDATOPBM: Convert Microdesign area to portable bitmap
    Copyright (C) 1999 John Elliott <jce@seasip.demon.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    See the file mdaspec.txt for a specification of the MDA format.
******************************************************************************/

#include <string.h>
#include <stdio.h>
#include "pbm.h"
#include "mallocvar.h"

/* Simple MDA -> portable bitmap converter */

typedef unsigned char mdbyte;   /* Must be exactly one byte */

static FILE *infile;            /* Input file */
static mdbyte header[128];      /* MDA file header */
static bit **data;          /* PBM image */
static mdbyte *mdrow;           /* MDA row after decompression (MD3 only) */
static int bInvert = 0;     /* Invert image? */
static int bScale  = 0;     /* Scale image? */
static int bAscii  = 0;     /* Ouput ASCII PBM? */
static int nInRows, nInCols;        /* Height, width of input (rows x bytes) */
static int nOutCols, nOutRows;      /* Height, width of output (rows x bytes) */

static mdbyte 
getbyte(void) {
    /* Read a byte from the input stream, with error trapping */
    int b;

    b = fgetc(infile);

    if (b == EOF) pm_error("Unexpected end of MDA file\n");
    
    return (mdbyte)b;
}



static void 
render_byte(int *col, int *xp, int *yp, int b) {

/* Convert a byte to 8 cells in the destination bitmap 
 *
 * *col = source column
 * *xp  = destination column
 * *yp  = destination row
 *  b   = byte to draw
 *
 * Will update *col, *xp and *yp to point to the next bit of the row.
 */

    int mask = 0x80;
    int n;
    int y3 = *yp;

    if (bScale) y3 *= 2;

    if (y3 >= nOutRows) return;

    for (n = 0; n < 8; ++n) {
        if (bInvert) data[y3][*xp] = (b & mask) ? PBM_BLACK : PBM_WHITE;
        else         data[y3][*xp] = (b & mask) ? PBM_WHITE : PBM_BLACK;
        mask = mask >> 1;
        if (bScale) data[y3+1][*xp] = data[y3][*xp];
        ++(*xp);
    }
    ++(*col);       /* Next byte */
    if ((*col) >= nInCols) {
        /* Onto next line? */
        *col = 0;
        *xp = 0;
        ++(*yp);
    }
}


static void 
md2_trans(void) {
    /* Convert a MicroDesign 2 area to PBM */
    /* MD2 has RLE encoding that may go over */

    int x1, y1, col;    /* multiple lines. */
    mdbyte b;
    int c;

    x1 = y1 = col = 0;

    while (y1 < nInRows) {
        b = getbyte();
    
        if (b == 0 || b == 0xFF) {
            /* RLE sequence */
            c = getbyte();
            if (c == 0) c = 256;
            while (c > 0) { 
                render_byte(&col, &x1, &y1, b); 
                --c; 
            }
        }
        else 
            render_byte(&col, &x1, &y1, b);    /* Not RLE */
    }
}



static void 
md3_trans(void) {
    /* Convert MD3 file. MD3 are encoded as rows, and 
       there are three types. 
    */
    int x1, y1, col;
    mdbyte b;
    int c, d, n;

    for (y1 = 0; y1 < nInRows; ++y1) {
        b = getbyte();   /* Row type */
        switch(b)  {
        case 0: /* All the same byte */
            c = getbyte();
            for (n = 0; n < nInCols; n++) 
                mdrow[n] = c;
            break;
            
        case 1:      /* Encoded data */
        case 2: col = 0; /* Encoded as XOR with previous row */
            while (col < nInCols) {
                c = getbyte();
                if (c >= 129) {
                    /* RLE sequence */
                    c = 257 - c;
                    d = getbyte();
                    for (n = 0; n < c; ++n) {
                        if (b == 1) 
                            mdrow[col++] = d;
                        else 
                            mdrow[col++] ^= d;
                    }   
                } else {
                    /* not RLE sequence */
                        ++c;
                        for (n = 0; n < c; ++n) {
                            d = getbyte();
                            if (b == 1) 
                                mdrow[col++] = d;
                            else
                                mdrow[col++] ^= d;
                        }
                } 
            }
        }
        /* Row loaded. Convert it. */
        x1 = 0; col = 0; 
        for (n = 0; n < nInCols; ++n) {
            d  = y1;
            render_byte(&col, &x1, &d, mdrow[n]);
        }
    }
}



static void 
usage(char *s) {        
    printf("mdatopbm v1.00, Copyright (C) 1999 "
           "John Elliott <jce@seasip.demon.co.uk>\n"
           "This program is redistributable under the terms of "
           "the GNU General Public\n"
           "License, version 2 or later.\n\n"
           "Usage: %s [ -a ] [ -d ] [ -i ] [ -- ] [ infile ]\n\n"
           "-a: Output an ASCII pbm file\n"
           "-d: Double height (to compensate for the PCW aspect ratio)\n"
           "-i: Invert colors\n"
           "--: No more options (use if filename begins with a dash)\n",
           s);

    exit(0);
}



int 
main(int argc, char **argv) {
    int n, optstop = 0;
    char *fname = NULL;

    pbm_init(&argc, argv);

    /* Parse options */

    for (n = 1; n < argc; ++n) {
        if (argv[n][0] == '-' && !optstop) {   
            if (argv[n][1] == 'a' || argv[n][1] == 'A') bAscii = 1;
            if (argv[n][1] == 'd' || argv[n][1] == 'D') bScale = 1;
            if (argv[n][1] == 'i' || argv[n][1] == 'I') bInvert = 1;
            if (argv[n][1] == 'h' || argv[n][1] == 'H') usage(argv[0]);
            if (argv[n][1] == '-' && argv[n][2] == 0 && !fname) {
                /* "--" */
                optstop = 1;
            }
            if (argv[n][1] == '-' && (argv[n][2] == 'h' || argv[n][2] == 'H'))
                usage(argv[0]);
        }
        else if (argv[n][0] && !fname) {
            /* Filename */
            fname = argv[n];
        }
    }

    if (fname) 
        infile = pm_openr(fname);
    else
        infile = stdin;

    /* Read MDA file header */

    if (fread(header, 1, 128, infile) < 128)
        pm_error("Not a .MDA file\n");

    if (strncmp((char*) header, ".MDA", 4) && 
        strncmp((char*) header, ".MDP", 4))
        pm_error("Not a .MDA file\n");

    {
        short yy;
        pm_readlittleshort(infile, &yy); nInRows = yy;
        pm_readlittleshort(infile, &yy); nInCols = yy;
    }
    
    overflow2(nOutCols, 8);
    nOutCols = 8 * nInCols;
    nOutRows = nInRows;
    if (bScale) {
        overflow2(nOutRows, 2);
        nOutRows *= 2;
    }

    data = pbm_allocarray(nOutCols, nOutRows);
    
    MALLOCARRAY_NOFAIL(mdrow, nInCols);

    if (header[21] == '0') 
        md2_trans();
    else
        md3_trans();

    pbm_writepbm(stdout, data, nInCols*8, nOutRows, bAscii);

    if (infile != stdin) 
        pm_close(infile);
    fflush(stdout);
    pbm_freearray(data, nOutRows);
    free(mdrow);

    return 0;
}
