Minipix in 16 colors in Double Lo-res

Lo-Res Graphics Mode for the Apple II is such a coarse resolution that it is unsuitable for much more than a medium for very primitive graphics demos or similar programs. However, by doubling-up the horizontal resolution, a pretty decent venue for coloured versions of Minipix becomes possible. Since Double Lo-res is 80 pixels x 48 rasters and Minipix are 88 pixels x 52 rasters, this is close enough for the BMP2LO conversion utility to satisfactorily output these little graphics in glorious detail and 16 discrete colors. And since this utility works on Windows 16 color BMP files it is also easy enough to create original Double Lo-Res images of ones own design to be converted.

BMP2LO outputs a BSaved Image File pair which can be easily BLoaded in an AppleSoft BASIC program, and also outputs a raster oriented image which is more elegant, more suitable for a C language program outside BASIC's bog and mire, and which will not clobber the Apple II's screen holes like the BSaved Image File Pair.

For an example of an Apple II C program that will display both image formats, you can review the Aztec C Demo Program source code for LODELO.PRG. And for additional information including how these are created review the Microsoft C source code for BMP2LO.EXE (see below).


<< Back to Apple II Graphics

<< Back to Apple Oldies


/* ------------------------------------------------------------------------ */
/* BMP2LO.C (C) Copyright Bill Buckels 2009                                 */
/* All Rights Reserved.                                                     */
/*                                                                          */
/* Licence Agreement                                                        */
/* -----------------                                                        */
/*                                                                          */
/* You have a royalty-free right to use, modify, reproduce and              */
/* distribute this source code in any way you find useful,                  */
/* provided that you agree that Bill Buckels has no warranty obligations    */
/* or liability resulting from said distribution in any way whatsoever.     */
/* If you don't agree, remove this source code from your computer now.      */
/*                                                                          */
/* Written by   : Bill Buckels                                              */
/* Email:         bbuckels@escape.ca                                        */
/*                                                                          */
/* Purpose      : This utility will allow you to convert to                 */
/*                Apple II Double Lo-res 80 x 48 x 16 color images          */
/*                from IBM-PC graphics files in the following formats:      */
/*                                                                          */
/*                CGA 320 x 200 x 4 color BASIC BSAVED IMAGE (.BAS) Files   */
/*                CGA 320 x 200 x 4 color ZSOFT .PCX Files                  */
/*                EGA 320 x 200 x 16 color Windows .BMP Files               */
/*                                                                          */
/* Revision     : 1.0 First Release                                         */
/* ------------------------------------------------------------------------ */
/* Written in Large Model 16 bit Microsoft C (MSC) Version 8.00c            */
/* Note: Run in an MS-DOS emulator like DOSBox if you can't run it raw.     */
/* ------------------------------------------------------------------------ */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <dos.h>
#include <bios.h>
#include <io.h>
#include <malloc.h>
#include <conio.h>

/* ------------------------------------------------------------------------ */
/* Declarations, Vars. etc.                                                 */
/* ------------------------------------------------------------------------ */

typedef unsigned char     uchar;
typedef unsigned int      uint;
typedef unsigned long     ulong;

uchar *szTitle[]= {
"                                   ",
" BMP2LO(C)                         ",
" Copyright Bill Buckels 2009       ",
" All Rights Reserved.              ",
" Distributed as FreeWare.          ",
" Email: bbuckels@escape.ca         ",
"                                   ",
" Press Any Key To Continue...      ",
" --------------------------------- ",
" To Position Clip Box - Arrow Keys ",
" Page Up, Page Down, Home, and End ",
"                                   ",
" 'S' or [ENTER] to  Save           ",
" 'Q' or [ESC] to Quit              ",
" 'H' For Help                      ",
"                                   ",
" 0-9 Toggle Color (Numeric Keys)   ",
" A-F Toggle Color (Alpha Keys)     ",
"                                   ",
NULL};

uchar *szTextTitle =
    "BMP2LO(C) Version 1.0 Copyright Bill Buckels 2009\n"
    "All Rights Reserved.";

#define TRUE     1
#define FALSE    0
#define SUCCESS  0
#define VALID    SUCCESS
#define FAILURE  -1
#define INVALID  FAILURE

#define MCGA '\x13'
#define TEXT '\x03'

#define ASCIIZ     '\x00'
#define CRETURN    '\r'
#define LFEED      '\n'

#define ENTERKEY   '\x0d' /* character generated by the Enter Key          */
#define ESCKEY     '\x1b' /* character generated by the Esc key            */
#define FUNCKEY    '\x00' /* first character generated by function keys    */
#define UPARROW    'H'    /* second character generated by up-arrow key    */
#define DOWNARROW  'P'    /* second character generated by down-arrow key  */
#define LTARROW    'K'    /* second character generated by left-arrow key  */
#define RTARROW    'M'    /* second character generated by right-arrow key */
#define PGUP       'I'    /* second character generated by page up key     */
#define PGDOWN     'Q'    /* second character generated by page down key   */
#define HOMEKEY    'G'    /* second character generated by home key        */
#define ENDKEY     'O'    /* second character generated by end key         */

/* second character generated by numerical fkeys */
/* starting at character 59                      */
/* ending at character 68                        */
#define F1         ';'
#define F2         '<'
#define F3         '='
#define F4         '>'
#define F5         '?'
#define F6         '@'
#define F7         'A'
#define F8         'B'
#define F9         'C'
#define F10        'D'

#define FRAMESIZE ((unsigned)64000)
#define FRAMEADDR ((uchar *)0xa0000000l)

/* middle of screen */
#define XMIN 0
#define YMIN 0
#define XMOS 159
#define YMOS 99
#define XMAX 319
#define YMAX 199

#define SCREENWIDTH  (XMAX + 1)
#define RASTERWIDTH  SCREENWIDTH
#define SCREENHEIGHT (YMAX + 1)
#define CELL_SIZE    8


// some "helpful" macros

