/***************************************************************************** VGA graphics demo Chris Giese http://my.execpc.com/~geezer Release date: ? This code is public domain (no copyright). You can do whatever you want with it. This code uses the BIOS to set graphics mode, and uses the BIOS font. Should compile cleanly with Turbo C++ 1.0, Turbo C++ 3.0, 16- or 32-bit Watcom C, or DJGPP. DJGPP version will not work in Windows NT/2000/XP DOS box. Some additional things you could do with this: - Write a function tblit1(), similar to blit1(), that uses an on-off transparency mask. Use this function to blit non-rectangular objects such as a mouse cursor. - Write blit_plane(): a fast function to blit from monochrome to monochrome or 4-plane bitmaps. Use an external shift() function, written in asm - Support VBE 1.x banked framebuffer - Support VBE 2.x linear framebuffer (pmode only, not at A000h:0000) - Support greater color depths: 15 bpp, 16 bpp, 24 bpp, 32 bpp - Color reduction, e.g. Heckbert (median-cut) algorithm - Clipping engine that lets you draw a window that is partially obscured by "closer" windows - Mouse, keyboard, and timer events - Widgets: push button, checkbox, radio buttons, listbox, dialog, etc. *****************************************************************************/ #include /* [_f]memset() */ /********************************* TURBO C **********************************/ #if defined(__TURBOC__) #include /* struct REGPACK, intr() */ /* The framebuffer is far outside the 16-bit data segment. The only way to make the framebuffer work like in-memory bitmaps is to use far pointers. We still use the SMALL memory model. */ #define FAR far #define FARPTR(S, O) MK_FP(S, O) #define outportw(P,V) outport(P,V) #define R_AX r_ax #define R_BX r_bx #define R_BP r_bp #define R_ES r_es #define trap(N,R) intr(N,R) typedef struct REGPACK regs_t; #if __TURBOC__<0x300 void vmemset(unsigned char FAR *s, unsigned c, unsigned n) { for(; n != 0; n--) { *s = c; s++; } } #else void vmemset(unsigned char FAR *s, unsigned c, unsigned n) { _fmemset(s, c, n); } #endif /********************************* DJGPP ************************************/ #elif defined(__DJGPP__) #include /* __dpmi_... */ #include /* inportb(), outportb() */ #define FAR /* nothing */ #define FARPTR(S, O) (unsigned char *)((S) * 16L + (O) + \ __djgpp_conventional_base) /* near pointers; not supported in Windows NT/2k/XP DOS box */ #include /* __djgpp_conventional_base, __djgpp_nearptr_enable() */ #include /* printf() */ #include /* _CRT0_FLAG_NEARPTR, _crt0_startup_flags */ #define R_AX x.ax #define R_BX x.bx #define R_BP x.bp #define R_ES x.es #define trap(N,R) __dpmi_int(N,R) typedef __dpmi_regs regs_t; void vmemset(unsigned char FAR *s, unsigned c, unsigned n) { memset(s, c, n); } /******************************** WATCOM C **********************************/ #elif defined(__WATCOMC__) #include /* union REGPACK, MK_FP(), intr() */ #if defined(__386__) #define FAR /* nothing */ #define FARPTR(S, O) (unsigned char *)((S) * 16L + (O)) void vmemset(unsigned char FAR *s, unsigned c, unsigned n) { memset(s, c, n); } #else #define FAR far #define FARPTR(S, O) MK_FP(S, O) void vmemset(unsigned char FAR *s, unsigned c, unsigned n) { _fmemset(s, c, n); } #endif #define inportb(P) inp(P) #define outportb(P,V) outp(P,V) #define outportw(P,V) outpw(P,V) #define R_AX w.ax #define R_BX w.bx #define R_BP w.bp #define R_ES w.es /* WARNING: for 32-bit code, unused fields of regs_t must be zeroed before using this macro */ #define trap(N,R) intr(N,R) typedef union REGPACK regs_t; #else #error Not Turbo C, not DJGPP, not Watcom C. Sorry. #endif #include /* getch() */ /* need direct access to some VGA registers to select plane, enable Mode X, and fix screwy CGA addressing */ #define VGA_SEQ_INDEX 0x3C4 #define VGA_SEQ_DATA 0x3C5 #define VGA_GC_INDEX 0x3CE #define VGA_GC_DATA 0x3CF #define VGA_CRTC_INDEX 0x3D4 #define VGA_CRTC_DATA 0x3D5 /* bitmap "class" */ typedef struct { unsigned wd, ht; unsigned char FAR *raster; unsigned fore_color, back_color; /* "member functions" */ const struct _driver *ops; } bmp_t; typedef struct _driver { /* "pure virtual functions": color drivers MUST implement these */ void (*write_pixel)(bmp_t *bmp, unsigned x, unsigned y, unsigned c); unsigned (*read_pixel)(bmp_t *bmp, unsigned x, unsigned y); /* "virtual functions": drivers MAY implement these, for speed fill rectangular area with solid color */ void (*fill_rect)(bmp_t *bmp, int x, int y, int wd, int ht); /* copy monochrome bitmap to this bitmap (used to display text) */ void (*blit1)(bmp_t *src, bmp_t *dst, unsigned dst_x, unsigned dst_y); /* copy all or part of one bitmap to another (both of the same depth) */ void (*blit)(bmp_t *src, bmp_t *dst, unsigned dst_x, unsigned dst_y); } ops_t; /*============================================================================ helper functions ============================================================================*/ /***************************************************************************** *****************************************************************************/ void set_plane(unsigned p) { static unsigned curr_p = -1u; /**/ unsigned char pmask; p &= 3; if(p == curr_p) return; curr_p = p; pmask = 1 << p; #if 0 outportb(VGA_GC_INDEX, 4); outportb(VGA_GC_DATA, p); outportb(VGA_SEQ_INDEX, 2); outportb(VGA_SEQ_DATA, pmask); #else /* this is a little faster... */ outportw(VGA_GC_INDEX, (p << 8) | 4); outportw(VGA_SEQ_INDEX, (pmask << 8) | 2); #endif } /***************************************************************************** fast planar (monochrome or 16-color) rectangle fill *****************************************************************************/ void fill_plane(bmp_t *bmp, int x, int y, int wd, int ht, unsigned c) { unsigned w, wd_in_bytes, off; unsigned char lmask, rmask; int x2, y2; x2 = x + wd - 1; w = (x2 >> 3) - (x >> 3) + 1; lmask = 0x00FF >> (x & 7); /* FF 7F 3F 1F 0F 07 03 01 */ rmask = 0xFF80 >> (x2 & 7);/* 80 C0 E0 F0 F8 FC FE FF */ if(w == 1) lmask &= rmask; wd_in_bytes = bmp->wd / 8; off = wd_in_bytes * y + x / 8; if(c) /* for each row... */ for(y2 = y; y2 < y + ht; y2++) { /* do partial byte on left */ bmp->raster[off] |= lmask; /* do solid bytes in middle */ if(w > 2) vmemset(bmp->raster + off + 1, 0xFF, w - 2); /* do partial byte on right */ if(w > 1) bmp->raster[off + w - 1] |= rmask; /* next row */ off += wd_in_bytes; } else { lmask = ~lmask; rmask = ~rmask; for(y2 = y; y2 < y + ht; y2++) { bmp->raster[off] &= lmask; if(w > 2) vmemset(bmp->raster + off + 1, 0, w - 2); if(w > 1) bmp->raster[off + w - 1] &= rmask; off += wd_in_bytes; } } } /***************************************************************************** fast planar blit *****************************************************************************/ void blit_plane(bmp_t *src, bmp_t *dst, unsigned dst_x, unsigned dst_y) { /* left as an exercise for the reader :) You may need an external, assembly-language function to shift (left or right) a long string of bytes. No need to shift by more than 7 bits. */ } /*============================================================================ driver for monochrome (1-bit) graphics ============================================================================*/ /***************************************************************************** *****************************************************************************/ static void write_pixel1(bmp_t *bmp, unsigned x, unsigned y, unsigned c) { unsigned wd_in_bytes; unsigned off, mask; c = (c & 1) * 0xFF; wd_in_bytes = bmp->wd / 8; off = wd_in_bytes * y + x / 8; x = (x & 7) * 1; mask = 0x80 >> x; bmp->raster[off] = (bmp->raster[off] & ~mask) | (c & mask); } /***************************************************************************** *****************************************************************************/ static unsigned read_pixel1(bmp_t *bmp, unsigned x, unsigned y) { unsigned wd_in_bytes; unsigned off, mask; wd_in_bytes = bmp->wd / 8; off = wd_in_bytes * y + x / 8; x = (x & 7) * 1; mask = 0x80 >> x; return (bmp->raster[off] & mask) != 0; } /***************************************************************************** *****************************************************************************/ static void fill_rect1(bmp_t *bmp, int x, int y, int wd, int ht) { fill_plane(bmp, x, y, wd, ht, bmp->fore_color & 1); } /***************************************************************************** *****************************************************************************/ const ops_t g_ops1 = { write_pixel1, read_pixel1, fill_rect1, NULL, /* blit1 */ NULL /* blit */ }; /*============================================================================ driver for 2-bit packed pixel (4-color CGA) graphics ============================================================================*/ /***************************************************************************** *****************************************************************************/ static void write_pixel2(bmp_t *bmp, unsigned x, unsigned y, unsigned c) { unsigned wd_in_bytes, off, mask; c = (c & 3) * 0x55; wd_in_bytes = bmp->wd / 4; off = wd_in_bytes * y + x / 4; x = (x & 3) * 2; mask = 0xC0 >> x; bmp->raster[off] = (bmp->raster[off] & ~mask) | (c & mask); } /***************************************************************************** *****************************************************************************/ const ops_t g_ops2 = { write_pixel2, NULL, /* read_pixel */ NULL, /* fill_rect */ NULL, /* blit1 */ NULL /* blit */ }; /*============================================================================ driver for 4-plane 16-color graphics ============================================================================*/ /***************************************************************************** *****************************************************************************/ static void write_pixel4p(bmp_t *bmp, unsigned x, unsigned y, unsigned c) { unsigned wd_in_bytes, off, mask, p, pmask; wd_in_bytes = bmp->wd / 8; off = wd_in_bytes * y + x / 8; x = (x & 7) * 1; mask = 0x80 >> x; pmask = 1; for(p = 0; p < 4; p++) { set_plane(p); if(pmask & c) bmp->raster[off] |= mask; else bmp->raster[off] &= ~mask; pmask <<= 1; } } /***************************************************************************** pixel-by-pixel fill is too slow, so use this optimized function: *****************************************************************************/ static void fill_rect4p(bmp_t *bmp, int x, int y, int wd, int ht) { unsigned char p, pmask; pmask = 1; for(p = 0; p < 4; p++) { set_plane(p); fill_plane(bmp, x, y, wd, ht, bmp->fore_color & pmask); pmask <<= 1; } } /***************************************************************************** *****************************************************************************/ const ops_t g_ops4p = { write_pixel4p, NULL, /* read_pixel */ fill_rect4p, NULL, /* blit1 */ NULL /* blit */ }; /*============================================================================ driver for 8-bit 256-color graphics ============================================================================*/ /***************************************************************************** *****************************************************************************/ static void write_pixel8(bmp_t *bmp, unsigned x, unsigned y, unsigned c) { unsigned wd_in_bytes; unsigned off; wd_in_bytes = bmp->wd; off = wd_in_bytes * y + x; bmp->raster[off] = c; } /***************************************************************************** *****************************************************************************/ static void fill_rect8(bmp_t *bmp, int x, int y, int wd, int ht) { unsigned wd_in_bytes, off, y2; wd_in_bytes = bmp->wd; off = wd_in_bytes * y + x; for(y2 = y; y2 < y + ht; y2++) { vmemset(bmp->raster + off, bmp->fore_color, wd); off += wd_in_bytes; } } /***************************************************************************** *****************************************************************************/ const ops_t g_ops8 = { write_pixel8, NULL, /* read_pixel */ fill_rect8, NULL, /* blit1 */ NULL /* blit */ }; /*============================================================================ driver for 8-bit 256-color Mode-X graphics ============================================================================*/ /***************************************************************************** *****************************************************************************/ static void write_pixel8x(bmp_t *bmp, unsigned x, unsigned y, unsigned c) { unsigned wd_in_bytes; unsigned off; wd_in_bytes = bmp->wd / 4; off = wd_in_bytes * y + x / 4; set_plane(x & 3); bmp->raster[off] = c; } /***************************************************************************** *****************************************************************************/ const ops_t g_ops8x = { write_pixel8x, NULL, /* read_pixel */ NULL, /* fill_rect */ NULL, /* blit1 */ NULL /* blit */ }; /*============================================================================ depth-independent routines, which call the depth-dependent routines ============================================================================*/ /***************************************************************************** *****************************************************************************/ unsigned read_pixel(bmp_t *bmp, unsigned x, unsigned y) { if(x >= bmp->wd || y >= bmp->ht) return 0; if(bmp->ops->read_pixel == NULL) return 0; /* uh-oh */ return bmp->ops->read_pixel(bmp, x, y); } /***************************************************************************** *****************************************************************************/ void write_pixel(bmp_t *bmp, unsigned x, unsigned y, unsigned c) { if(x >= bmp->wd || y >= bmp->ht) return; if(bmp->ops->write_pixel == NULL) return; /* uh-oh */ bmp->ops->write_pixel(bmp, x, y, c); } /***************************************************************************** *****************************************************************************/ void fill_rect(bmp_t *bmp, int x, int y, int wd, int ht) { int x2, y2; /* clip */ if(x < 0) { if(wd + x < 0) return; wd += x; x = 0; } if(x + wd >= (int)bmp->wd) { if(x >= (int)bmp->wd) return; wd = bmp->wd - x; } if(y < 0) { if(ht + y < 0) return; ht += y; y = 0; } if(y + ht >= (int)bmp->ht) { if(y >= (int)bmp->ht) return; ht = bmp->ht - y; } /* use fast routine if available */ if(bmp->ops->fill_rect != NULL) { bmp->ops->fill_rect(bmp, x, y, wd, ht); return; } for(y2 = y; y2 < y + ht; y2++) for(x2 = x; x2 < x + wd; x2++) write_pixel(bmp, x2, y2, bmp->fore_color); } /***************************************************************************** *****************************************************************************/ void hline(bmp_t *bmp, int x, int y, unsigned wd) { fill_rect(bmp, x, y, wd, 1); } /***************************************************************************** *****************************************************************************/ void vline(bmp_t *bmp, int x, int y, unsigned ht) { fill_rect(bmp, x, y, 1, ht); } /***************************************************************************** blit1 = blit from monochrome bitmap to bitmap of any color depth *****************************************************************************/ void blit1(bmp_t *src, bmp_t *dst, unsigned dst_x, unsigned dst_y) { unsigned x, y, c; /* source bitmap _must_ be monochrome */ if(src->ops != &g_ops1) return; /* use fast routine if available */ if(src->ops->blit1 != NULL) { src->ops->blit1(src, dst, dst_x, dst_y); return; } for(y = 0; y < src->ht; y++) for(x = 0; x < src->wd; x++) { c = read_pixel(src, x, y); /* xxx - on-off transparency? if(c == 0) continue; */ if(c != 0) c = dst->fore_color; else c = dst->back_color; write_pixel(dst, dst_x + x, dst_y + y, c); } } /***************************************************************************** blit = copy from one bitmap to another, both of the same color depth *****************************************************************************/ void blit(bmp_t *src, bmp_t *dst, unsigned dst_x, unsigned dst_y) { unsigned x, y, c; /* they must be the same depth */ if(src->ops != dst->ops) return; /* use fast routine if available */ if(src->ops->blit != NULL) { src->ops->blit(src, dst, dst_x, dst_y); return; } for(y = 0; y < src->ht; y++) for(x = 0; x < src->wd; x++) { c = read_pixel(src, x, y); write_pixel(dst, dst_x + x, dst_y + y, c); } } /***************************************************************************** find 8x8 font in VGA BIOS ROM *****************************************************************************/ unsigned char FAR *bios_8x8_font(void) { unsigned char FAR *font; regs_t regs; /* use BIOS INT 10h AX=1130h to find font #3 (8x8) in ROM */ memset(®s, 0, sizeof(regs)); /* for Watcom C */ regs.R_AX = 0x1130; regs.R_BX = 0x0300; trap(0x10, ®s); /* CauseWay DOS extender seems to return a selector in ES, instead of real-mode segment value (usu. 0xC000) */ #if defined(__WATCOMC__)&&defined(__386__) font = FARPTR(0xC000, regs.R_BP); #else font = FARPTR(regs.R_ES, regs.R_BP); #endif return font; } /***************************************************************************** *****************************************************************************/ void bputs(bmp_t *bmp, unsigned x, unsigned y, const char *s) { unsigned char FAR *font; bmp_t src; font = bios_8x8_font(); src.wd = 8; src.ht = 8; src.ops = &g_ops1; for(; *s != '\0'; s++) { src.raster = font + 8 * (*s); blit1(&src, bmp, x, y); x += 8; } } /*============================================================================ DEMO ============================================================================*/ /***************************************************************************** *****************************************************************************/ static void border3d(bmp_t *bmp, int x, int y, unsigned wd, unsigned ht, char down) { if(down) { bmp->fore_color = 8; hline(bmp, x + 0, y + 0, wd - 1); vline(bmp, x + 0, y + 0, ht - 1); bmp->fore_color = 0; hline(bmp, x + 1, y + 1, wd - 3); vline(bmp, x + 1, y + 1, ht - 3); bmp->fore_color = 7; hline(bmp, x + 1, y + ht - 2, wd - 2); vline(bmp, x + wd - 2, y + 1, ht - 2); bmp->fore_color = 15; hline(bmp, x + 0, y + ht - 1, wd); vline(bmp, x + wd - 1, y + 0, ht); } else { bmp->fore_color = 7; hline(bmp, x + 0, y + 0, wd - 1); vline(bmp, x + 0, y + 0, ht - 1); bmp->fore_color = 15; hline(bmp, x + 1, y + 1, wd - 3); vline(bmp, x + 1, y + 1, ht - 3); bmp->fore_color = 8; hline(bmp, x + 1, y + ht - 2, wd - 2); vline(bmp, x + wd - 2, y + 1, ht - 2); bmp->fore_color = 0; hline(bmp, x + 0, y + ht - 1, wd); vline(bmp, x + wd - 1, y + 0, ht); } } /***************************************************************************** *****************************************************************************/ static void demo(bmp_t *bmp, const char *title) { unsigned x = 10, y = 10, wd = 180, ht = 50; /* erase screen to blue */ bmp->fore_color = 1; fill_rect(bmp, 0, 0, bmp->wd, bmp->ht); /* draw gray window with 3D border */ bmp->fore_color = 7; fill_rect(bmp, x, y, wd, ht); border3d(bmp, x, y, wd, ht, 0); /* draw white-on-green title bar */ bmp->fore_color = 2; fill_rect(bmp, x + 2, y + 2, wd - 4, 10); bmp->back_color = 2; bmp->fore_color = 15; bputs(bmp, x + 3, y + 3, title); /* draw menu bar on existing gray background */ bmp->back_color = 7; bmp->fore_color = 0; bputs(bmp, x + 3, y + 13, "File Edit"); /* draw white inner area with 3D border */ bmp->fore_color = 15; fill_rect(bmp, x + 3, y + 21, wd - 6, ht - 24); border3d(bmp, x + 3, y + 21, wd - 6, ht - 24, 1); /* await key pressed */ getch(); } /***************************************************************************** *****************************************************************************/ int main(void) { static const unsigned wd[] = { 640, 320, 640, 320, 320 }; static const unsigned ht[] = { 480, 200, 480, 200, 200 }; static const ops_t *ops[] = { &g_ops1, &g_ops2, &g_ops4p, &g_ops8, &g_ops8x }; static const unsigned mode[] = { 0x11, 5, 0x12, 0x13, 0x13 }; static const char *title[] = { "640x480x2", "320x200x4", "640x480x16", "320x200x256", "320x200x256 ModeX" }; /**/ regs_t regs; unsigned i; bmp_t bmp; #if defined(__DJGPP__) if(!(_crt0_startup_flags & _CRT0_FLAG_NEARPTR)) { if(!__djgpp_nearptr_enable()) { printf("Could not enable nearptr access " "(Windows NT/2000/XP?)\n"); } } #endif for(i = 0; i < sizeof(wd) / sizeof(wd[0]); i++) { bmp.raster = FARPTR(0xA000, 0); bmp.wd = wd[i]; bmp.ht = ht[i]; bmp.ops = ops[i]; memset(®s, 0, sizeof(regs)); /* for Watcom C */ regs.R_AX = mode[i]; trap(0x10, ®s); /* to make CGA graphics work like other graphics modes... */ if(mode[i] == 0x05) { /* 1) turn off screwy CGA addressing */ outportb(VGA_CRTC_INDEX, 0x17); outportb(VGA_CRTC_DATA, inportb(VGA_CRTC_DATA) | 1); /* 2) turn off doublescan */ outportb(VGA_CRTC_INDEX, 9); outportb(VGA_CRTC_DATA, inportb(VGA_CRTC_DATA) & ~0x80); /* 3) move the framebuffer from B800:0000 to A000:0000 */ outportb(VGA_GC_INDEX, 6); outportb(VGA_GC_DATA, inportb(VGA_GC_INDEX) & ~0x0C); } /* to convert mode 13h to Mode X... */ else if(i == 4) { /* 1) turn off Chain-4 addressing */ outportb(VGA_SEQ_INDEX, 0x04); outportb(VGA_SEQ_DATA, inportb(VGA_SEQ_DATA) & ~0x08); /* 2) turn off doubleword clocking */ outportb(VGA_CRTC_INDEX, 0x14); outportb(VGA_CRTC_DATA, inportb(VGA_CRTC_DATA) & ~0x40); /* 3) turn off word clocking in case it's on */ outportb(VGA_CRTC_INDEX, 0x17); outportb(VGA_CRTC_DATA, inportb(VGA_CRTC_DATA) | 0x40); } demo(&bmp, title[i]); } /* return to text mode */ memset(®s, 0, sizeof(regs)); /* for Watcom C */ regs.R_AX = 0x03; trap(0x10, ®s); return 0; }