/* Sarien - A Sierra AGI resource interpreter engine * Copyright (C) 1999-2001 Stuart George and Claudio Matsuoka * * $Id: graphics.c,v 1.40.2.1 2001/11/11 11:01:21 cmatsuoka Exp $ * * 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; see docs/COPYING for further details. */ #include #include #include "sarien.h" #include "graphics.h" #if defined PALMOS || defined FAKE_PALMOS # define DEV_X0(x) (x) # define DEV_X1(x) (x) # define DEV_Y(x) ((x) * PIC_HEIGHT / 168) #else # define DEV_X0(x) ((x) << 1) # define DEV_X1(x) (((x) << 1) + 1) # define DEV_Y(x) (x) #endif #ifndef MAX_INT # define MAX_INT (int)((unsigned)~0 >> 1) #endif extern UINT8 cur_font[]; #ifdef __TURBOC__ static far UINT8 sarien_screen[64000]; #ifdef USE_CONSOLE static far UINT8 console_screen[64000]; #endif #else static UINT8 sarien_screen[GFX_WIDTH * GFX_HEIGHT]; #ifdef USE_CONSOLE static UINT8 console_screen[GFX_WIDTH * GFX_HEIGHT]; #endif #endif #ifdef USE_EGA_PALETTE /** * 16 color RGB palette (plus 16 transparent colors). * This array contains the 6-bit RGB values of the EGA palette exported * to the console drivers. */ UINT8 palette [32 * 3]= { 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x2A, 0x00, 0x00, 0x2A, 0x2A, 0x2A, 0x00, 0x00, 0x2A, 0x00, 0x2A, 0x2A, 0x15, 0x00, 0x2A, 0x2A, 0x2A, 0x15, 0x15, 0x15, 0x15, 0x15, 0x3F, 0x15, 0x3F, 0x15, 0x15, 0x3F, 0x3F, 0x3F, 0x15, 0x15, 0x3F, 0x15, 0x3F, 0x3F, 0x3F, 0x15, 0x3F, 0x3F, 0x3F }; #else /** * 16 color amiga-ish palette. */ UINT8 palette [32 * 3]= { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x2A, 0x00, 0x00, 0x2A, 0x2A, 0x33, 0x00, 0x00, 0x2f, 0x1c, 0x37, 0x23, 0x14, 0x00, 0x2f, 0x2f, 0x2f, 0x15, 0x15, 0x15, 0x00, 0x2f, 0x3f, 0x00, 0x33, 0x15, 0x15, 0x3F, 0x3F, 0x3f, 0x27, 0x23, 0x3f, 0x15, 0x3f, 0x3b, 0x3b, 0x00, 0x3F, 0x3F, 0x3F }; #endif struct update_block { int x1, y1; int x2, y2; }; static struct update_block update = { MAX_INT, MAX_INT, 0, 0 }; struct gfx_driver *gfx; /* graphics driver */ /* * Layer 4: 640x480? ================== User display * ^ * | do_update(), put_block() * | * Layer 3: 640x480? ================== Framebuffer * ^ * | flush_block(), put_pixels() * | * Layer 2: 320x200 ================== Sarien screen (console), put_pixel() * | * Layer 1: 160x168 ================== AGI screen */ #ifdef USE_CONSOLE /** * Draws a row of pixels in the output device framebuffer. * This function adds the console layer using transparent colors if * appropriate. */ static void put_pixels (const int x, const int y, const int w, UINT8 *p) { int i; UINT8 _b[GFX_WIDTH] = { 0 }; UINT8 *b, *c = NULL; if (console.y <= y) { gfx->put_pixels (x, y, w, p); return; } b = &_b[0]; c = &console_screen[x + (y + GFX_HEIGHT - 1 - console.y) * GFX_WIDTH]; for (i = 0; i < w; i++, c++, p++) { *b++ = *c ? *c : *p + 16; } gfx->put_pixels (x, y, w, _b); } static void init_console () { int i; /* "Transparent" colors */ for (i = 0; i < 48; i++) palette[i + 48] = (palette[i] + 0x30) >> 2; /* Console */ for (i = 0; i < CONSOLE_LINES_BUFFER; i++) console.line[i] = strdup ("\n"); } #else static void put_pixels (const int x, const int y, const int w, UINT8 *p) { gfx->put_pixels (x, y, w, p); } static void init_console () { } #endif /* USE_CONSOLE */ /* Based on LAGII 0.1.5 by XoXus */ void shake_screen (int n) { #define MAG 3 int i; UINT8 *b, *c; if ((b = (UINT8*)malloc(GFX_WIDTH*GFX_HEIGHT)) == NULL) return; if ((c = (UINT8*)malloc(GFX_WIDTH*GFX_HEIGHT)) == NULL) { free (b); return; } commit_both (); /* Fixes SQ1 demo */ memset (c, 0, GFX_WIDTH * GFX_HEIGHT); memcpy (b, sarien_screen, GFX_WIDTH * GFX_HEIGHT); for (i = 0; i < (GFX_HEIGHT - MAG); i++) memcpy (&c[GFX_WIDTH * (i + MAG) + MAG], &b[GFX_WIDTH * i], GFX_WIDTH - MAG); for (i = 0; i < (2 * n); i++) { main_cycle (); memcpy (sarien_screen, c, GFX_WIDTH * GFX_HEIGHT); flush_block (0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); main_cycle (); memcpy (sarien_screen, b, GFX_WIDTH * GFX_HEIGHT); flush_block (0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); } free (c); free (b); #undef MAG } void put_text_character (int l, int x, int y, unsigned int c, int fg, int bg) { int x1, y1, xx, yy, cc; UINT8 *p; #if defined PALMOS || defined FAKE_PALMOS if (c & 0x80) c = 1; #endif p = cur_font + ((unsigned int)c * CHAR_LINES); for (y1 = 0; y1 < CHAR_LINES; y1++) { for (x1 = 0; x1 < CHAR_COLS; x1 ++) { xx = x + x1; yy = y + y1; cc = (*p & (1 << (7 - x1))) ? fg : bg; #ifdef USE_CONSOLE if (l) { console_screen[xx + yy * GFX_WIDTH] = cc; } else #endif { sarien_screen [xx + yy * GFX_WIDTH] = cc; } } p++; } /* FIXME: we don't want this when we're writing on the * console! */ flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); } void draw_rectangle (int x1, int y1, int x2, int y2, int c) { int y, w, h; UINT8 *p0; if (x1 >= GFX_WIDTH) x1 = GFX_WIDTH - 1; if (y1 >= GFX_HEIGHT) y1 = GFX_HEIGHT - 1; if (x2 >= GFX_WIDTH) x2 = GFX_WIDTH - 1; if (y2 >= GFX_HEIGHT) y2 = GFX_HEIGHT - 1; w = x2 - x1 + 1; h = y2 - y1 + 1; p0 = &sarien_screen[x1 + y1 * GFX_WIDTH]; for (y = 0; y < h; y++) { memset (p0, c, w); p0 += GFX_WIDTH; } } static void draw_frame (int x1, int y1, int x2, int y2, int c1, int c2) { int y, w; UINT8 *p0; /* top line */ w = x2 - x1 + 1; p0 = &sarien_screen[x1 + y1 * GFX_WIDTH]; memset (p0, c1, w); /* bottom line */ p0 = &sarien_screen[x1 + y2 * GFX_WIDTH]; memset (p0, c2, w); /* side lines */ for (y = y1; y <= y2; y++) { sarien_screen[x1 + y * GFX_WIDTH] = c1; sarien_screen[x2 + y * GFX_WIDTH] = c2; } } void draw_box (int x1, int y1, int x2, int y2, int colour1, int colour2, int m) { x1 += m; y1 += m; x2 -= m; y2 -= m; #ifdef FANCY_BOX draw_rectangle (x1, y1, x2, y2, 7); draw_frame (x1, y1, x2, y2, 15, 8); if (m > 0) draw_frame (x1 + 3, y1 + 3, x2 - 3, y2 - 3, 8, 15); flush_block (x1, y1, x2, y2); #else draw_rectangle (x1, y1, x2, y2, colour1); draw_frame (x1 + 2, y1 + 2, x2 - 2, y2 - 2, colour2, colour2); flush_block (x1, y1, x2, y2); #endif } void print_character (int x, int y, char c, int fg, int bg) { x *= CHAR_COLS; y *= CHAR_LINES; put_text_character (0, x, y, c, fg, bg); /* CM: the extra pixel in y is for the underline cursor */ flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES); } /** * Draw button * @param x x coordinate of the button * @param y y coordinate of the button * @param a set if the button has focus * @param p set if the button is pressed */ void draw_button (int x, int y, char *s, int a, int p) { int len = strlen (s); int x1, y1, x2, y2; x1 = x - 3; y1 = y - 3; x2 = x + CHAR_COLS * len + 2; y2 = y + CHAR_LINES + 2; #ifdef FANCY_BOX if (p) draw_frame (x1, y1, x2, y2, 8, 15); else draw_frame (x1, y1, x2, y2, 15, 8); #endif while (*s) { #ifdef FANCY_BOX put_text_character (0, x + (!!p), y + (!!p), *s++, 0, 7); #else put_text_character (0, x + (!!p), y + (!!p), *s++, a ? 15 : 0, a ? 0 : 15); #endif x += CHAR_COLS; } x1 -= 2; y1 -= 2; x2 += 2; y2 += 2; #ifdef FANCY_BOX if (a) draw_frame (x1, y1, x2, y2, 8, 15); else draw_frame (x1, y1, x2, y2, 7, 7); #endif flush_block (x1, y1, x2, y2); } int test_button (int x, int y, char *s) { int len = strlen (s); int x1, y1, x2, y2; x1 = x - 3; y1 = y - 3; x2 = x + CHAR_COLS * len + 2; y2 = y + CHAR_LINES + 2; if (mouse.x >= x1 && mouse.y >= y1 && mouse.x <= x2 && mouse.y <= y2) return TRUE; return FALSE; } void put_block (int x1, int y1, int x2, int y2) { gfx->put_block (x1, y1, x2, y2); } void put_screen () { put_block (0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); } void poll_timer () { gfx->poll_timer (); } int get_key () { return gfx->get_key (); } int keypress () { return gfx->keypress (); } /* * Public functions */ /** * Initialize graphics device. * * @see deinit_video() */ int init_video () { #ifndef _WIN32 /* XXX: this causes the win32 debug build to crash for some reason */ fprintf (stderr, "Initializing graphics: %dx%d (scale = %d)\n", GFX_WIDTH, GFX_HEIGHT, opt.scale); #endif init_console (); return gfx->init_video_mode (); } /** * Deinitialize graphics device. * * @see init_video() */ int deinit_video () { return gfx->deinit_video_mode (); } /** * Write pixels on the output device. * This function writes a row of pixels on the output device. Only the * lower 4 bits of each pixel in the row will be used, making this * function suitable for use with rows from the AGI screen. * @param x x coordinate of the row start (AGI coord.) * @param y y coordinate of the row start (AGI coord.) * @param n number of pixels in the row * @param p pointer to the row start in the AGI screen */ void put_pixels_a (int x, int y, int n, UINT8 *p) { /* y += CHAR_LINES; */ #ifdef FAKE_PALMOS for (; n--; p++, x ++) { *(UINT8 *)&sarien_screen[x + DEV_Y(y) * GFX_WIDTH] = *p & 0x0f; } #else for (x *= 2; n--; p++, x += 2) { register UINT16 q = ((UINT16)*p << 8) | *p; #ifdef USE_CONSOLE if (debug.priority) q >>= 4; #endif *(UINT16 *)&sarien_screen[x + y * GFX_WIDTH] = q & 0x0f0f; } #endif } #ifdef USE_HIRES void put_pixels_hires (int x, int y, int n, UINT8 *p) { /* y += CHAR_LINES; */ for (; n--; p++, x++) { UINT8 q = *p; #ifdef USE_CONSOLE if (debug.priority) q >>= 4; #endif sarien_screen[x + y * GFX_WIDTH] = q & 0x0f; } } #endif /** * Schedule blocks for blitting on the output device. * This function gets the coordinates of a block in the AGI screen and * schedule it to be updated in the output device. * @param x1 x coordinate of the upper left corner of the block (AGI coord.) * @param y1 y coordinate of the upper left corner of the block (AGI coord.) * @param x2 x coordinate of the lower right corner of the block (AGI coord.) * @param y2 y coordinate of the lower right corner of the block (AGI coord.) * * @see do_update() */ void schedule_update (int x1, int y1, int x2, int y2) { if (x1 < update.x1) update.x1 = x1; if (y1 < update.y1) update.y1 = y1; if (x2 > update.x2) update.x2 = x2; if (y2 > update.y2) update.y2 = y2; } /** * Update scheduled blocks on the output device. * This function exposes the blocks scheduled for updating to the output * device. Blocks can be scheduled at any point of the AGI cycle. * * @see schedule_update() */ void do_update () { if (update.x1 <= update.x2 && update.y1 <= update.y2) { gfx->put_block (update.x1, update.y1, update.x2, update.y2); } /* reset update block variables */ update.x1 = MAX_INT; update.y1 = MAX_INT; update.x2 = 0; update.y2 = 0; } /** * Updates a block of the framebuffer with contents of the Sarien screen. * This function updates a block in the output device with the contents of * the Sarien screen, handling console transparency. * @param x1 x coordinate of the upper left corner of the block * @param y1 y coordinate of the upper left corner of the block * @param x2 x coordinate of the lower right corner of the block * @param y2 y coordinate of the lower right corner of the block * * @see flush_block_a() */ void flush_block (int x1, int y1, int x2, int y2) { int y, w; UINT8 *p0; schedule_update (x1, y1, x2, y2); p0 = &sarien_screen[x1 + y1 * GFX_WIDTH]; w = x2 - x1 + 1; for (y = y1; y <= y2; y++) { put_pixels (x1, y, w, p0); p0 += GFX_WIDTH; } } /** * Updates a block of the framebuffer receiving AGI picture coordinates. * @param x1 x AGI picture coordinate of the upper left corner of the block * @param y1 y AGI picture coordinate of the upper left corner of the block * @param x2 x AGI picture coordinate of the lower right corner of the block * @param y2 y AGI picture coordinate of the lower right corner of the block * * @see flush_block() */ void flush_block_a (int x1, int y1, int x2, int y2) { /* y1 += 8; y2 += 8; */ flush_block (DEV_X0(x1), DEV_Y(y1), DEV_X1(x2), DEV_Y(y2)); } /** * Updates the framebuffer with contents of the Sarien screen (console-aware). * This function updates the output device with the contents of the AGI * screen, handling console transparency. */ void flush_screen () { flush_block (0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); } /** * Clear the output device screen (console-aware). * This function clears the output device screen and updates the * output device. Contents of the AGI screen are left untouched. This * function can be used to simulate a switch to a text mode screen in * a graphic-only device. * @param c color to clear the screen */ void clear_screen (int c) { memset (sarien_screen, c, GFX_WIDTH * GFX_HEIGHT); flush_screen (); } #ifdef USE_CONSOLE /** * Clear the console screen. * This function clears the top n lines of the console screen. * @param n number of lines to clear (in pixels) */ void clear_console_screen (int n) { memset (console_screen + n * GFX_WIDTH, 0, (200 - n) * GFX_WIDTH); } #endif /** * Save a block of the Sarien screen */ void save_block (int x1, int y1, int x2, int y2, UINT8 *b) { UINT8 *p0; int w, h; p0 = &sarien_screen[x1 + GFX_WIDTH * y1]; w = x2 - x1 + 1; h = y2 - y1 + 1; while (h--) { memcpy (b, p0, w); b += w; p0 += GFX_WIDTH; } } /** * Restore a block of the Sarien screen */ void restore_block (int x1, int y1, int x2, int y2, UINT8 *b) { UINT8 *p0; int w, h; p0 = &sarien_screen[x1 + GFX_WIDTH * y1]; w = x2 - x1 + 1; h = y2 - y1 + 1; while (h--) { memcpy (p0, b, w); b += w; p0 += GFX_WIDTH; } flush_block (x1, y1, x2, y2); } UINT8 *get_sarien_screen () { return &sarien_screen[0]; } /* end: graphics.c */