#define vload() memcpy(FRAMEADDR,(uchar *)&rawbuffer[0],FRAMESIZE)
#define vsave() memcpy((uchar *)&rawbuffer[0],FRAMEADDR,FRAMESIZE)
#define zap(x) memset(FRAMEADDR,x,FRAMESIZE)
#define getpixel(x,y) rawbuffer[(y*RASTERWIDTH)+x]


// function prototypes

uchar GetMcgaPaletteIndex(uint cgacolor),
      lsb(uint),
      msb(uint),
      SetCrtMode(uchar);

uint byteword(uchar, uchar);

int BMP16_Read(uchar *),
    BSAVE_Read(uchar *),
    CheckForCGAPCX(uchar *),
    EatKeys(),
    GetVGAIndex(uchar, uchar, uchar),
    LoadPalette(void),
    MakeBMP(uchar *, uchar *),
    PCX_Read(uchar *),
    savelofragment(uchar *, int, int);

void LineBox(int, int, int, int, uint),
     PCMidFont(uchar *, int, int, int, int, int),
     PCRomFont(uchar *, int, int, int, int, int),
     PutPixel(int, int, uint, uchar *),
     setlopixel(uchar,int, int, int),
     SetPalette(),
     ShowTitle(void),
     TogglePalette(uchar),
     XBox(int, int, int, int),
     XPixel(int, int, uchar *);

uchar outlinecolor;
uchar drawcolor;
uchar *rawbuffer;


#define NUM_MCGA_COLORS    256
#define NUM_RGB_COLORS     3

uchar rgbinfo[NUM_MCGA_COLORS][NUM_RGB_COLORS];  /* the vga palette */

enum {  BLACK = 0,
        BLUE,
        GREEN,
        CYAN,
        RED,
        MAGENTA,
        BROWN,
        WHITE,
        GRAY,
        LBLUE,
        LGREEN,
        LCYAN,
        LRED,
        LMAGENTA,
        YELLOW,
        BWHITE,
        NUM_VGA_COLORS};


/* the following 4 palettes are used to troll
   for standard colors in the order given below */

/* 16 Color BMP style palette (probably PBRUSH from Windows 3.1) */
unsigned char rgbBmpArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
0x00, 0x00, 0x00,    // BLACK	 
0x00, 0x00, 0xBF,    // BLUE		 
0x00, 0xBF, 0x00,    // GREEN	 
0x00, 0xBF, 0xBF,    // CYAN		 
0xBF, 0x00, 0x00,    // RED		 
0xBF, 0x00, 0xBF,    // MAGENTA	 
0xBF, 0xBF, 0x00,    // BROWN	 
0xC0, 0xC0, 0xC0,    // WHITE	 
0x80, 0x80, 0x80,    // GRAY		 
0x00, 0x00, 0xFF,    // LBLUE	 
0x00, 0xFF, 0x00,    // LGREEN	 
0x00, 0xFF, 0xFF,    // LCYAN	 
0xFF, 0x00, 0x00,    // LRED		 
0xFF, 0x00, 0xFF,    // LMAGENTA	 
0xFF, 0xFF, 0x00,    // YELLOW	 
0xFF, 0xFF, 0xFF};   // BWHITE	 

/* 16 Color BMP style palette (Windows XP) */
/* notice that these johnny-come-latelies reversed GRAY and WHITE */
unsigned char rgbXmpArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
0x00, 0x00, 0x00,    // BLACK	 
0x00, 0x00, 0x80,    // BLUE		 
0x00, 0x80, 0x00,    // GREEN	 
0x00, 0x80, 0x80,    // CYAN		 
0x80, 0x00, 0x00,    // RED		 
0x80, 0x00, 0x80,    // MAGENTA	 
0x80, 0x80, 0x00,    // BROWN	 
0x80, 0x80, 0x80,    // GRAY		 
0xC0, 0xC0, 0xC0,    // WHITE	 
0x00, 0x00, 0xFF,    // LBLUE	 
0x00, 0xFF, 0x00,    // LGREEN	 
0x00, 0xFF, 0xFF,    // LCYAN	 
0xFF, 0x00, 0x00,    // LRED		 
0xFF, 0x00, 0xFF,    // LMAGENTA	 
0xFF, 0xFF, 0x00,    // YELLOW	 
0xFF, 0xFF, 0xFF};   // BWHITE	 

// VGA Palette
unsigned char rgbVgaArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
0x00, 0x00, 0x00,            /* BLACK    */
0x00, 0x00, 0xFF,            /* BLUE     */
0x00, 0xFF, 0x00,            /* GREEN    */
0x00, 0xFF, 0xFF,            /* CYAN     */
0xFF, 0x00, 0x00,            /* RED      */
0xFF, 0x00, 0xFF,            /* MAGENTA  */
0xFF, 0xFF, 0x00,            /* BROWN    */
0xC0, 0xC0, 0xC0,            /* WHITE    */
0x55, 0x55, 0x55,            /* GRAY     */
0x55, 0x55, 0xFF,            /* LBLUE    */
0x55, 0xFF, 0x55,            /* LGREEN   */
0x55, 0xFF, 0xFF,            /* LCYAN    */
0xFF, 0x55, 0x55,            /* LRED     */
0xFF, 0x55, 0xFF,            /* LMAGENTA */
0xFF, 0xFF, 0x55,            /* YELLOW   */
0xFF, 0xFF, 0xFF};           /* BWHITE   */

