/* ScummVM - Scumm Interpreter * Copyright (C) 2006 The ScummVM project * * Copyright (C) 1999-2003 Sarien Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ #include "common/stdafx.h" #include "graphics/cursorman.h" #include "agi/agi.h" #include "agi/graphics.h" namespace Agi { #define DEV_X0(x) ((x) << 1) #define DEV_X1(x) (((x) << 1) + 1) #define DEV_Y(x) (x) #ifndef MAX_INT # define MAX_INT (int)((unsigned)~0 >> 1) #endif static uint8 *agi_screen; static unsigned char *screen; extern uint8 cur_font[]; /** * 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 ega_palette[16 * 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 }; /** * 16 color amiga-ish palette. */ uint8 new_palette[16 * 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 }; uint8 palette[32 * 3]; static uint16 cga_map[16] = { 0x0000, /* 0 - black */ 0x0d00, /* 1 - blue */ 0x0b00, /* 2 - green */ 0x0f00, /* 3 - cyan */ 0x000b, /* 4 - red */ 0x0b0d, /* 5 - magenta */ 0x000d, /* 6 - brown */ 0x0b0b, /* 7 - gray */ 0x0d0d, /* 8 - dark gray */ 0x0b0f, /* 9 - light blue */ 0x0b0d, /* 10 - light green */ 0x0f0d, /* 11 - light cyan */ 0x0f0d, /* 12 - light red */ 0x0f00, /* 13 - light magenta */ 0x0f0b, /* 14 - yellow */ 0x0f0f /* 15 - white */ }; struct update_block { int x1, y1; int x2, y2; }; static struct update_block update = { MAX_INT, MAX_INT, 0, 0 }; /* * Layer 4: 640x480? ================== User display * ^ * | do_update(), put_block() * | * Layer 3: 640x480? ================== Framebuffer * ^ * | flush_block(), put_pixels() * | * Layer 2: 320x200 ================== AGI engine screen (console), put_pixel() * | * Layer 1: 160x168 ================== AGI screen */ #define SHAKE_MAG 3 static uint8 *shake_h, *shake_v; void shake_start() { int i; if ((shake_h = (uint8 *)malloc(GFX_WIDTH * SHAKE_MAG)) == NULL) return; if ((shake_v = (uint8 *)malloc(SHAKE_MAG * (GFX_HEIGHT - SHAKE_MAG))) == NULL) { free(shake_h); return; } for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { memcpy(shake_v + i * SHAKE_MAG, agi_screen + i * GFX_WIDTH, SHAKE_MAG); } for (i = 0; i < SHAKE_MAG; i++) { memcpy(shake_h + i * GFX_WIDTH, agi_screen + i * GFX_WIDTH, GFX_WIDTH); } } void shake_screen(int n) { int i; if (n == 0) { for (i = 0; i < (GFX_HEIGHT - SHAKE_MAG); i++) { memmove(&agi_screen[GFX_WIDTH * i], &agi_screen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], GFX_WIDTH - SHAKE_MAG); } } else { for (i = GFX_HEIGHT - SHAKE_MAG - 1; i >= 0; i--) { memmove(&agi_screen[GFX_WIDTH * (i + SHAKE_MAG) + SHAKE_MAG], &agi_screen[GFX_WIDTH * i], GFX_WIDTH - SHAKE_MAG); } } } void shake_end() { int i; for (i = 0; i < GFX_HEIGHT - SHAKE_MAG; i++) { memcpy(agi_screen + i * GFX_WIDTH, shake_v + i * SHAKE_MAG, SHAKE_MAG); } for (i = 0; i < SHAKE_MAG; i++) { memcpy(agi_screen + i * GFX_WIDTH, shake_h + i * GFX_WIDTH, GFX_WIDTH); } flush_block(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); free(shake_v); free(shake_h); } 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; 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; agi_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 = &agi_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 = &agi_screen[x1 + y1 * GFX_WIDTH]; memset(p0, c1, w); /* bottom line */ p0 = &agi_screen[x1 + y2 * GFX_WIDTH]; memset(p0, c2, w); /* side lines */ for (y = y1; y <= y2; y++) { agi_screen[x1 + y * GFX_WIDTH] = c1; agi_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; 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); } 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); // redundant! already inside put_text_character! // flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); } /** * 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, const 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; while (*s) { put_text_character(0, x + (!!p), y + (!!p), *s++, a ? 15 : 0, a ? 0 : 15); x += CHAR_COLS; } x1 -= 2; y1 -= 2; x2 += 2; y2 += 2; flush_block(x1, y1, x2, y2); } int test_button(int x, int y, const 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 ((int)mouse.x >= x1 && (int)mouse.y >= y1 && (int)mouse.x <= x2 && (int)mouse.y <= y2) return true; return false; } void put_block(int x1, int y1, int x2, int y2) { gfx_putblock(x1, y1, x2, y2); } void put_screen() { put_block(0, 0, GFX_WIDTH - 1, GFX_HEIGHT - 1); } void poll_timer() { agi_timer_low(); } int get_key() { return agi_get_keypress_low(); } int keypress() { return agi_is_keypress_low(); } /* * Public functions */ /** * Initialize the color palette * This function initializes the color palette using the specified 16-color * RGB palette and creates 16 extra palette entries with translucent colors * for the interpreter console. * @param p A pointer to the 16-color RGB palette. */ void init_palette(uint8 *p) { int i; for (i = 0; i < 48; i++) { palette[i] = p[i]; palette[i + 48] = (p[i] + 0x30) >> 2; } } void gfx_set_palette() { int i; byte pal[32 * 4]; for (i = 0; i < 32; i++) { pal[i * 4 + 0] = palette[i * 3 + 0] << 2; pal[i * 4 + 1] = palette[i * 3 + 1] << 2; pal[i * 4 + 2] = palette[i * 3 + 2] << 2; pal[i * 4 + 3] = 0; } g_system->setPalette(pal, 0, 32); } /* put a block onto the screen */ void gfx_putblock(int x1, int y1, int x2, int y2) { 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; g_system->copyRectToScreen(screen + y1 * 320 + x1, 320, x1, y1, x2 - x1 + 1, y2 - y1 + 1); //g_system->copyRectToScreen(screen, 320, 0, 0, 320, 200); } static const byte mouseCursorArrow[] = { 0x00, 0x00, 0x40, 0x00, 0x60, 0x00, 0x70, 0x00, 0x78, 0x00, 0x7C, 0x00, 0x7E, 0x00, 0x7F, 0x00, 0x7F, 0x80, 0x7C, 0x00, 0x6C, 0x00, 0x46, 0x00, 0x06, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0xC0, 0x00, 0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0xFC, 0x00, 0xFE, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0xC0, 0xFF, 0xC0, 0xFE, 0x00, 0xFF, 0x00, 0xCF, 0x00, 0x07, 0x80, 0x07, 0x80, 0x03, 0x80 }; /** * Initialize graphics device. * * @see deinit_video() */ int init_video() { if (opt.renderMode == Common::kRenderEGA) init_palette(ega_palette); else init_palette(new_palette); if ((agi_screen = (uint8 *)calloc(GFX_WIDTH, GFX_HEIGHT)) == NULL) return err_NotEnoughMemory; gfx_set_palette(); byte mouseCursor[16 * 16]; const byte *src = mouseCursorArrow; for (int i = 0; i < 32; ++i) { int offs = i * 8; for (byte mask = 0x80; mask != 0; mask >>= 1) { if (src[0] & mask) { mouseCursor[offs] = 2; } else if (src[32] & mask) { mouseCursor[offs] = 0; } else { mouseCursor[offs] = 0xFF; } ++offs; } ++src; } CursorMan.replaceCursor(mouseCursor, 16, 16, 1, 1); return err_OK; } /** * Deinitialize graphics device. * * @see init_video() */ int deinit_video() { free(agi_screen); return err_OK; } int init_machine() { screen = (unsigned char *)malloc(320 * 200); clock_count = 0; clock_ticks = 0; return err_OK; } int deinit_machine() { free(screen); return err_OK; } /** * 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) { if (opt.renderMode == Common::kRenderCGA) { for (x *= 2; n--; p++, x += 2) { register uint16 q = (cga_map[(*p & 0xf0) >> 4] << 4) | cga_map[*p & 0x0f]; if (debug_.priority) q >>= 4; *(uint16 *)&agi_screen[x + y * GFX_WIDTH] = q & 0x0f0f; } } else { for (x *= 2; n--; p++, x += 2) { register uint16 q = ((uint16) * p << 8) | *p; if (debug_.priority) q >>= 4; *(uint16 *)&agi_screen[x + y * GFX_WIDTH] = q & 0x0f0f; } } } void put_pixels_hires(int x, int y, int n, uint8 *p) { //y += CHAR_LINES; for (; n--; p++, x++) { uint8 q = *p; if (debug_.priority) q >>= 4; agi_screen[x + y * GFX_WIDTH] = q & 0x0f; } } /** * 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_putblock(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 AGI engine screen. * This function updates a block in the output device with the contents of * the AGI engine 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 = &agi_screen[x1 + y1 * GFX_WIDTH]; w = x2 - x1 + 1; for (y = y1; y <= y2; y++) { memcpy(screen + 320 * y + x1, p0, w); 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 AGI engine 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(agi_screen, c, GFX_WIDTH * GFX_HEIGHT); flush_screen(); } /** * Save a block of the AGI engine screen */ void save_block(int x1, int y1, int x2, int y2, uint8 *b) { uint8 *p0; int w, h; p0 = &agi_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 AGI engine screen */ void restore_block(int x1, int y1, int x2, int y2, uint8 *b) { uint8 *p0; int w, h; p0 = &agi_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); } } // End of namespace Agi