//////////////////////////////////////// // Graph.h // Coded by Denn Man // // Licensed under the Academic Free License version 1.2 // // *Special thanks to Micheal Abrash // and his columns in Dr. Dobbs Journal // (Jul91 and Aug91) documenting // ModeX // // For use with Turbo-C // Uses as much assembly as possible // in order to optimize drawing speed // Self-sufficient: Nothing else needed //////////////////////////////////////// #include #include #include #include // Start of video memory (SEGMENT) #define MCGA_MEM 0xA000 // Video Digital-Analog-Converter Port #define VIDEO_DAC 0x03C8 // Video DAC Read Port #define DAC_READ 0x03C7 // Video Data Port #define VIDEO_DATA 0x03C9 // VGA Sequence Controller #define SC_INDEX 0x03C4 // CRT Control Register #define CRTC_INDEX 0x03D4 // Miscellaneous Output Register #define MISC_OUTPUT 0x03C2 // VGA Map Mask Register #define MAP_MASK 0x02 // Screen Width #define SCR_WIDTH 80 // VGA Graphics Controller Register #define GC_INDEX 0x03CE // VGA Read Map Register #define READ_MAP 0x04 // PI #define PI 3.14159265358 // Datatypes typedef unsigned char byte; typedef unsigned int word; typedef unsigned long dword; typedef struct tagTILE { word height; word width; byte *data; } TILE; // FOR BITMAP FILES!!! typedef struct tagBITMAPFILEHEADER { word bfType; dword bfSize; word bfReserved1; word bfReserved2; dword bfOffBits; } BITMAPFILEHEADER; typedef struct tagBITMAPINFOHEADER { dword biSize; dword biWidth; dword biHeight; word biPlanes; word biBitCount; dword biCompression; dword biSizeImage; dword biXPelsPerMeter; dword biYPelsPerMeter; dword biClrUsed; dword biClrImportant; } BITMAPINFOHEADER; typedef struct tagRGBQUAD { byte rgbBlue; byte rgbGreen; byte rgbRed; byte rgbReserved; } RGBQUAD; typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[256]; } BITMAPINFO; typedef struct tagBITMAP { BITMAPFILEHEADER bmFileHeader; BITMAPINFO bmInfo; byte *bmData; } BITMAP; // DONE WITH BITMAP FILE INFO // Monitor settings for ModeX struct tagCRTParms { word V_Total; // Vertical Total word OverFlow; // Overflow word CellHeight; // Cell Height word V_Sync_Start; // Vertical Sync Start word V_Sync_End; // Vertical Sync End word V_Disp; // Vertical Displayed word D_Off; // Turn off dword mode word V_Blank_Start; // Vertical Blank Start word V_Blank_End; // Vertical Blank End word B_On; // Turn byte mode on } CRTParms; byte VIDEO_MODE; // The current video mode word MAX_X; // Last available X word MAX_Y; // Last available Y word SCREEN_WIDTH; word SCREEN_HEIGHT; byte MAX_COLORS; // Last available color byte FG_COLOR; // For text and mono stuff byte BG_COLOR; word CUR_X; // Current X word CUR_Y; // Current Y word visibleStart;// Start offset in video memory of screen word activeStart; // Start offset in video memory to write to // For rectangle filling (ModeX) byte LeftClipPlaneMask[] = { 0x0F, 0x0E, 0x0C, 0x08 }; byte RightClipPlaneMask[] = { 0x0F, 0x01, 0x03, 0x07 }; // In case things go awry char msg_error[81]; void exit_error(void) { printf("%s\a\n", msg_error); } //////////////////////////////////////// // init_modeX // // -Initialized modeX 320x240x256 graphics // -Clears video memory // -Sets page 1 active at offset 0 //////////////////////////////////////// void init_modeX(void) { // Fill the CRT Parameters CRTParms.V_Total = 0x0D06; CRTParms.OverFlow = 0x3E07; CRTParms.CellHeight = 0x4109; CRTParms.V_Sync_Start = 0xEA10; CRTParms.V_Sync_End = 0xAC11; CRTParms.V_Disp = 0xDF12; CRTParms.D_Off = 0x0014; CRTParms.V_Blank_Start = 0xE715; CRTParms.V_Blank_End = 0x0616; CRTParms.B_On = 0xE317; asm { mov ax, 0x0013 // Start in mode 13h int 0x10 // Call interrupt mov dx, SC_INDEX // Disable Chain-4 Memory model mov ax, 0x0604 out dx, ax mov ax, 0x0100 // Synchronous reset while switching clocks out dx, ax mov dx, MISC_OUTPUT // Select 28MHz dot clock, 60 Hz scan rate mov al, 0xE3 out dx, al mov dx, SC_INDEX // Restart Sequencer mov ax, 0x0300 out dx, ax mov dx, CRTC_INDEX // Reprogram the CRT Controller mov al, 0x11 // Select Write-Protect bit out dx, al inc dx // CRT-Data register into DX now in al, dx // Get current VSync End Register setting and al, 0x7F // Remove write-protect on various registers out dx, al dec dx // CRT-Controller cld // Clear the direction mov si, OFFSET CRTParms // Load settings mov cx, 20 // 20 Entries } SetCRTParmsLoop:asm { lodsw // Load the addr:data pair out dx, ax // Send them out loop SetCRTParmsLoop // Do it for each one mov dx, SC_INDEX // Enable write to all bitplanes mov ax, 0x0F02 out dx, ax mov ax, MCGA_MEM // Load video memory mov es, ax mov di, 0 // Start at the top mov ax, 0 // Store 0's mov cx, 0x8000 // Do it 8000h times rep stosw // Store them } // Fill globals VIDEO_MODE = 0xFF; MAX_X = 319; MAX_Y = 239; SCREEN_WIDTH = 320; SCREEN_HEIGHT = 240; MAX_COLORS = 255; FG_COLOR = 15; BG_COLOR = 0; CUR_X = 0; CUR_Y = 0; activeStart = 0; // Write to Page 0 visibleStart = 0; // Read from Page 0 } //////////////////////////////////////// // close_graph // // -Shuts down graphics, sets text mode, // and fills appropriate variables // -Frees memory taken by video buffers //////////////////////////////////////// void close_graph(void) { asm { mov ax, 0x0003 int 0x10 } VIDEO_MODE = 0x03; MAX_X = 80; MAX_Y = 25; SCREEN_WIDTH = 80; SCREEN_HEIGHT = 25; MAX_COLORS = 15; FG_COLOR = 7; BG_COLOR = 0; CUR_X = 1; CUR_Y = 1; } //////////////////////////////////////// // setActiveStart // // -Sets the starting memory address // at which graphics are written to //////////////////////////////////////// void setActiveStart(word PageBase) { activeStart = PageBase; } //////////////////////////////////////// // setVisibleStart // // -Sets the starting memory address // at which graphics are displayed from //////////////////////////////////////// void setVisibleStart(word PageBase) { visibleStart = PageBase; asm { pusha // Dont touch! mov bx, PageBase // Load the offset into BX mov dx, CRTC_INDEX // Load the CRT Index Port into DX mov al, 0x0C // Function 0Ch, High Byte of addres mov ah, bh // Load the high byte out dx, ax // Send it inc al // Function 0Dh, Low Byte of address mov ah, bl // Load the low byte out dx, ax // Send it popa // We were never here } } //////////////////////////////////////// // setActivePage // // -Sets page in which graphics are // written //////////////////////////////////////// void setActivePage(byte Page) { // Page = (Page Offset * Width of Screen (Bytes) * Screen Height (Rows)) setActiveStart(Page * SCR_WIDTH * SCREEN_HEIGHT); } //////////////////////////////////////// // setVisiblePage // // -Sets page from which graphics are // drawn //////////////////////////////////////// void setVisiblePage(byte Page) { // Page = (Page Offset * Width of Screen (Bytes) * Screen Height (Rows)) setVisibleStart(Page * SCR_WIDTH * SCREEN_HEIGHT); } void vScroll(int rows) { // Scrolling = current start + (rows * bytes in a row) setVisibleStart(visibleStart + (rows * SCR_WIDTH)); } void read_palette(byte *pal) { int i; for (i = 0; i < 256; i++) { asm cli outportb(DAC_READ, i); pal[(i * 3) + 0] = inportb(VIDEO_DATA); pal[(i * 3) + 1] = inportb(VIDEO_DATA); pal[(i * 3) + 2] = inportb(VIDEO_DATA); asm sti } } void load_palette(byte *pal) { int i; for (i = 0; i < 256; i++) { asm cli outportb(VIDEO_DAC, i); outportb(VIDEO_DATA, pal[(i * 3) + 0]); outportb(VIDEO_DATA, pal[(i * 3) + 1]); outportb(VIDEO_DATA, pal[(i * 3) + 2]); asm sti } } //////////////////////////////////////// // setpalette // // -Self explanatory, sets the color // of a given index in 256 MCGA mode //////////////////////////////////////// void setpalette(byte index, byte red, byte green, byte blue) { outportb(VIDEO_DAC, index); outportb(VIDEO_DATA, red); outportb(VIDEO_DATA, green); outportb(VIDEO_DATA, blue); } //////////////////////////////////////// // putpixel // // -Puts a pixel (in modeX) at (X, Y) // in active page //////////////////////////////////////// void putpixel(word x, word y, byte Color) { asm { pusha push es push di mov ax, SCR_WIDTH // Load screen width, in bytes mul y // Multiply it by Y to get row mov di, x // Load X into BX shr di, 2 // Divide X by 4 to get plane add di, ax // Add to col offset, row offset add di, activeStart // Add the page offset mov ax, MCGA_MEM // Load video memory into AX mov es, ax // Then ES mov cx, x // Get X pos and cl, 0x03 // Get X plane mov ax, 0x0100 + MAP_MASK // Load Map-Mask register shl ah, cl // Write to only the desired plane mov dx, SC_INDEX // Tell the Map-Mask to do the same out dx, ax // Do it mov al, Color // Load the color stosb // Store it pop di pop es popa } } //////////////////////////////////////// // getpixel // // -Returns color of pixel (in ModeX) // at (x, y) //////////////////////////////////////// byte getpixel(word x, word y) { byte returnvalue; // To hold color asm { pusha // Dont touch anything push es push di mov ax, SCR_WIDTH // Move screen width (in bytes) into AX mul y // Multiply by y to get row mov bx, x // Move x into BX shr bx, 2 // Divide by 4 to get plane add bx, ax // Add the two offsets together add bx, activeStart // Add the PageBase mov ax, MCGA_MEM // Video Memory into AX mov es, ax // Now into ES mov ah, byte ptr x // X into AH and ah, 0x03 // Get Plane mov al, READ_MAP // Tell the Video Card mov dx, GC_INDEX out dx, ax // Do it mov al, es:[bx] // Get the color sub ah, ah // Clear AH mov returnvalue, al // Store the color into returnvalue pop di pop es popa // Put everything back } return returnvalue; // Return color } //////////////////////////////////////// // vline // // -Draws a vertical line from (x,y1)-(x,y2) // of color in activePage //////////////////////////////////////// void vline(word y1, word y2, word x, byte color) { asm { pusha // Dont touch anything here push es push di push si // For lowY mov si, y1 // Move y1 into SI mov bx, y2 // y2 into BX cmp si, bx // Compare the two jle vline_ordered // IF y1 <= y2, skip the next lines xchg si, bx // ELSE switch them } vline_ordered:asm { mov ax, MCGA_MEM // Load the video memory into AX mov es, ax // then into ES mov ax, SCR_WIDTH // Load screen width (bytes) into AX mul si // and Multiply by the y1 value mov di, ax // Move it into DI mov ax, x // x into AX shr ax, 2 // Divided by 4 add di, ax // and added to get first point offset mov dx, SC_INDEX // Point to Sequence Controller mov al, MAP_MASK // Select Map Mask Register (0x02) out dx, al // Do it inc dx // Next Register (SC Data) mov cl, byte ptr x // x into CL and cl, 0x03 // x & 0011b to get plane mov al, 0x01 // Only one plane, so load 0001b shl ax, cl // Shift it left by the plane number out dx, al // Send it mov cx, bx // Load highY into CX sub cx, si // Subtract lowY to get ydelta inc cx // Add one mov al, color // Load the color into AL } vline_row_top:asm { stosb // Store the color add di, SCR_WIDTH // Add a row dec di // Subtract one, because STO incs DI loop vline_row_top // Loop until done pop si pop di pop es popa // Put everything back } } //////////////////////////////////////// // hline // // -Draws a horizontal line from // (x1, y)-(x2, y) of color in activePage // -Faster than a speeding bullet! (Draws // four pixels at a time by turning on // all bitplanes for read at once. // -This function is ALL MINE!!! Not one // bit of a copy. :) //////////////////////////////////////// void hline(word x1, word x2, word y, byte color) { asm { pusha // Dont touch anything push es push di push si // For lowX mov ax, MCGA_MEM // Load the graphics memory mov es, ax mov ax, SCR_WIDTH // Screen width (bytes) into AX mul y // Multiply by y to get row offset mov di, ax // Move row offset into DI mov si, x1 // This was a pain to order the variables mov cx, x2 // Load x1 into SI, x2 into CX cmp si, cx // Compare them jle hline_ordered // If x1 is <= x2, skip the next stuff xchg si, cx // ELSE switch them } hline_ordered:asm { mov ax, si // lowX into AX shr ax, 2 // Divided by 4 add di, ax // Add the lowX offset to DI mov ax, si // lowX back into AX and ax, 0x0003 // lowX AND 0011b to get left bit planes } _BH = LeftClipPlaneMask[_AX]; // Load the left mask into BH asm { mov dx, SC_INDEX // Sequence Controller into DX mov al, MAP_MASK // Map Mask (Function 0Ch) into AL out dx, al // Send it inc dx // SC Data Register mov al, bh // Load the mask into AL out dx, al // Send it mov al, color // Load color into AL stosb // Store the first byte of line mov dx, SC_INDEX // Loading a mask again mov al, MAP_MASK // ... out dx, al // ... inc dx // ... as before mov al, 0x0F // Turn all bits on, we want MAX SPEED! out dx, al // Send it push cx // Store the highX sub cx, si // Subtract the lowX from highX shr cx, 2 // Divide it by 4 dec cx // Minus the last byte mov al, color // Load the color into AL rep stosb // Repeat the storing of color pop cx // Get out highX back inc cl // Increment, BUG FIX : Not sure why and cl, 0x03 // AND 0011b to get bit planes } _BL = RightClipPlaneMask[_CL];// Load the right mask into BL asm { mov dx, SC_INDEX // Again, we send the mask to the mov al, MAP_MASK // VGA card out dx, al // ... inc dx // ... mov al, bl // ... out dx, al // ... We have seen this already... mov al, color // Load the color into AL stosb // Store the last byte pop si pop di pop es popa // Put everything back, we're done! } } //////////////////////////////////////// // line // // -Draws a line from (x1, y1)-(x2, y2) // of color in the active page // -Uses Bresenhem's Line routine //////////////////////////////////////// void line(word x1, word y1, word x2, word y2, byte color) { int dx, dy; int xinc, yinc; int P_right; int P_right_up; int P; if (x1 == x2) vline(y1, y2, x1, color); // if (y1 == y2) // hline(x1, x1, y1, 0, color); dx = abs(x2 - x1); dy = abs(y2 - y1); xinc = (x1 > x2) ? -1 : 1; yinc = (y1 > y2) ? -1 : 1; if (dx > dy) { P_right = (2 * dy); P_right_up = P_right - (2 * dx); P = P_right - dx; for (; dx >= 0; dx--) { putpixel(x1, y1, color); if (P > 0) { x1 += xinc; y1 += yinc; P += P_right_up; } else { x1 += xinc; P += P_right; } } } else { P_right = (2 * dx); P_right_up = P_right - (2 * dy); P = P_right - dy; for (; dy >= 0; dy--) { putpixel(x1, y1, color); if (P > 0) { x1 += xinc; y1 += yinc; P += P_right_up; } else { y1 += yinc; P += P_right; } } } } void rect(word x1, word y1, word x2, word y2, byte color) { // Just draw the four lines, x is longer because hline is a // faster routine than vline. hline(x1, x2, y1, color); hline(x1, x2, y2, color); vline(y1 + 1, y2 - 1, x1, color); vline(y1 + 1, y2 - 1, x2, color); } //////////////////////////////////////// // fillrect // // -Fills a rectangle (in ModeX) of color // from (x1, y1) to (x2, y2) // // *Adopted from Dr. Dobbs Journal Sep 91 //////////////////////////////////////// void fillrect(word x1, word y1, word x2, word y2, byte color) { asm { pusha // Dont touch anything push ds push si push es push di push bp cld // Clear direction flag (left to right) mov ax, SCR_WIDTH // Screen Width (bytes) into AX mul y1 // Times y1 value mov di, x1 // x1 into DI shr di, 2 // Divided by four to get plane add di, ax // Add the y offset add di, activeStart // Add the PageBase mov ax, MCGA_MEM // Now set up the video memory mov es, ax // into ES mov dx, SC_INDEX // Point to Sequence Controller mov al, MAP_MASK // Select Map Mask Register (0x02) out dx, al // Do it inc dx // Next Register (SC Data) mov si, x1 // x1 into SI and si, 0x0003 // x1 & 0011b to get plane } _BH = LeftClipPlaneMask[_SI]; // Select appropriate mask asm { mov si, x2 // Do the same for x2 and si, 0x0003 // x2 & 0011b } _BL = RightClipPlaneMask[_SI]; // Mask select asm { mov cx, x2 // x2 into CX mov si, x1 // x1 into SI cmp cx, si // Compare them jle FillDone // IF CX <= SI jump to FillDone dec cx // Decrement CX and si, 0xFFFC // x1 & 1111111111111100 sub cx, si // Subtract x1 from x2 shr cx, 2 // Divide CX by 4 jnz MasksSet // Jump if not zero to MasksSet and bh, bl // BH = BH and BL } MasksSet:asm { mov si, y2 // y2 into SI sub si, y1 // Subtract y1 jle FillDone // IF SI <= 0 jump to FillDone mov ah, byte ptr color // Load color mov bp, SCR_WIDTH // Load screen width (bytes) sub bp, cx // Subtract xdelta from width dec bp // Minus 1 } FillRowsLoop:asm { push cx // Dont hurt CX mov al, bh // Left Clip Mask into AL out dx, al // Send it out mov al, ah // Color to AL stosb // Send it out dec cx // Minus left edge byte js FillLoopBottom // Jump if SIGNAL flag NOT set jz DoRightEdge // If that's it, do the right. mov al, 0x0F // Turn all bit planes on out dx, al // Do it mov al, ah // Load color into AL rep stosb // Store it over and over } DoRightEdge:asm { mov al, bl // Right Clip Mask into AL out dx, al // Do it mov al, ah // Load color into AL stosb // Store it } FillLoopBottom:asm { add di, bp // Next scan line pop cx // What row? dec si // One less row jnz FillRowsLoop // If not zero, go again } FillDone:asm { pop bp pop di pop es pop si pop ds popa // All done, put everything back } } void circle(word x, word y, word radius, byte color) { float angle; float ainc = 1; word xc, yc; if (radius > 119) // IF the radius is greater than 119, make the ainc = 0.25; // increment smaller if (radius > 61) // IF it's bigger than 61 ainc = 0.5; // do the same // Only calculate 1/8th of the circle // Four quadrants = (x, y), (-x, y), (-x, -y), (x, -y) // Eight octants are made by switching x's and y's. // Draw eight pixels per iteration and make the circle // eight times faster than conventional routines. for (angle = 0; angle <= 45; angle += ainc) { xc = (word)floor(cos((angle * PI) / 180) * radius); yc = (word)floor(sin((angle * PI) / 180) * radius); putpixel(x + xc, y + yc, color); putpixel(x + xc, y - yc, color); putpixel(x - xc, y + yc, color); putpixel(x - xc, y - yc, color); putpixel(x + yc, y + xc, color); putpixel(x + yc, y - xc, color); putpixel(x - yc, y + xc, color); putpixel(x - yc, y - xc, color); } } void load_BMP_palette(BITMAP *s) { int i; for (i = 0; i < 256; i++) setpalette(i, (s->bmInfo.bmiColors[i].rgbRed / 63) * 255, (s->bmInfo.bmiColors[i].rgbGreen / 63) * 255, (s->bmInfo.bmiColors[i].rgbBlue / 63) * 255); } //////////////////////////////////////// // load_BMP // // -Opens a bitmap (Non-Compressed) and // stores the data into a given BITMAP // variable // -Allocate memory BEFORE calling this // function for the BITMAP structure // -Must be 256 Color, Columns divisible // by four //////////////////////////////////////// void load_BMP(char *filename, TILE *target) { FILE *inBMP; // Create file handle BITMAP *tempBMP; int color; if ((inBMP = fopen(filename, "r+b")) == NULL)// Open it { sprintf(msg_error, "Cannot open %s", filename); atexit(exit_error); // IF error, exit and exit(1); // tell user. } tempBMP = (BITMAP *)malloc(sizeof(BITMAP) - 1); fseek(inBMP, 0, 0); // Goto the beginning fread(tempBMP, sizeof(BITMAP) - 1, 1, inBMP); // And load it all into // the given variable fseek(inBMP, 54, 0); // Goto color offset for (color = 0; color < 256; color++) // and read 'em in. { tempBMP->bmInfo.bmiColors[color].rgbBlue = fgetc(inBMP); tempBMP->bmInfo.bmiColors[color].rgbGreen = fgetc(inBMP); tempBMP->bmInfo.bmiColors[color].rgbRed = fgetc(inBMP); tempBMP->bmInfo.bmiColors[color].rgbReserved = fgetc(inBMP); } load_BMP_palette(tempBMP); // Allocate space for the data to hold the pixels // Goto the pixel offset in the file // Read in the pixels target->data = (byte *)calloc((tempBMP->bmFileHeader.bfSize - tempBMP->bmFileHeader.bfOffBits), 1); target->height = tempBMP->bmInfo.bmiHeader.biHeight; target->width = tempBMP->bmInfo.bmiHeader.biWidth; fseek(inBMP, tempBMP->bmFileHeader.bfOffBits, 0); fread(target->data, target->height * target->width, 1, inBMP); free(tempBMP); fclose(inBMP); // Dont forget to close } void show_BMP(TILE *source, word x, word y) { word row, col, offset = 0; for (row = source->height; row > 0; row--) { for (col = 0; col < source->height; col++) { putpixel(x + col, y + row, source->data[offset]); offset++; } } } void show_tile(TILE *tile, word x, word y) { word cx, cy; for (cy = 1; cy <= tile->height; cy++) for (cx = 0; cx < tile->width; cx++) putpixel(x + cx, y + cy, tile->data[cx + (cy * tile->width)]); } void make_tile(TILE *target, TILE *source) { int row, col, offset = 0; target->data = (byte *)malloc(source->height * source->width + source->width); target->height = source->height; target->width = source->width; for (row = source->height * source->width; row >= 0; row -= source->width) { for (col = 0; col < source->width; col++) { target->data[offset] = source->data[col + row]; offset++; } } }