// 16 color ZSOFT PCPAINT PCX style palette
unsigned char rgbPcxArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
0x00, 0x00, 0x00,            /* BLACK    */
0x00, 0x00, 0xAA,            /* BLUE     */
0x00, 0xAA, 0x00,            /* GREEN    */
0x00, 0xAA, 0xAA,            /* CYAN     */
0xAA, 0x00, 0x00,            /* RED      */
0xAA, 0x00, 0xAA,            /* MAGENTA  */
0xAA, 0xAA, 0x00,            /* BROWN    */
0xAA, 0xAA, 0xAA,            /* WHITE    */
0x55, 0x55, 0x55,            /* GRAY     */
0x55, 0x55, 0xFF,            /* LBLUE    */
0x55, 0xFF, 0x55,            /* LGREEN   */
0x55, 0xFF, 0xFF,            /* LCYAN    */
0xFF, 0x55, 0x55,            /* LRED     */
0xFF, 0x55, 0xFF,            /* LMAGENTA */
0xFF, 0xFF, 0x55,            /* YELLOW   */
0xFF, 0xFF, 0xFF};           /* BWHITE   */


/* this palette is in the actual apple II lores color order */
/* this shows us a[[roximately what we will end-up with */
uchar rgbLoresArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
  0,   0,   0,  /* black    */
227,  30,  96,  /* red      */
 96,  78, 189,  /* dk blue  */
255,  68, 253,  /* purple   */
  0, 163,  96,  /* dk green */
156, 156, 156,  /* gray     */
 20, 207, 253,  /* med blue */
208, 195, 255,  /* lt blue  */
 96, 114,   3,  /* brown    */
255, 106,  60,  /* orange   */
156, 156, 156,  /* grey     */
255, 160, 208,  /* pink     */
 20, 245,  60,  /* lt green */
208, 221, 141,  /* yellow   */
114, 255, 208,  /* aqua     */
255, 255, 255}; /* white    */

#define LOBLACK     0
#define LORED       1
#define LODKBLUE    2
#define LOPURPLE    3
#define LODKGREEN   4
#define LOGRAY      5
#define LOMEDBLUE   6
#define LOLTBLUE    7
#define LOBROWN     8
#define LOORANGE    9
#define LOGREY      10
#define LOPINK      11
#define LOLTGREEN   12
#define LOYELLOW    13
#define LOAQUA      14
#define LOWHITE     15

unsigned char VGAtoApple[NUM_VGA_COLORS]={
    LOBLACK,
    LODKBLUE,
    LODKGREEN,
    LOMEDBLUE,
    LORED,
    LOPURPLE,
    LOBROWN,
    LOWHITE,
    LOGRAY,
    LOLTBLUE,
    LOLTGREEN,
    LOAQUA,
    LOORANGE,
    LOPINK,
    LOYELLOW,
    LOWHITE};

// to rollback the colors in case we can't automatically assign
unsigned char VGAtoAppleSaved[NUM_VGA_COLORS]={
    LOBLACK,
    LODKBLUE,
    LODKGREEN,
    LOMEDBLUE,
    LORED,
    LOPURPLE,
    LOBROWN,
    LOWHITE,
    LOGRAY,
    LOLTBLUE,
    LOLTGREEN,
    LOAQUA,
    LOORANGE,
    LOPINK,
    LOYELLOW,
    LOWHITE};


/* our working copy of the apple II lores colors */
/* this is in Apple II order */
uchar rgbArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
  0,   0,   0,  /* black    */
227,  30,  96,  /* red      */
 96,  78, 189,  /* dk blue  */
255,  68, 253,  /* purple   */
  0, 163,  96,  /* dk green */
156, 156, 156,  /* gray     */
 20, 207, 253,  /* med blue */
208, 195, 255,  /* lt blue  */
 96, 114,   3,  /* brown    */
255, 106,  60,  /* orange   */
156, 156, 156,  /* grey     */
255, 160, 208,  /* pink     */
 20, 245,  60,  /* lt green */
208, 221, 141,  /* yellow   */
114, 255, 208,  /* aqua     */
255, 255, 255}; /* white    */


/* 16 Color VGA style palettes with RGB values
   from corresponding Apple II lores colors */
unsigned char rgbOriginalArray[NUM_VGA_COLORS][NUM_RGB_COLORS]={
  0,   0,   0,    // BLACK    - black		 
 96,  78, 189,    // BLUE     - dk blue		 
  0, 163,  96,    // GREEN    - dk green		 
 20, 207, 253,    // CYAN     - med blue		 
227,  30,  96,    // RED      - red			 
255,  68, 253,    // MAGENTA  - purple		 
 96, 114,   3,    // BROWN    - brown		 
255, 255, 255,    // WHITE    - white		 
156, 156, 156,    // GRAY     - gray or grey	 
208, 195, 255,    // LBLUE    - lt blue		 
 20, 245,  60,    // LGREEN   - lt green		 
114, 255, 208,    // LCYAN    - aqua			 
255, 106,  60,    // LRED     - orange		 
255, 160, 208,    // LMAGENTA - pink			 
208, 221, 141,    // YELLOW   - yellow		 
255, 255, 255};   // BWHITE   - white		 

enum {  CGA_BLACK = 0,
        CGA_CYAN,
        CGA_MAGENTA,
        CGA_WHITE,
        NUM_CGA_COLORS};

/* a microsoft compatible bsaved image format descriptor */
uchar BSAVED_header[7]={
    '\xfd','\x00','\xb8','\x00','\x00','\x00','\x40'};


/* bmiHeader.biClrUsed and bmiHeader.biClrImportant fields
   will vary depending on the process that created the bmp
   but the other fields (below) should be invariant. */
uchar BMP_checkheader[] ={
0x42, 0x4D, 0x76, 0x7D, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0xC8, 0x00,
0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};


/* ------------------------------------------------------------------------ */
/* Low Level Video Routines, drawing routines, etc.                         */
/* ------------------------------------------------------------------------ */

uchar SetCrtMode(uchar vidmode)
{
  union REGS inregs, outregs;

  /* set mode */
  inregs.h.ah = 0;
  inregs.h.al = vidmode;
  int86(0x10, &inregs, &outregs);

  /* get mode */
  inregs.h.ah = 0xf;
  int86(0x10, &inregs, &outregs);

  /* return mode */
  return outregs.h.al;

}

int LoadPalette()
{
  union REGS regs;
  struct SREGS segregs;

  regs.h.ah = 0x10;                    /* function 10h */
  regs.h.al = 0x12;
  /* subfunction 12h - set block of color registers */
  regs.x.bx = 0;                       /* start with this reg */
  regs.x.cx = NUM_MCGA_COLORS;         /* do this many */
  regs.x.dx = (uint)rgbinfo;   /* offset to array */
  segregs.es = (uint)((long)rgbinfo >> 16);
  /* segment of array */
  int86x(0x10, ®s, ®s, &segregs);/* dump data to color registers */

  return SUCCESS;

}



/* ---------------------------------------------------------------------- */
/* Write a pixel at x,y using color                                       */
/* ---------------------------------------------------------------------- */
void PutPixel(int x, int y,uint pixelvalue, uchar *framebuffer)
{
    if (x<XMIN || x>XMAX || y<YMIN || y>YMAX)
      return;

    framebuffer[(y*RASTERWIDTH)+x]=(uchar)pixelvalue;
}


void XPixel(int x, int y, uchar *framebuffer)
{

    if (x<XMIN || x>XMAX || y<YMIN || y>YMAX)
      return;

    framebuffer[(y*RASTERWIDTH)+x] = framebuffer[(y*RASTERWIDTH)+x]^0xff;
}


void XBox(int x1, int y1, int x2, int y2)
{
   int x, y;

   for (x = x1; x <= x2; x++)XPixel(x, y1, FRAMEADDR);
   for (y = (y1+1); y < y2; y++) {
     XPixel(x1, y, FRAMEADDR);
     XPixel(x2, y, FRAMEADDR);
   }
   for (x = x1; x <= x2; x++)XPixel(x, y2, FRAMEADDR);
   return;
}


void LineBox(int x1, int y1, int x2, int y2, uint pixelvalue)
{
   int x, y;

   for (x = x1; x <= x2; x++)PutPixel(x, y1, pixelvalue, FRAMEADDR);
   for (y = (y1+1); y < y2; y++) {
     PutPixel(x1, y, pixelvalue, FRAMEADDR);
     PutPixel(x2, y, pixelvalue, FRAMEADDR);
   }
   for (x = x1; x <= x2; x++)PutPixel(x, y2, pixelvalue, FRAMEADDR);
   return;
}

/* ---------------------------------------------------------------------- */
/* PCRomFont                                                              */
/* Uses the rom font as a template for a bitmap font.                     */
/* This allows us to map a font using scaling techniques on any PC        */
/* without the need for an external font file or a bios supported font.   */
/* This was more prevalent in days gone by than it is today, of course.   */
/* ---------------------------------------------------------------------- */
void PCRomFont(uchar *str,
               int xorigin, int yorigin, int scale,
               int fontcolor, int outlinecolor)
{
    int scanline,
        yreg=yorigin,
        xreg=xorigin,
        byt,
        character,
        nibble,
        color;
    int x,y;

    /* flags etcetera */

    uchar *romfont=(uchar *) 0xffa6000el;
    /* a pointer to the 8 x 8 BITMAPPED font in the PC ROM */

    int target = strlen(str);  /* string length */

    for (scanline=0;scanline<CELL_SIZE;scanline++)/* finish the current scanline*/
    {                                     /*  before advancing to the next*/
      for (byt=0;byt<target;byt++)     /* run the scanline*/
      {
        /* get the bitmap  */
        character = romfont[(str[byt]&0x7f)*CELL_SIZE+scanline];
        for (nibble=0;nibble<CELL_SIZE;nibble++)
        {
          xreg+=scale;
          /* chew the byte to bits and lite the pixel if it's a swallow  */
          if (str[byt]!=CRETURN && str[byt]!=LFEED) {
            if (character & 0x80>>nibble)
              color = fontcolor;
            else
              color = outlinecolor;
            if (color > -1 ) {
              for (x=0; x!=scale; x++)
                for (y=0;y!=scale;y++)
                  PutPixel(xreg+x,yreg+y,color, FRAMEADDR);
            }
          }
        }
      }
      yreg+=scale;
      xreg=xorigin;
    }

}

/* ---------------------------------------------------------------------- */
/* PCMidFont                                                              */
/* Maps to PCRomfont, uses a centre-justified x-coordinate.               */
/* ---------------------------------------------------------------------- */
void PCMidFont(uchar *str, int xmiddle, int yorigin,
               int scale, int fontcolor, int outlinecolor)
{   /* centre justified string */
    PCRomFont(str,(xmiddle-(4*(strlen(str))*scale)),yorigin,scale,
              fontcolor, outlinecolor);
}

/* ---------------------------------------------------------------------- */
/* GetMcgaPalette is called during the loading of the input file.         */
/* ---------------------------------------------------------------------- */
uchar GetMcgaPaletteIndex(uint cgacolor)
{

  // I just hardcoded these in here for CGA images.
  // I couldn't see any point in getting too gancy with 4 colors
  // There isn't much hardship in toggling 4 colors anyway
  // My main target for automation was when a 16 color BMP is colored
  // in Windows Paint... then exported in this thing.
  uint uiIndex;

  switch(cgacolor) {
    case  CGA_WHITE:
          uiIndex = LOWHITE; break;
    case  CGA_MAGENTA:
          uiIndex = LORED; break;
    case  CGA_CYAN:
          uiIndex = LODKBLUE; break;
    case  CGA_BLACK:
    default:
          uiIndex = LOBLACK; break;
  }
  return (uchar)uiIndex;
}


void SetPalette()
{

  uint idx, x, temp;

    memset((uchar *)&rgbinfo[0][0], 0, (NUM_MCGA_COLORS*NUM_RGB_COLORS));

    /* Set the lightest and darkest color in the current palette.  */
    /* use the darkest color for the drawcolor                     */
    /* use the lightest color for the outline color                */

    drawcolor    = 254;
    outlinecolor = 255;

    // using 6 bit color model (VGA standard video uses 6 bits)
    // for the internal program. values are in the range 0-63
    rgbinfo[255][0] = rgbinfo[255][1] =  rgbinfo[255][2] = 63;

    // leave the drawcolor and outline colors alone
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        for (x = 0; x < NUM_RGB_COLORS; x++) {
           temp = rgbArray[idx][x];
           rgbinfo[idx][x] = temp >> 2; // downshift to 6 bits of color
        }
    }

}


/* the following attempts to match BMP colors with a common palette */
/* that is to say... a basic default EGA style 16 color palette like */
/* the kind that Windows Paint provides as a lowest common denominator */
/* my rationale here is that if we can't automate the entire remapping */
/* we fail the whole sh*terree because otherwise we could start mashing */
/* two colors or more together and I for one would not care for that... */
int GetVGAIndex(uchar red, uchar green, uchar blue)
{

    int idx;

    // try exact match
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbBmpArray[idx][0] && green == rgbBmpArray[idx][1] && blue == rgbBmpArray[idx][2])return idx;
    }
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbXmpArray[idx][0] && green == rgbXmpArray[idx][1] && blue == rgbXmpArray[idx][2])return idx;
    }
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbVgaArray[idx][0] && green == rgbVgaArray[idx][1] && blue == rgbVgaArray[idx][2])return idx;
    }
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbPcxArray[idx][0] && green == rgbPcxArray[idx][1] && blue == rgbPcxArray[idx][2])return idx;
    }

    // adjust gun values to match using EGA-like thresholds
    // this corresponds to the old PCX style palette
    if (red < 43) red = 0;         // 0x00
    else if (red < 128) red = 85;  // 0x55
    else if (red < 224) red = 170; // 0xaa
    else red = 255;                // 0xff

    if (green < 43) green = 0;
    else if (green < 128) green = 85;
    else if (green < 213) green = 170;
    else green = 255;

    if (blue < 43) blue = 0;
    else if (blue < 128) blue = 85;
    else if (blue < 213) blue = 170;
    else blue = 255;

    // try again
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbBmpArray[idx][0] && green == rgbBmpArray[idx][1] && blue == rgbBmpArray[idx][2])return idx;
    }
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbXmpArray[idx][0] && green == rgbXmpArray[idx][1] && blue == rgbXmpArray[idx][2])return idx;
    }
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbVgaArray[idx][0] && green == rgbVgaArray[idx][1] && blue == rgbVgaArray[idx][2])return idx;
    }
    for (idx = 0; idx < NUM_VGA_COLORS; idx++) {
        if (red == rgbPcxArray[idx][0] && green == rgbPcxArray[idx][1] && blue == rgbPcxArray[idx][2])return idx;
    }
    // if no match yet let them pick lores color manually
    return INVALID;
}


int PaletteIndex[NUM_VGA_COLORS] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

void TogglePalette(uchar ch)
{
    /* after the initial remapping of colors by color order */
    /* it is very likely that not all entries will remap correctly */
    /* so the palette can be toggled to adjust this */
    int idx, jdx;

    ch = toupper(ch);

    switch(ch)
    {
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
          idx = ch - 55;
          break;

        default:

            if (ch < '0' || ch > '9')return;
            idx = ch - 48;
    }

    /* update remapping index */
    jdx = PaletteIndex[idx] + 1;
    if (jdx > 15)jdx = 0;
    PaletteIndex[idx] = jdx;
    /* update the working array */
    rgbArray[idx][0] = rgbLoresArray[jdx][0];
    rgbArray[idx][1] = rgbLoresArray[jdx][1];
    rgbArray[idx][2] = rgbLoresArray[jdx][2];
    /* update the visual */
    SetPalette();
    LoadPalette();

}


/* ---------------------------------------------------------------------- */
/* File Related Functions                                                 */
/* Image Loaders, Image Savers, etc.                                      */
/* ---------------------------------------------------------------------- */

/* type conversion functions */
uint byteword(uchar a, uchar b){
  return b << 8 | a;
}
uchar lsb(uint word){
  return word &0xff;
}
uchar msb(uint word){
  return word >> 8;
}

int CheckForCGAPCX(uchar *name)
{
  FILE *fp;
  /* reads a ZSOFT .PCX header but ignores the color map */
  int i;
  /* we only want CGA COLOR compatible full screens. */

  uchar pcxheader[128];
  uint zsoft,version,codetype,pixbits;
  uint xmin, ymin, xmax, ymax;
  uint x, y;
  uint no_planes, bytesperline;
  int status = VALID;

  /* read the file header */
  if((fp = fopen(name, "rb")) == NULL)return INVALID;
  for(i = 0;i < 128;i++)pcxheader[i] = fgetc(fp);
  fclose(fp);

  zsoft = pcxheader[0];
  version = pcxheader[1];
  codetype = pcxheader[2];
  pixbits = pcxheader[3];

  if(zsoft != 10)
    status = INVALID;
  if(codetype != 1)
    status = INVALID;
  if(pixbits != 2)        /* accept only CGA color images */
    status = INVALID;     /* monochrome images can't be mapped properly */

  xmin = byteword(pcxheader[4], pcxheader[5]);
  ymin = byteword(pcxheader[6], pcxheader[7]);
  xmax = byteword(pcxheader[8], pcxheader[9]);
  ymax = byteword(pcxheader[10], pcxheader[11]);

  no_planes = pcxheader[65];
  bytesperline = byteword(pcxheader[66], pcxheader[67]);

  x =  xmax - xmin;
  y =  ymax - ymin;

  if(x != XMAX)status = INVALID;
  if(y != YMAX)status = INVALID;
  if(no_planes != 1)status = INVALID;
  if(bytesperline != 80)status = INVALID;    /* full screens only */

  /* we can ignore the color map since we        */
  /* are limiting ourselves to CGA modes         */
  /* so we will not handle over 2-bits per pixel */

  return status;

}

int BSAVE_Read(uchar *name)
{
  int fh,fh2,y,i;
  uchar byte;
  uchar *crt;
  uchar namebuf[128];
  uchar headbuf[7];
  uchar linebuffer[80];
  long target = 16384l,target2,header = 7;

  /* open the file twice,eliminate the interleaf,read contiguous */
  sprintf(namebuf, "%s.BAS", name);

  if((fh = open(namebuf, O_RDONLY | O_BINARY)) == - 1)return INVALID;

  if((fh2 = open(namebuf, O_RDONLY | O_BINARY)) == - 1) {
    close(fh);
    return INVALID;
  }

  read(fh, headbuf, sizeof(BSAVED_header));

  /* only read the first 3 bytes, some of these are a little */
  /* different size, depending on how they were saved.       */

  for(i = 0;i < 3;i++)
    if(headbuf[i] != BSAVED_header[i]) {
      close(fh);
      close(fh2);
      return INVALID;
    }

  target2 = (target / 2) + header;
  lseek(fh, (long)(header), SEEK_SET)  ;
  lseek(fh2, (long)(target2), SEEK_SET);

  crt = (uchar *)&rawbuffer[0];

  /* translate from a 2 bit color pixel to an 8 bit color pixel */

  for(y = 0;y < SCREENHEIGHT;y += 2) {
    read(fh, linebuffer, 80);
    for(i = 0;i < 80;i++) {
      byte = linebuffer[i];
      *crt++ = GetMcgaPaletteIndex((byte >> 6));
      *crt++ = GetMcgaPaletteIndex((byte >> 4)&3);
      *crt++ = GetMcgaPaletteIndex((byte >> 2)&3);
      *crt++ = GetMcgaPaletteIndex((byte)&3);
    }
    read(fh2, linebuffer, 80);
    for(i = 0;i < 80;i++) {
      byte = linebuffer[i];
      *crt++ = GetMcgaPaletteIndex((byte >> 6));
      *crt++ = GetMcgaPaletteIndex((byte >> 4)&3);
      *crt++ = GetMcgaPaletteIndex((byte >> 2)&3);
      *crt++ = GetMcgaPaletteIndex((byte)&3);
    }
  }
  close(fh);
  close(fh2);
  return SUCCESS;

}

/* Translation for VGA to display CGA */
int PCX_Read(uchar *pcxfilename)
{
  uchar pcxheader[128];
  uchar *crt;
  uint packet;
  uchar bit[4];
  FILE *fp;
  uchar byte,bytecount;
  long wordcount,target;
  uchar name1[128], name2[128];

  sprintf(name1, "%s.PCX", pcxfilename);

  if(CheckForCGAPCX(name1) == - 1)return INVALID;

  if (NULL == (fp = fopen(name1, "rb")))return INVALID;

  target = filelength(fileno(fp));
  for(wordcount = 0;wordcount != 128;wordcount++)byte = fgetc(fp);

  crt = (uchar *)&rawbuffer[0];

  do {
    bytecount = 1;                     /* start with a seed count */
    byte = fgetc(fp);
    wordcount++;
    /* check to see if its raw */
    if(0xC0 == (0xC0 &byte)) {
      /* if its not, run encoded */
      bytecount = 0x3f &byte;
      byte = fgetc(fp);
      wordcount++;
    }
    /* translate from 2 bit pixel to 8 bit pixel */

    bit[0] = GetMcgaPaletteIndex((byte >> 6));
    bit[1] = GetMcgaPaletteIndex((byte >> 4)&3);
    bit[2] = GetMcgaPaletteIndex((byte >> 2)&3);
    bit[3] = GetMcgaPaletteIndex((byte)&3);
    packet = 0;
    while(packet++ < bytecount) {
      *crt++ = bit[0];
      *crt++ = bit[1];
      *crt++ = bit[2];
      *crt++ = bit[3];
    }
  }while(wordcount < target);
  fclose(fp);
  return(0);
}



int BMP16_Read(uchar *basename)
{

    uint temp, temp2;
    uchar *screenbuffer, bmpfile[128];
    int x, y;
    FILE *fp;

    sprintf(bmpfile,"%s.BMP",basename);
    fp=fopen(bmpfile,"rb");

    if (NULL == fp) return INVALID;

    /* check the invariant fields in the bmp header */
    for (x=0; x < sizeof(BMP_checkheader); x++) {
      temp2 = fgetc(fp);
      temp=BMP_checkheader[x];
      if (temp != temp2) {
        fclose(fp);
        return INVALID;

      }
    }

    /* ignore variant fields in header - 8 bytes */
    /* bmiHeader.biClrUsed and bmiHeader.biClrImportant */
    for (x=0; x < 8; x++)fgetc(fp);

    for(y=0;y<NUM_VGA_COLORS;y++)
    {
      /* RGB Quad Structure b,g,r,0 */
      for(x=3;x>0;x--)
      {
        temp = fgetc(fp);
        rgbOriginalArray[y][x-1] = temp;

      }
      fgetc(fp);
    }

    for(y=0;y<NUM_VGA_COLORS;y++) {
        // attempt to reorganize remapping of the BMP
        // to the apple lores palette based on rgb values
        x = GetVgaIndex(rgbOriginalArray[y][0],
                        rgbOriginalArray[y][1],
                        rgbOriginalArray[y][2]);
        // however, if all colors do not match then
        // we simply use the default color order
        // and let them manually select the apple lores color
        if (x == INVALID) {
            for (x = 0; x < NUM_VGA_COLORS; x++)
               VGAtoApple[x] = VGAtoAppleSaved[x];  // reset
            break;

        }
        /* move x to y for the remap of the image data */
        /* if they don't like it they can toggle the colors */
        VGAtoApple[y] = VGAtoAppleSaved[x];

    }

    /* remap the image to the equivalent apple2 lores colors */
    /* to the best known equivalents */
    for (y = SCREENHEIGHT; y > 0; y--) {
      screenbuffer=(uchar *)&rawbuffer[((y-1)*SCREENWIDTH)];
      for (x = 0; x < SCREENWIDTH; x++) {
        if (x % 2 == 0) {
          temp  = fgetc(fp);
          temp2 = (temp >> 4);
        }
        else {
          temp2 = (temp & 15);
        }

        screenbuffer[x] = VGAtoApple[temp2];

      }
    }

    fclose(fp);


    return SUCCESS;

}

/* routines to save to Apple 2 Lores Format */

/* base addresses for Apple II primary text page */
/* also the base addresses for the 48 scanline pairs */
/* for Apple II lores graphics mode 40 x 48 x 16 colors */
int textbase[24]={
    0x0400,
    0x0480,
    0x0500,
    0x0580,
    0x0600,
    0x0680,
    0x0700,
    0x0780,
    0x0428,
    0x04A8,
    0x0528,
    0x05A8,
    0x0628,
    0x06A8,
    0x0728,
    0x07A8,
    0x0450,
    0x04D0,
    0x0550,
    0x05D0,
    0x0650,
    0x06D0,
    0x0750,
    0x07D0};

#define RAGWIDTH  80
#define RAGHEIGHT 48
#define RAGSIZE   1920
#define BINSIZE   1016
unsigned char lobuf[RAGSIZE];

/* sets the pixels in the lores buffer (lobuf) */
void setlopixel(uchar color,int x, int y,int ragflag)
{
     unsigned char *crt, c1, c2;
     int y1, offset;

     y1 = y / 2;
     c2 = (unsigned char ) (color & 15);

     if (y%2 == 0) {
         /* even rows in low nibble */
         /* mask value to preserve high nibble */
         c1 = 240;
     }
     else {
         /* odd rows in high nibble */
         /* mask value to preserve low nibble */
         c1 = 15;
         c2 = c2 * 16;
     }

     if (ragflag)
         offset = (y1 * 80) + x;
     else
         offset = (textbase[y1]-1024)+x;

     crt = (unsigned char *)&lobuf[offset];
     crt[0] &= c1;
     crt[0] |= c2;
}





/* save 2 output files */
int savelofragment(uchar *basename, int x1, int y1)
{

    FILE *fp;
    uchar outfile[128], temp, remap;
    int x,y,x2,y2;

    sprintf(outfile,"%s.DLO",basename);
    fp = fopen(outfile,"wb");
    if (NULL == fp)return INVALID;


    // On the double lo res display each byte in
    // high memory is interleaved with a byte in low memory
    // in the interests of efficiency I am saving and loading
    // the interleaf on a scanline by scanline basis.
    memset(lobuf,0,RAGSIZE);
    for (y = 0; y< 48; y++) {
        y2 = y + y1;
        // first 40 bytes goes to auxilliary memory (even pixels)
        for (x = 0; x < 40; x++) {
            x2 = (x*2) + x1;
            temp = getpixel(x2,y2);
            remap =  PaletteIndex[temp];
            setlopixel(remap,x,y,1);
        }
        // followed by the interleaf (odd pixels)
        // next 40 bytes goes to main memory
        for (x = 0; x < 40; x++) {
            x2 = (x*2) + x1 + 1;
            temp = getpixel(x2,y2);
            remap =  PaletteIndex[temp];
            setlopixel(remap,x+40,y,1);
        }
    }
    fputc(80,fp); // bytes
    fputc(24,fp); // bytes (rasters / 2)
    fwrite(lobuf,1,RAGSIZE,fp);
    fclose(fp);


    // the bsaved images are split into two files
    // the first file is loaded into aux mem
    sprintf(outfile,"%s.DL1",basename);
    fp = fopen(outfile,"wb");
    if (NULL == fp)return INVALID;

    memset(lobuf,0,BINSIZE);
    for (y = 0; y< 48; y++) {
        y2 = y + y1;
        for (x = 0; x < 40; x++) {
            x2 = (x*2) + x1;
            temp = getpixel(x2,y2);
            remap =  PaletteIndex[temp];
            setlopixel(remap,x,y,0);
        }
    }
    fwrite(lobuf,1,BINSIZE,fp);
    fclose(fp);

    // the second file is loaded into main mem
    sprintf(outfile,"%s.DL2",basename);
    fp = fopen(outfile,"wb");
    if (NULL == fp)return INVALID;

    memset(lobuf,0,BINSIZE);
    for (y = 0; y< 48; y++) {
        y2 = y + y1;
        for (x = 0; x < 40; x++) {
            x2 = (x*2) + x1 + 1;
            temp = getpixel(x2,y2);
            remap =  PaletteIndex[temp];
            setlopixel(remap,x,y,0);
        }
    }
    fwrite(lobuf,1,BINSIZE,fp);
    fclose(fp);

    return SUCCESS;
}


/* ------------------------------------------------------------------------ */
/* User Input and helper functions and Main Program                         */
/* ------------------------------------------------------------------------ */

// eat keystrokes... avoid mindlessly cycling through the program
// if the user leans on the keyboard and doesn't budge for a moment.
// Any DOS program should do this...

int EatKeys()
{
  if (kbhit())
    while (kbhit())
      if (getch() == FUNCKEY)
         getch();

  return SUCCESS;
}

// show the title at startup and when 'H' (Help) is pressed.
void ShowTitle()
{
    int y, yorg, y1, x1, xx, c;
    uchar *ptr;

    for (y = 0; szTitle[y]!= NULL; y++);
    y+=2;


    yorg = y1 = ((SCREENHEIGHT - y*CELL_SIZE) / 2) - 1;
    for (y = 0; szTitle[y]!= NULL; y++) {
      ptr = (char *)&szTitle[y][0];
      PCMidFont(ptr, XMOS, y1, 1, drawcolor, outlinecolor);
      y1+=CELL_SIZE;
    }
    PCMidFont(ptr, XMOS, y1, 1, drawcolor, outlinecolor);
    PCMidFont(ptr, XMOS, y1+CELL_SIZE, 1, drawcolor, outlinecolor);

    // a little finishing touch for the help screen
    // to show the currently selected colors.
    xx = XMOS-136;
    c = 1;
    PCMidFont("C", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("u", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("r", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("r", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("e", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("n", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("t", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont(" ", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("C", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("o", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("l", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("o", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("r", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("s", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont(":", (xx+=8), y1, 1,  c,    drawcolor);
    PCMidFont(" ", (xx+=8), y1, 1,  c,    drawcolor);
    c = 0;
    PCMidFont("0", (xx+=8), y1, 1, (c++), outlinecolor);
    PCMidFont("1", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("2", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("3", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("4", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("5", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("6", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("7", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("8", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("9", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("A", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("B", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("C", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("D", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("E", (xx+=8), y1, 1, (c++), drawcolor);
    PCMidFont("F",  xx,     y1, 1,  c,    drawcolor);
    y1+=CELL_SIZE;
    y1+=CELL_SIZE;

    x1 = strlen(ptr) * 4;
    LineBox(XMOS - x1 + 2, yorg+1, XMOS + x1 - 1, y1-2, drawcolor);

    EatKeys();
    while (!kbhit());
    EatKeys();

}

void main(int argc, char **argv)
{
  int status = 0, idx, iMax;

  /* bounds of lores image */
  int oldx1=0, oldy1=0, oldx2=79, oldy2=47;
  int x1=0, y1=0, x2=79, y2=47;

  uchar c, kdx;
  uchar fname[128],sname[128],outfile[128];
  uchar *wordptr;
  uchar scratchbuf[128];
  FILE *fp;

  if(argc == 1) {
    puts(szTextTitle);
    puts("Command line Usage is \"BMP2LO MyCGA.PCX\"");
    puts("                      \"BMP2LO MyBSAVE.BAS\"");
    puts("                      \"BMP2LO My16Color.BMP\"");
    puts("                      \"BMP2LO MyCGA.PCX OutfileBaseName\"");
    puts("                      \"BMP2LO MyBSAVE.BAS OutfileBaseName\"");
    puts("                      \"BMP2LO My16Color.BMP OutfileBaseName\"");

    printf("Enter Input FileName (Blank to Exit): ");
    gets(fname);
    if (fname[0] == ASCIIZ)
      exit(1);
    printf("Enter Output FileBaseName (Blank for None) : ");
    gets(outfile);
  }
  else {
    strcpy(fname, argv[1]);
    if (argc > 2)
      strcpy(outfile, argv[2]);
    else
      outfile[0] = ASCIIZ;
  }

  if((rawbuffer = malloc((unsigned)65000)) == NULL) {
    puts(szTextTitle);
    puts("Out of Memory...");
    exit(1);
  }


  strcpy(sname, fname);
  wordptr = strtok(sname, ".");
  if (outfile[0] == ASCIIZ)strcpy(outfile,sname);

  status = PCX_Read(sname);
  if(status)status = BSAVE_Read(sname);
  if(status)status = BMP16_Read(sname);
  if (status) {
    puts(szTextTitle);
    printf("%s is an Unsupported Format or cannot be opened.\n", fname);
    free(rawbuffer);
    exit(1);
  }

  status = ESCKEY;

  if((SetCrtMode(MCGA)) == MCGA) {
    SetPalette();
    LoadPalette();
    vload();
    ShowTitle();
    vload();
    xbox(oldx1, oldy1, oldx2, oldy2);
    do {
      c=toupper(getch());

      if (FUNCKEY == c) {
        kdx=getch();
        switch(kdx) {
          case HOMEKEY:
               x1 = 0;
          case LTARROW:
               x1-=1;
               x2-=1;
               if (x1 < 0) {
                 x1 = 0;
                 x2 = 79;
               }
               break;
          case ENDKEY:
               x2=319;
          case RTARROW:
               x1+=4;
               x2+=4;
               if (x2 > 319) {
                 x2 = 319;
                 x1 = 319 - 79;
               }
               break;
          case PGUP:
               y1 = 0;
          case UPARROW:
               y1-=1;
               y2-=1;
               if (y1 < 0) {
                 y1 = 0;
                 y2 = 47;
               }
               break;
          case PGDOWN:
               y2 = 199;
          case DOWNARROW:
               y1+=4;
               y2+=4;
               if (y2 > 199) {
                  y2 = 199;
                  y1 = 199 - 47;
               }

               break;
          default:
            break;
        }
      }
      else {

        if (c == 'S' || c == ENTERKEY)
          break;

        switch (c) {
          case 'H':
            xbox(oldx1, oldy1, oldx2, oldy2);
            ShowTitle();
            vload();
            xbox(oldx1, oldy1, oldx2, oldy2);
            break;
          case 'Q':
            c = ESCKEY;
            break;
          default:
            if (c < '0' || c > 'F') break;
            if (c > '9' && c < 'A') break;
            TogglePalette(c);
        }
      }

      if (x1!=oldx1 || y1!=oldy1) {
         /* erase old box */
         xbox(oldx1, oldy1, oldx2, oldy2);
         /* update old cords with new cords */
         oldx1 = x1; oldy1 = y1; oldx2 = x2; oldy2 = y2;
         /* plot new box */
         xbox(oldx1, oldy1, oldx2, oldy2);
      }


    } while (c!=ESCKEY);

    if(c!=ESCKEY)
      status = savelofragment(outfile,x1,y1);
    else
      status = ESCKEY;

    SetCrtMode(TEXT);

  }

  if (status != ESCKEY) {
    if (status == SUCCESS)printf("%s.HL1 & .HL2 and %s.HLO Saved!\n",outfile,outfile);
    else printf("Error saving %s.HL1 & .HL2 and %s.HLO!\n",outfile,outfile);
  }
  else printf("Exiting without saving...\n");

  free(rawbuffer);
  puts("Have a Nice Dos!");
  exit(0);

}

<< Back to Apple II Graphics

<< Back to Apple Oldies


© Copyright Bill Buckels 2010
All Rights Reserved.