1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/config-manager.h"
24 #include "common/file.h"
25 #include "common/textconsole.h"
26 #include "engines/util.h"
27
28 #include "graphics/cursorman.h"
29 #include "graphics/palette.h"
30
31 #include "agi/agi.h"
32 #include "agi/graphics.h"
33 #include "agi/mouse_cursor.h"
34 #include "agi/palette.h"
35 #include "agi/picture.h"
36 #include "agi/text.h"
37
38 namespace Agi {
39
40 #include "agi/font.h"
41
GfxMgr(AgiBase * vm,GfxFont * font)42 GfxMgr::GfxMgr(AgiBase *vm, GfxFont *font) : _vm(vm), _font(font) {
43 _agipalFileNum = 0;
44
45 memset(&_paletteGfxMode, 0, sizeof(_paletteGfxMode));
46 memset(&_paletteTextMode, 0, sizeof(_paletteTextMode));
47
48 memset(&_mouseCursor, 0, sizeof(_mouseCursor));
49 memset(&_mouseCursorBusy, 0, sizeof(_mouseCursorBusy));
50
51 initPriorityTable();
52
53 _renderStartVisualOffsetY = 0;
54 _renderStartDisplayOffsetY = 0;
55
56 _upscaledHires = DISPLAY_UPSCALED_DISABLED;
57 _displayScreenWidth = DISPLAY_DEFAULT_WIDTH;
58 _displayScreenHeight = DISPLAY_DEFAULT_HEIGHT;
59 _displayFontWidth = 8;
60 _displayFontHeight = 8;
61
62 _displayWidthMulAdjust = 0; // visualPos * (2+0) = displayPos
63 _displayHeightMulAdjust = 0; // visualPos * (1+0) = displayPos
64
65 _pixels = 0;
66 _displayPixels = 0;
67
68 _activeScreen = NULL;
69 _gameScreen = NULL;
70 _priorityScreen = NULL;
71 _displayScreen = NULL;
72 }
73
74 /**
75 * Initialize graphics device.
76 *
77 * @see deinit_video()
78 */
initVideo()79 int GfxMgr::initVideo() {
80 bool forceHires = false;
81
82 // Set up palettes
83 initPalette(_paletteTextMode, PALETTE_EGA);
84
85 switch (_vm->_renderMode) {
86 case Common::kRenderEGA:
87 initPalette(_paletteGfxMode, PALETTE_EGA);
88 break;
89 case Common::kRenderCGA:
90 initPalette(_paletteGfxMode, PALETTE_CGA, 4, 8);
91 break;
92 case Common::kRenderVGA:
93 initPalette(_paletteGfxMode, PALETTE_VGA, 256, 8);
94 break;
95 case Common::kRenderHercG:
96 initPalette(_paletteGfxMode, PALETTE_HERCULES_GREEN, 2, 8);
97 forceHires = true;
98 break;
99 case Common::kRenderHercA:
100 initPalette(_paletteGfxMode, PALETTE_HERCULES_AMBER, 2, 8);
101 forceHires = true;
102 break;
103 case Common::kRenderAmiga:
104 if (!ConfMan.getBool("altamigapalette")) {
105 // Set the correct Amiga palette depending on AGI interpreter version
106 if (_vm->getVersion() < 0x2936)
107 initPalette(_paletteGfxMode, PALETTE_AMIGA_V1, 16, 4);
108 else if (_vm->getVersion() == 0x2936)
109 initPalette(_paletteGfxMode, PALETTE_AMIGA_V2, 16, 4);
110 else if (_vm->getVersion() > 0x2936)
111 initPalette(_paletteGfxMode, PALETTE_AMIGA_V3, 16, 4);
112 } else {
113 // Set the old common alternative Amiga palette
114 initPalette(_paletteGfxMode, PALETTE_AMIGA_ALT);
115 }
116 break;
117 case Common::kRenderApple2GS:
118 switch (_vm->getGameID()) {
119 case GID_SQ1:
120 // Special one, only used for Space Quest 1 on Apple IIgs. Is the same as Amiga v1 palette
121 initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS_SQ1, 16, 4);
122 break;
123 default:
124 // Regular "standard" Apple IIgs palette, used by everything else
125 initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4);
126 break;
127 }
128 break;
129 case Common::kRenderAtariST:
130 initPalette(_paletteGfxMode, PALETTE_ATARI_ST, 16, 3);
131 break;
132 case Common::kRenderMacintosh:
133 switch (_vm->getGameID()) {
134 case GID_KQ3:
135 case GID_PQ1:
136 initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT, 16);
137 break;
138 case GID_GOLDRUSH:
139 // We use the common KQ3/PQ1 palette at the moment.
140 // It seems the Gold Rush palette, that came with the game is quite ugly.
141 initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT, 16);
142 break;
143 case GID_SQ2:
144 initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT3, 16);
145 break;
146 default:
147 initPaletteCLUT(_paletteGfxMode, PALETTE_MACINTOSH_CLUT3, 16);
148 break;
149 }
150 break;
151 default:
152 error("initVideo: unsupported render mode");
153 break;
154 }
155
156 //bool forcedUpscale = true;
157
158 if (_font->isFontHires() || forceHires) {
159 // Upscaling enable
160 _upscaledHires = DISPLAY_UPSCALED_640x400;
161 _displayScreenWidth = 640;
162 _displayScreenHeight = 400;
163 _displayFontWidth = 16;
164 _displayFontHeight = 16;
165
166 _displayWidthMulAdjust = 2;
167 _displayHeightMulAdjust = 1;
168 }
169
170 // set up mouse cursors
171 switch (_vm->_renderMode) {
172 case Common::kRenderEGA:
173 case Common::kRenderCGA:
174 case Common::kRenderVGA:
175 case Common::kRenderHercG:
176 case Common::kRenderHercA:
177 initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 0, 0);
178 initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8);
179 break;
180 case Common::kRenderAmiga:
181 initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 0, 0);
182 initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8);
183 break;
184 case Common::kRenderApple2GS:
185 // had no special busy mouse cursor
186 initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 0, 0);
187 initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8);
188 break;
189 case Common::kRenderAtariST:
190 initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 0, 0);
191 initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8);
192 break;
193 case Common::kRenderMacintosh:
194 // It looks like Atari ST + Macintosh used the same standard mouse cursor
195 // TODO: Verify by checking actual hardware
196 initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 0, 0);
197 initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_MACINTOSH_BUSY, 10, 14, 7, 8);
198 break;
199 default:
200 error("initVideo: unsupported render mode");
201 break;
202 }
203
204 _pixels = SCRIPT_WIDTH * SCRIPT_HEIGHT;
205 _gameScreen = (byte *)calloc(_pixels, 1);
206 _priorityScreen = (byte *)calloc(_pixels, 1);
207 _activeScreen = _gameScreen;
208 //_activeScreen = _priorityScreen;
209
210 _displayPixels = _displayScreenWidth * _displayScreenHeight;
211 _displayScreen = (byte *)calloc(_displayPixels, 1);
212
213 initGraphics(_displayScreenWidth, _displayScreenHeight);
214
215 setPalette(true); // set gfx-mode palette
216
217 // set up mouse cursor palette
218 CursorMan.replaceCursorPalette(MOUSECURSOR_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_PALETTE) / 3);
219 setMouseCursor();
220
221 return errOK;
222 }
223
224 /**
225 * Deinitialize graphics device.
226 *
227 * @see init_video()
228 */
deinitVideo()229 int GfxMgr::deinitVideo() {
230 // Free mouse cursors in case they were allocated
231 if (_mouseCursor.bitmapDataAllocated)
232 free(_mouseCursor.bitmapDataAllocated);
233 if (_mouseCursorBusy.bitmapDataAllocated)
234 free(_mouseCursorBusy.bitmapDataAllocated);
235
236 free(_displayScreen);
237 free(_gameScreen);
238 free(_priorityScreen);
239
240 return errOK;
241 }
242
setRenderStartOffset(uint16 offsetY)243 void GfxMgr::setRenderStartOffset(uint16 offsetY) {
244 if (offsetY >= (VISUAL_HEIGHT - SCRIPT_HEIGHT))
245 error("invalid render start offset");
246
247 _renderStartVisualOffsetY = offsetY;
248 _renderStartDisplayOffsetY = offsetY * (1 + _displayHeightMulAdjust);
249 }
getRenderStartDisplayOffsetY()250 uint16 GfxMgr::getRenderStartDisplayOffsetY() {
251 return _renderStartDisplayOffsetY;
252 }
253
254 // Translates a game screen coordinate to a display screen coordinate
255 // Game screen to 320x200 -> x * 2, y + renderStart
256 // Game screen to 640x400 -> x * 4, (y * 2) + renderStart
translateGamePosToDisplayScreen(int16 & x,int16 & y)257 void GfxMgr::translateGamePosToDisplayScreen(int16 &x, int16 &y) {
258 x = x * (2 + _displayWidthMulAdjust);
259 y = y * (1 + _displayHeightMulAdjust) + _renderStartDisplayOffsetY;
260 }
261
262 // Translates a visual coordinate to a display screen coordinate
263 // Visual to 320x200 -> x * 2, y
264 // Visual to 640x400 -> x * 4, y * 2
translateVisualPosToDisplayScreen(int16 & x,int16 & y)265 void GfxMgr::translateVisualPosToDisplayScreen(int16 &x, int16 &y) {
266 x = x * (2 + _displayWidthMulAdjust);
267 y = y * (1 + _displayHeightMulAdjust);
268 }
269
270 // Translates a display screen coordinate to a game screen coordinate
271 // Display screen to 320x200 -> x / 2, y - renderStart
272 // Display screen to 640x400 -> x / 4, (y / 2) - renderStart
translateDisplayPosToGameScreen(int16 & x,int16 & y)273 void GfxMgr::translateDisplayPosToGameScreen(int16 &x, int16 &y) {
274 y -= _renderStartDisplayOffsetY; // remove status bar line
275 x = x / (2 + _displayWidthMulAdjust);
276 y = y / (1 + _displayHeightMulAdjust);
277 if (y < 0)
278 y = 0;
279 if (y >= SCRIPT_HEIGHT)
280 y = SCRIPT_HEIGHT + 1; // 1 beyond
281 }
282
283 // Translates dimension from visual screen to display screen
translateVisualDimensionToDisplayScreen(int16 & width,int16 & height)284 void GfxMgr::translateVisualDimensionToDisplayScreen(int16 &width, int16 &height) {
285 width = width * (2 + _displayWidthMulAdjust);
286 height = height * (1 + _displayHeightMulAdjust);
287 }
288
289 // Translates dimension from display screen to visual screen
translateDisplayDimensionToVisualScreen(int16 & width,int16 & height)290 void GfxMgr::translateDisplayDimensionToVisualScreen(int16 &width, int16 &height) {
291 width = width / (2 + _displayWidthMulAdjust);
292 height = height / (1 + _displayHeightMulAdjust);
293 }
294
295 // Translates a rect from game screen to display screen
translateGameRectToDisplayScreen(int16 & x,int16 & y,int16 & width,int16 & height)296 void GfxMgr::translateGameRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) {
297 translateGamePosToDisplayScreen(x, y);
298 translateVisualDimensionToDisplayScreen(width, height);
299 }
300
301 // Translates a rect from visual screen to display screen
translateVisualRectToDisplayScreen(int16 & x,int16 & y,int16 & width,int16 & height)302 void GfxMgr::translateVisualRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) {
303 translateVisualPosToDisplayScreen(x, y);
304 translateVisualDimensionToDisplayScreen(width, height);
305 }
306
getDisplayOffsetToGameScreenPos(int16 x,int16 y)307 uint32 GfxMgr::getDisplayOffsetToGameScreenPos(int16 x, int16 y) {
308 translateGamePosToDisplayScreen(x, y);
309 return (y * _displayScreenWidth) + x;
310 }
311
getDisplayOffsetToVisualScreenPos(int16 x,int16 y)312 uint32 GfxMgr::getDisplayOffsetToVisualScreenPos(int16 x, int16 y) {
313 translateVisualPosToDisplayScreen(x, y);
314 return (y * _displayScreenWidth) + x;
315 }
316
317 // Attention: uses display screen coordinates!
copyDisplayRectToScreen(int16 x,int16 y,int16 width,int16 height)318 void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) {
319 // Clamp to sane values to prevent off screen blits causing exceptions in backend
320 // FIXME: Add warnings / debug of clamping?
321 width = CLIP<int16>(width, 0, _displayScreenWidth);
322 height = CLIP<int16>(height, 0, _displayScreenHeight);
323 x = CLIP<int16>(x, 0, _displayScreenWidth-width);
324 y = CLIP<int16>(y, 0, _displayScreenHeight-height);
325
326 g_system->copyRectToScreen(_displayScreen + y * _displayScreenWidth + x, _displayScreenWidth, x, y, width, height);
327 }
copyDisplayRectToScreen(int16 x,int16 adjX,int16 y,int16 adjY,int16 width,int16 adjWidth,int16 height,int16 adjHeight)328 void GfxMgr::copyDisplayRectToScreen(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight) {
329 switch (_upscaledHires) {
330 case DISPLAY_UPSCALED_DISABLED:
331 break;
332 case DISPLAY_UPSCALED_640x400:
333 adjX *= 2; adjY *= 2;
334 adjWidth *= 2; adjHeight *= 2;
335 break;
336 default:
337 assert(0);
338 break;
339 }
340 x += adjX; y += adjY;
341 width += adjWidth; height += adjHeight;
342 g_system->copyRectToScreen(_displayScreen + y * _displayScreenWidth + x, _displayScreenWidth, x, y, width, height);
343 }
copyDisplayRectToScreenUsingGamePos(int16 x,int16 y,int16 width,int16 height)344 void GfxMgr::copyDisplayRectToScreenUsingGamePos(int16 x, int16 y, int16 width, int16 height) {
345 translateGameRectToDisplayScreen(x, y, width, height);
346 g_system->copyRectToScreen(_displayScreen + (y * _displayScreenWidth) + x, _displayScreenWidth, x, y, width, height);
347 }
copyDisplayRectToScreenUsingVisualPos(int16 x,int16 y,int16 width,int16 height)348 void GfxMgr::copyDisplayRectToScreenUsingVisualPos(int16 x, int16 y, int16 width, int16 height) {
349 translateVisualRectToDisplayScreen(x, y, width, height);
350 g_system->copyRectToScreen(_displayScreen + (y * _displayScreenWidth) + x, _displayScreenWidth, x, y, width, height);
351 }
copyDisplayToScreen()352 void GfxMgr::copyDisplayToScreen() {
353 g_system->copyRectToScreen(_displayScreen, _displayScreenWidth, 0, 0, _displayScreenWidth, _displayScreenHeight);
354 }
355
translateFontPosToDisplayScreen(int16 & x,int16 & y)356 void GfxMgr::translateFontPosToDisplayScreen(int16 &x, int16 &y) {
357 x *= _displayFontWidth;
358 y *= _displayFontHeight;
359 }
translateDisplayPosToFontScreen(int16 & x,int16 & y)360 void GfxMgr::translateDisplayPosToFontScreen(int16 &x, int16 &y) {
361 x /= _displayFontWidth;
362 y /= _displayFontHeight;
363 }
translateFontDimensionToDisplayScreen(int16 & width,int16 & height)364 void GfxMgr::translateFontDimensionToDisplayScreen(int16 &width, int16 &height) {
365 width *= _displayFontWidth;
366 height *= _displayFontHeight;
367 }
translateFontRectToDisplayScreen(int16 & x,int16 & y,int16 & width,int16 & height)368 void GfxMgr::translateFontRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) {
369 translateFontPosToDisplayScreen(x, y);
370 translateFontDimensionToDisplayScreen(width, height);
371 }
getFontRectForDisplayScreen(int16 column,int16 row,int16 width,int16 height)372 Common::Rect GfxMgr::getFontRectForDisplayScreen(int16 column, int16 row, int16 width, int16 height) {
373 Common::Rect displayRect(width * _displayFontWidth, height * _displayFontHeight);
374 displayRect.moveTo(column * _displayFontWidth, row * _displayFontHeight);
375 return displayRect;
376 }
377
debugShowMap(int mapNr)378 void GfxMgr::debugShowMap(int mapNr) {
379 switch (mapNr) {
380 case 0:
381 _activeScreen = _gameScreen;
382 break;
383 case 1:
384 _activeScreen = _priorityScreen;
385 break;
386 default:
387 break;
388 }
389
390 render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT);
391 }
392
clear(byte color,byte priority)393 void GfxMgr::clear(byte color, byte priority) {
394 memset(_gameScreen, color, _pixels);
395 memset(_priorityScreen, priority, _pixels);
396 }
397
clearDisplay(byte color,bool copyToScreen)398 void GfxMgr::clearDisplay(byte color, bool copyToScreen) {
399 memset(_displayScreen, color, _displayPixels);
400
401 if (copyToScreen) {
402 copyDisplayToScreen();
403 }
404 }
405
putPixel(int16 x,int16 y,byte drawMask,byte color,byte priority)406 void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority) {
407 int offset = y * SCRIPT_WIDTH + x;
408
409 if (drawMask & GFX_SCREEN_MASK_VISUAL) {
410 _gameScreen[offset] = color;
411 }
412 if (drawMask & GFX_SCREEN_MASK_PRIORITY) {
413 _priorityScreen[offset] = priority;
414 }
415 }
416
putPixelOnDisplay(int16 x,int16 y,byte color)417 void GfxMgr::putPixelOnDisplay(int16 x, int16 y, byte color) {
418 uint32 offset = 0;
419
420 switch (_upscaledHires) {
421 case DISPLAY_UPSCALED_DISABLED:
422 offset = y * _displayScreenWidth + x;
423
424 _displayScreen[offset] = color;
425 break;
426 case DISPLAY_UPSCALED_640x400:
427 offset = (y * _displayScreenWidth) + x;
428
429 _displayScreen[offset + 0] = color;
430 _displayScreen[offset + 1] = color;
431 _displayScreen[offset + _displayScreenWidth + 0] = color;
432 _displayScreen[offset + _displayScreenWidth + 1] = color;
433 break;
434 default:
435 break;
436 }
437 }
438
putPixelOnDisplay(int16 x,int16 adjX,int16 y,int16 adjY,byte color)439 void GfxMgr::putPixelOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, byte color) {
440 switch (_upscaledHires) {
441 case DISPLAY_UPSCALED_DISABLED:
442 break;
443 case DISPLAY_UPSCALED_640x400:
444 adjX *= 2; adjY *= 2;
445 break;
446 default:
447 assert(0);
448 break;
449 }
450 x += adjX;
451 y += adjY;
452 putPixelOnDisplay(x, y, color);
453 }
454
putFontPixelOnDisplay(int16 baseX,int16 baseY,int16 addX,int16 addY,byte color,bool isHires)455 void GfxMgr::putFontPixelOnDisplay(int16 baseX, int16 baseY, int16 addX, int16 addY, byte color, bool isHires) {
456 uint32 offset = 0;
457
458 switch (_upscaledHires) {
459 case DISPLAY_UPSCALED_DISABLED:
460 offset = ((baseY + addY) * _displayScreenWidth) + (baseX + addX);
461 _displayScreen[offset] = color;
462 break;
463 case DISPLAY_UPSCALED_640x400:
464 if (isHires) {
465 offset = ((baseY + addY) * _displayScreenWidth) + (baseX + addX);
466 _displayScreen[offset] = color;
467 } else {
468 offset = ((baseY + addY * 2) * _displayScreenWidth) + (baseX + addX * 2);
469 _displayScreen[offset + 0] = color;
470 _displayScreen[offset + 1] = color;
471 _displayScreen[offset + _displayScreenWidth + 0] = color;
472 _displayScreen[offset + _displayScreenWidth + 1] = color;
473 }
474 break;
475 default:
476 break;
477 }
478 }
479
getColor(int16 x,int16 y)480 byte GfxMgr::getColor(int16 x, int16 y) {
481 int offset = y * SCRIPT_WIDTH + x;
482
483 return _gameScreen[offset];
484 }
485
getPriority(int16 x,int16 y)486 byte GfxMgr::getPriority(int16 x, int16 y) {
487 int offset = y * SCRIPT_WIDTH + x;
488
489 return _priorityScreen[offset];
490 }
491
492 // used, when a control pixel is found
493 // will search downwards and compare priority in case any is found
checkControlPixel(int16 x,int16 y,byte viewPriority)494 bool GfxMgr::checkControlPixel(int16 x, int16 y, byte viewPriority) {
495 int offset = y * SCRIPT_WIDTH + x;
496 byte curPriority;
497
498 while (1) {
499 y++;
500 offset += SCRIPT_WIDTH;
501 if (y >= SCRIPT_HEIGHT) {
502 // end of screen, nothing but control pixels found
503 return true; // draw view pixel
504 }
505 curPriority = _priorityScreen[offset];
506 if (curPriority > 2) // valid priority found?
507 break;
508 }
509 if (curPriority <= viewPriority)
510 return true; // view priority is higher, draw
511 return false; // view priority is lower, don't draw
512 }
513
514 static byte CGA_MixtureColorTable[] = {
515 0x00, 0x08, 0x04, 0x0C, 0x01, 0x09, 0x02, 0x05,
516 0x0A, 0x0D, 0x06, 0x0E, 0x0B, 0x03, 0x07, 0x0F
517 };
518
getCGAMixtureColor(byte color)519 byte GfxMgr::getCGAMixtureColor(byte color) {
520 return CGA_MixtureColorTable[color & 0x0F];
521 }
522
523 // Attention: in our implementation, y-coordinate is upper left.
524 // Sierra passed the lower left instead. We changed it to make upscaling easier.
render_Block(int16 x,int16 y,int16 width,int16 height,bool copyToScreen)525 void GfxMgr::render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
526 if (!render_Clip(x, y, width, height))
527 return;
528
529 switch (_vm->_renderMode) {
530 case Common::kRenderHercG:
531 case Common::kRenderHercA:
532 render_BlockHercules(x, y, width, height, copyToScreen);
533 break;
534 case Common::kRenderCGA:
535 render_BlockCGA(x, y, width, height, copyToScreen);
536 break;
537 case Common::kRenderEGA:
538 default:
539 render_BlockEGA(x, y, width, height, copyToScreen);
540 break;
541 }
542
543 if (copyToScreen) {
544 copyDisplayRectToScreenUsingGamePos(x, y, width, height);
545 }
546 }
547
render_Clip(int16 & x,int16 & y,int16 & width,int16 & height,int16 clipAgainstWidth,int16 clipAgainstHeight)548 bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth, int16 clipAgainstHeight) {
549 if ((x >= clipAgainstWidth) || ((x + width - 1) < 0) ||
550 (y < 0) || ((y + (height - 1)) >= clipAgainstHeight)) {
551 return false;
552 }
553
554 if (y < 0) {
555 height += y;
556 y = 0;
557 }
558
559 if ((y + height - 1) >= clipAgainstHeight) {
560 height = clipAgainstHeight - y;
561 }
562
563 #if 0
564 if ((y - height + 1) < 0)
565 height = y + 1;
566
567 if (y >= clipAgainstHeight) {
568 height -= y - (clipAgainstHeight - 1);
569 y = clipAgainstHeight - 1;
570 }
571 #endif
572
573 if (x < 0) {
574 width += x;
575 x = 0;
576 }
577
578 if ((x + width - 1) >= clipAgainstWidth) {
579 width = clipAgainstWidth - x;
580 }
581 return true;
582 }
583
render_BlockEGA(int16 x,int16 y,int16 width,int16 height,bool copyToScreen)584 void GfxMgr::render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
585 uint32 offsetVisual = SCRIPT_WIDTH * y + x;
586 uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y);
587 int16 remainingWidth = width;
588 int16 remainingHeight = height;
589 byte curColor = 0;
590 int16 displayWidth = width * (2 + _displayWidthMulAdjust);
591
592 while (remainingHeight) {
593 remainingWidth = width;
594
595 switch (_upscaledHires) {
596 case DISPLAY_UPSCALED_DISABLED:
597 while (remainingWidth) {
598 curColor = _activeScreen[offsetVisual++];
599 _displayScreen[offsetDisplay++] = curColor;
600 _displayScreen[offsetDisplay++] = curColor;
601 remainingWidth--;
602 }
603 break;
604 case DISPLAY_UPSCALED_640x400:
605 while (remainingWidth) {
606 curColor = _activeScreen[offsetVisual++];
607 memset(&_displayScreen[offsetDisplay], curColor, 4);
608 memset(&_displayScreen[offsetDisplay + _displayScreenWidth], curColor, 4);
609 offsetDisplay += 4;
610 remainingWidth--;
611 }
612 break;
613 default:
614 assert(0);
615 break;
616 }
617
618 offsetVisual += SCRIPT_WIDTH - width;
619 offsetDisplay += _displayScreenWidth - displayWidth;
620
621 switch (_upscaledHires) {
622 case DISPLAY_UPSCALED_640x400:
623 offsetDisplay += _displayScreenWidth;
624 break;
625 default:
626 break;
627 }
628
629 remainingHeight--;
630 }
631 }
632
render_BlockCGA(int16 x,int16 y,int16 width,int16 height,bool copyToScreen)633 void GfxMgr::render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
634 uint32 offsetVisual = SCRIPT_WIDTH * y + x;
635 uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y);
636 int16 remainingWidth = width;
637 int16 remainingHeight = height;
638 byte curColor = 0;
639 int16 displayWidth = width * (2 + _displayWidthMulAdjust);
640
641 while (remainingHeight) {
642 remainingWidth = width;
643
644 switch (_upscaledHires) {
645 case DISPLAY_UPSCALED_DISABLED:
646 while (remainingWidth) {
647 curColor = _activeScreen[offsetVisual++];
648 _displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture
649 _displayScreen[offsetDisplay++] = curColor >> 2;
650 remainingWidth--;
651 }
652 break;
653 case DISPLAY_UPSCALED_640x400:
654 while (remainingWidth) {
655 curColor = _activeScreen[offsetVisual++];
656 _displayScreen[offsetDisplay + 0] = curColor & 0x03; // we process CGA mixture
657 _displayScreen[offsetDisplay + 1] = curColor >> 2;
658 _displayScreen[offsetDisplay + 2] = curColor & 0x03;
659 _displayScreen[offsetDisplay + 3] = curColor >> 2;
660 _displayScreen[offsetDisplay + _displayScreenWidth + 0] = curColor & 0x03;
661 _displayScreen[offsetDisplay + _displayScreenWidth + 1] = curColor >> 2;
662 _displayScreen[offsetDisplay + _displayScreenWidth + 2] = curColor & 0x03;
663 _displayScreen[offsetDisplay + _displayScreenWidth + 3] = curColor >> 2;
664 offsetDisplay += 4;
665 remainingWidth--;
666 }
667 break;
668 default:
669 assert(0);
670 break;
671 }
672
673 offsetVisual += SCRIPT_WIDTH - width;
674 offsetDisplay += _displayScreenWidth - displayWidth;
675
676 switch (_upscaledHires) {
677 case DISPLAY_UPSCALED_640x400:
678 offsetDisplay += _displayScreenWidth;
679 break;
680 default:
681 break;
682 }
683
684 remainingHeight--;
685 }
686 }
687
688 static const uint8 herculesColorMapping[] = {
689 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
690 0x88, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
691 0x80, 0x10, 0x02, 0x20, 0x01, 0x08, 0x40, 0x04,
692 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00,
693 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88,
694 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00,
695 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88,
696 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
697 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00,
698 0xD7, 0xFF, 0x7D, 0xFF, 0xD7, 0xFF, 0x7D, 0xFF,
699 0xDD, 0x55, 0x77, 0xAA, 0xDD, 0x55, 0x77, 0xAA,
700 0x7F, 0xEF, 0xFD, 0xDF, 0xFE, 0xF7, 0xBF, 0xFB,
701 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF,
702 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE,
703 0x77, 0xFF, 0xFF, 0xFF, 0xDD, 0xFF, 0xFF, 0xFF,
704 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
705 };
706
707 // Sierra actually seems to have rendered the whole screen all the time
render_BlockHercules(int16 x,int16 y,int16 width,int16 height,bool copyToScreen)708 void GfxMgr::render_BlockHercules(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) {
709 uint32 offsetVisual = SCRIPT_WIDTH * y + x;
710 uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y);
711 int16 remainingWidth = width;
712 int16 remainingHeight = height;
713 byte curColor = 0;
714 int16 displayWidth = width * (2 + _displayWidthMulAdjust);
715
716 assert(_upscaledHires == DISPLAY_UPSCALED_640x400);
717
718 uint16 lookupOffset1 = (y * 2 & 0x07);
719 uint16 lookupOffset2 = 0;
720 bool getUpperNibble = false;
721 byte herculesColors1 = 0;
722 byte herculesColors2 = 0;
723
724 while (remainingHeight) {
725 remainingWidth = width;
726
727 lookupOffset1 = (lookupOffset1 + 0) & 0x07;
728 lookupOffset2 = (lookupOffset1 + 1) & 0x07;
729
730 getUpperNibble = (x & 1) ? false : true;
731 while (remainingWidth) {
732 curColor = _activeScreen[offsetVisual++] & 0x0F;
733
734 if (getUpperNibble) {
735 herculesColors1 = herculesColorMapping[curColor * 8 + lookupOffset1] & 0x0F;
736 herculesColors2 = herculesColorMapping[curColor * 8 + lookupOffset2] & 0x0F;
737 } else {
738 herculesColors1 = herculesColorMapping[curColor * 8 + lookupOffset1] >> 4;
739 herculesColors2 = herculesColorMapping[curColor * 8 + lookupOffset2] >> 4;
740 }
741 getUpperNibble ^= true;
742
743 _displayScreen[offsetDisplay + 0] = (herculesColors1 & 0x08) ? 1 : 0;
744 _displayScreen[offsetDisplay + 1] = (herculesColors1 & 0x04) ? 1 : 0;
745 _displayScreen[offsetDisplay + 2] = (herculesColors1 & 0x02) ? 1 : 0;
746 _displayScreen[offsetDisplay + 3] = (herculesColors1 & 0x01) ? 1 : 0;
747
748 _displayScreen[offsetDisplay + _displayScreenWidth + 0] = (herculesColors2 & 0x08) ? 1 : 0;
749 _displayScreen[offsetDisplay + _displayScreenWidth + 1] = (herculesColors2 & 0x04) ? 1 : 0;
750 _displayScreen[offsetDisplay + _displayScreenWidth + 2] = (herculesColors2 & 0x02) ? 1 : 0;
751 _displayScreen[offsetDisplay + _displayScreenWidth + 3] = (herculesColors2 & 0x01) ? 1 : 0;
752
753 offsetDisplay += 4;
754 remainingWidth--;
755 }
756
757 lookupOffset1 += 2;
758
759 offsetVisual += SCRIPT_WIDTH - width;
760 offsetDisplay += _displayScreenWidth - displayWidth;
761 offsetDisplay += _displayScreenWidth;
762
763 remainingHeight--;
764 }
765 }
766
767 // Table used for at least Manhunter 2, it renders 2 lines -> 3 lines instead of 4
768 // Manhunter 1 is shipped with a broken Hercules font
769 // King's Quest 4 aborts right at the start, when Hercules rendering is active
770 #if 0
771 static const uint8 herculesCoordinateOffset[] = {
772 0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x01, 0x02,
773 0x04, 0x05, 0x07, 0x00, 0x02, 0x03, 0x05, 0x06
774 };
775
776 static const uint8 herculesColorMapping[] = {
777 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x40, 0x00, 0x08, 0x00,
778 0x80, 0x10, 0x02, 0x20, 0x01, 0x08, 0x40, 0x04, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00,
779 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00,
780 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
781 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0xD7, 0xFF, 0x7D, 0xFF, 0xD7, 0xFF, 0x7D, 0xFF,
782 0xDD, 0x55, 0x77, 0xAA, 0xDD, 0x55, 0x77, 0xAA, 0x7F, 0xEF, 0xFD, 0xDF, 0xFE, 0xF7, 0xBF, 0xFB,
783 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE,
784 0x7F, 0xEF, 0xFB, 0xBF, 0xEF, 0xFE, 0xBF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
785 };
786 #endif
787
transition_Amiga()788 void GfxMgr::transition_Amiga() {
789 uint16 screenPos = 1;
790 uint32 screenStepPos = 1;
791 int16 posY = 0, posX = 0;
792 int16 stepCount = 0;
793
794 // disable mouse while transition is taking place
795 if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) {
796 CursorMan.showMouse(false);
797 }
798
799 do {
800 if (screenPos & 1) {
801 screenPos = screenPos >> 1;
802 screenPos = screenPos ^ 0x3500; // 13568d
803 } else {
804 screenPos = screenPos >> 1;
805 }
806
807 if ((screenPos < 13440) && (screenPos & 1)) {
808 screenStepPos = screenPos >> 1;
809 posY = screenStepPos / SCRIPT_WIDTH;
810 posX = screenStepPos - (posY * SCRIPT_WIDTH);
811
812 // Adjust to only update the game screen, not the status bar
813 translateGamePosToDisplayScreen(posX, posY);
814
815 switch (_upscaledHires) {
816 case DISPLAY_UPSCALED_DISABLED:
817 for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) {
818 screenStepPos = (posY * _displayScreenWidth) + posX;
819 g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 2, 1);
820 posY += 42;
821 }
822 break;
823 case DISPLAY_UPSCALED_640x400:
824 for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) {
825 screenStepPos = (posY * _displayScreenWidth) + posX;
826 g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 4, 2);
827 posY += 42 * 2;
828 }
829 break;
830 default:
831 assert(0);
832 break;
833 }
834
835 stepCount++;
836 if (stepCount == 220) {
837 // 30 times for the whole transition, so should take around 0.5 seconds
838 g_system->updateScreen();
839 g_system->delayMillis(16);
840 stepCount = 0;
841 }
842 }
843 } while (screenPos != 1);
844
845 // Enable mouse again
846 if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) {
847 CursorMan.showMouse(true);
848 }
849
850 g_system->updateScreen();
851 }
852
853 // This transition code was not reverse engineered, but created based on the Amiga transition code
854 // Atari ST definitely had a hi-res transition using the full resolution unlike the Amiga transition.
transition_AtariSt()855 void GfxMgr::transition_AtariSt() {
856 uint16 screenPos = 1;
857 uint32 screenStepPos = 1;
858 int16 posY = 0, posX = 0;
859 int16 stepCount = 0;
860
861 // disable mouse while transition is taking place
862 if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) {
863 CursorMan.showMouse(false);
864 }
865
866 do {
867 if (screenPos & 1) {
868 screenPos = screenPos >> 1;
869 screenPos = screenPos ^ 0x3500; // 13568d
870 } else {
871 screenPos = screenPos >> 1;
872 }
873
874 if ((screenPos < 13440) && (screenPos & 1)) {
875 screenStepPos = screenPos >> 1;
876 posY = screenStepPos / DISPLAY_DEFAULT_WIDTH;
877 posX = screenStepPos - (posY * DISPLAY_DEFAULT_WIDTH);
878
879 switch (_upscaledHires) {
880 case DISPLAY_UPSCALED_DISABLED:
881 posY += _renderStartDisplayOffsetY; // adjust to only update the main area, not the status bar
882 for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) {
883 screenStepPos = (posY * _displayScreenWidth) + posX;
884 g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 1, 1);
885 posY += 21;
886 }
887 break;
888 case DISPLAY_UPSCALED_640x400:
889 posX *= 2; posY *= 2;
890 posY += _renderStartDisplayOffsetY; // adjust to only update the main area, not the status bar
891 for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) {
892 screenStepPos = (posY * _displayScreenWidth) + posX;
893 g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 2, 2);
894 posY += 21 * 2;
895 }
896 break;
897 default:
898 break;
899 }
900
901 stepCount++;
902 if (stepCount == 168) {
903 // 40 times for the whole transition, so should take around 0.7 seconds
904 // When using an Atari ST emulator, the transition seems to be even slower than this
905 // TODO: should get checked on real hardware
906 g_system->updateScreen();
907 g_system->delayMillis(16);
908 stepCount = 0;
909 }
910 }
911 } while (screenPos != 1);
912
913 // Enable mouse again
914 if ((_vm->_game.mouseEnabled) && (!_vm->_game.mouseHidden)) {
915 CursorMan.showMouse(true);
916 }
917
918 g_system->updateScreen();
919 }
920
921 // Attention: y coordinate is here supposed to be the upper one!
block_save(int16 x,int16 y,int16 width,int16 height,byte * bufferPtr)922 void GfxMgr::block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) {
923 int16 startOffset = y * SCRIPT_WIDTH + x;
924 int16 offset = startOffset;
925 int16 remainingHeight = height;
926 byte *curBufferPtr = bufferPtr;
927
928 //warning("block_save: %d, %d -> %d, %d", x, y, width, height);
929
930 while (remainingHeight) {
931 memcpy(curBufferPtr, _gameScreen + offset, width);
932 offset += SCRIPT_WIDTH;
933 curBufferPtr += width;
934 remainingHeight--;
935 }
936
937 remainingHeight = height;
938 offset = startOffset;
939 while (remainingHeight) {
940 memcpy(curBufferPtr, _priorityScreen + offset, width);
941 offset += SCRIPT_WIDTH;
942 curBufferPtr += width;
943 remainingHeight--;
944 }
945 }
946
947 // Attention: y coordinate is here supposed to be the upper one!
block_restore(int16 x,int16 y,int16 width,int16 height,byte * bufferPtr)948 void GfxMgr::block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr) {
949 int16 startOffset = y * SCRIPT_WIDTH + x;
950 int16 offset = startOffset;
951 int16 remainingHeight = height;
952 byte *curBufferPtr = bufferPtr;
953
954 //warning("block_restore: %d, %d -> %d, %d", x, y, width, height);
955
956 while (remainingHeight) {
957 memcpy(_gameScreen + offset, curBufferPtr, width);
958 offset += SCRIPT_WIDTH;
959 curBufferPtr += width;
960 remainingHeight--;
961 }
962
963 remainingHeight = height;
964 offset = startOffset;
965 while (remainingHeight) {
966 memcpy(_priorityScreen + offset, curBufferPtr, width);
967 offset += SCRIPT_WIDTH;
968 curBufferPtr += width;
969 remainingHeight--;
970 }
971 }
972
973 // coordinates are for visual screen, but are supposed to point somewhere inside the playscreen
974 // x, y is the upper left. Sierra passed them as lower left. We change that to make upscaling easier.
975 // attention: Clipping is done here against 160x200 instead of 160x168
976 // Original interpreter didn't do any clipping, we do it for security.
977 // Clipping against the regular script width/height must not be done,
978 // because at least during the intro one message box goes beyond playscreen
979 // Going beyond 160x168 will result in messageboxes not getting fully removed
980 // In KQ4's case, the scripts clear the screen that's why it works.
drawBox(int16 x,int16 y,int16 width,int16 height,byte backgroundColor,byte lineColor)981 void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor) {
982 if (!render_Clip(x, y, width, height, VISUAL_WIDTH, VISUAL_HEIGHT - _renderStartVisualOffsetY))
983 return;
984
985 // coordinate translation: visual-screen -> display-screen
986 translateVisualRectToDisplayScreen(x, y, width, height);
987
988 y = y + _renderStartDisplayOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen
989
990 // draw box background
991 drawDisplayRect(x, y, width, height, backgroundColor);
992
993 // draw lines
994 switch (_vm->_renderMode) {
995 case Common::kRenderApple2GS:
996 case Common::kRenderAmiga:
997 // Slightly different window frame, and actually using 1-pixel width, which is "hi-res"
998 drawDisplayRect(x, +2, y, +2, width, -4, 0, 1, lineColor);
999 drawDisplayRect(x + width, -3, y, +2, 0, 1, height, -4, lineColor);
1000 drawDisplayRect(x, +2, y + height, -3, width, -4, 0, 1, lineColor);
1001 drawDisplayRect(x, +2, y, +2, 0, 1, height, -4, lineColor);
1002 break;
1003 case Common::kRenderMacintosh:
1004 // 1 pixel between box and frame lines. Frame lines were black
1005 drawDisplayRect(x, +1, y, +1, width, -2, 0, 1, 0);
1006 drawDisplayRect(x + width, -2, y, +1, 0, 1, height, -2, 0);
1007 drawDisplayRect(x, +1, y + height, -2, width, -2, 0, 1, 0);
1008 drawDisplayRect(x, +1, y, +1, 0, 1, height, -2, 0);
1009 break;
1010 case Common::kRenderHercA:
1011 case Common::kRenderHercG:
1012 lineColor = 0; // change linecolor to black
1013 // fall through
1014 case Common::kRenderCGA:
1015 case Common::kRenderEGA:
1016 case Common::kRenderVGA:
1017 case Common::kRenderAtariST:
1018 default:
1019 drawDisplayRect(x, +2, y, +1, width, -4, 0, 1, lineColor);
1020 drawDisplayRect(x + width, -4, y, +2, 0, 2, height, -4, lineColor);
1021 drawDisplayRect(x, +2, y + height, -2, width, -4, 0, 1, lineColor);
1022 drawDisplayRect(x, +2, y, +2, 0, 2, height, -4, lineColor);
1023 break;
1024 }
1025 }
1026
1027 // coordinates are directly for display screen
drawDisplayRect(int16 x,int16 y,int16 width,int16 height,byte color,bool copyToScreen)1028 void GfxMgr::drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color, bool copyToScreen) {
1029 switch (_vm->_renderMode) {
1030 case Common::kRenderCGA:
1031 drawDisplayRectCGA(x, y, width, height, color);
1032 break;
1033 case Common::kRenderHercG:
1034 case Common::kRenderHercA:
1035 if (color)
1036 color = 1; // change any color except black to green/amber
1037 // fall through
1038 case Common::kRenderEGA:
1039 default:
1040 drawDisplayRectEGA(x, y, width, height, color);
1041 break;
1042 }
1043 if (copyToScreen) {
1044 copyDisplayRectToScreen(x, y, width, height);
1045 }
1046 }
1047
drawDisplayRect(int16 x,int16 adjX,int16 y,int16 adjY,int16 width,int16 adjWidth,int16 height,int16 adjHeight,byte color,bool copyToScreen)1048 void GfxMgr::drawDisplayRect(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight, byte color, bool copyToScreen) {
1049 switch (_upscaledHires) {
1050 case DISPLAY_UPSCALED_DISABLED:
1051 x += adjX; y += adjY;
1052 width += adjWidth; height += adjHeight;
1053 break;
1054 case DISPLAY_UPSCALED_640x400:
1055 x += adjX * 2; y += adjY * 2;
1056 width += adjWidth * 2; height += adjHeight * 2;
1057 break;
1058 default:
1059 assert(0);
1060 break;
1061 }
1062 drawDisplayRect(x, y, width, height, color, copyToScreen);
1063 }
1064
drawDisplayRectEGA(int16 x,int16 y,int16 width,int16 height,byte color)1065 void GfxMgr::drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color) {
1066 uint32 offsetDisplay = (y * _displayScreenWidth) + x;
1067 int16 remainingHeight = height;
1068
1069 while (remainingHeight) {
1070 memset(_displayScreen + offsetDisplay, color, width);
1071
1072 offsetDisplay += _displayScreenWidth;
1073 remainingHeight--;
1074 }
1075 }
1076
drawDisplayRectCGA(int16 x,int16 y,int16 width,int16 height,byte color)1077 void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color) {
1078 uint32 offsetDisplay = (y * _displayScreenWidth) + x;
1079 int16 remainingHeight = height;
1080 int16 remainingWidth = width;
1081 byte CGAMixtureColor = getCGAMixtureColor(color);
1082 byte *displayScreen = nullptr;
1083
1084 // we should never get an uneven width
1085 assert((width & 1) == 0);
1086
1087 while (remainingHeight) {
1088 remainingWidth = width;
1089
1090 // set up pointer
1091 displayScreen = _displayScreen + offsetDisplay;
1092
1093 while (remainingWidth) {
1094 *displayScreen++ = CGAMixtureColor & 0x03;
1095 *displayScreen++ = CGAMixtureColor >> 2;
1096 remainingWidth -= 2;
1097 }
1098
1099 offsetDisplay += _displayScreenWidth;
1100 remainingHeight--;
1101 }
1102 }
1103
1104 // row + column are text-coordinates
drawCharacter(int16 row,int16 column,byte character,byte foreground,byte background,bool disabledLook)1105 void GfxMgr::drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook) {
1106 int16 x = column;
1107 int16 y = row;
1108 byte transformXOR = 0;
1109 byte transformOR = 0;
1110
1111 translateFontPosToDisplayScreen(x, y);
1112
1113 // Now figure out, if special handling needs to be done
1114 if (_vm->_game.gfxMode) {
1115 if (background & 0x08) {
1116 // invert enabled
1117 background &= 0x07; // remove invert bit
1118 transformXOR = 0xFF;
1119 }
1120 if (disabledLook) {
1121 transformOR = 0x55;
1122 }
1123 }
1124
1125 drawCharacterOnDisplay(x, y, character, foreground, background, transformXOR, transformOR);
1126 }
1127
1128 // only meant for internal use (SystemUI)
drawStringOnDisplay(int16 x,int16 y,const char * text,byte foregroundColor,byte backgroundColor)1129 void GfxMgr::drawStringOnDisplay(int16 x, int16 y, const char *text, byte foregroundColor, byte backgroundColor) {
1130 while (*text) {
1131 drawCharacterOnDisplay(x, y, *text, foregroundColor, backgroundColor);
1132 text++;
1133 x += _displayFontWidth;
1134 }
1135 }
1136
drawStringOnDisplay(int16 x,int16 adjX,int16 y,int16 adjY,const char * text,byte foregroundColor,byte backgroundColor)1137 void GfxMgr::drawStringOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, const char *text, byte foregroundColor, byte backgroundColor) {
1138 switch (_upscaledHires) {
1139 case DISPLAY_UPSCALED_DISABLED:
1140 x += adjX;
1141 y += adjY;
1142 break;
1143 case DISPLAY_UPSCALED_640x400:
1144 x += adjX * 2;
1145 y += adjY * 2;
1146 break;
1147 default:
1148 assert(0);
1149 break;
1150 }
1151 drawStringOnDisplay(x, y, text, foregroundColor, backgroundColor);
1152 }
1153
drawCharacterOnDisplay(int16 x,int16 y,const byte character,byte foreground,byte background,byte transformXOR,byte transformOR)1154 void GfxMgr::drawCharacterOnDisplay(int16 x, int16 y, const byte character, byte foreground, byte background, byte transformXOR, byte transformOR) {
1155 int16 curX, curY;
1156 const byte *fontData;
1157 bool fontIsHires = _font->isFontHires();
1158 int16 fontHeight = fontIsHires ? 16 : FONT_DISPLAY_HEIGHT;
1159 int16 fontWidth = fontIsHires ? 16 : FONT_DISPLAY_WIDTH;
1160 int16 fontBytesPerCharacter = fontIsHires ? 32 : FONT_BYTES_PER_CHARACTER;
1161 byte curByte = 0;
1162 uint16 curBit;
1163
1164 // get font data of specified character
1165 fontData = _font->getFontData() + character * fontBytesPerCharacter;
1166
1167 curBit = 0;
1168 for (curY = 0; curY < fontHeight; curY++) {
1169 for (curX = 0; curX < fontWidth; curX++) {
1170 if (!curBit) {
1171 curByte = *fontData;
1172 // do transformations in case they are needed (invert/disabled look)
1173 curByte ^= transformXOR;
1174 curByte |= transformOR;
1175 fontData++;
1176 curBit = 0x80;
1177 }
1178 if (curByte & curBit) {
1179 putFontPixelOnDisplay(x, y, curX, curY, foreground, fontIsHires);
1180 } else {
1181 putFontPixelOnDisplay(x, y, curX, curY, background, fontIsHires);
1182 }
1183 curBit = curBit >> 1;
1184 }
1185 if (transformOR)
1186 transformOR ^= 0xFF;
1187 }
1188
1189 copyDisplayRectToScreen(x, y, _displayFontWidth, _displayFontHeight);
1190 }
1191
1192 #define SHAKE_VERTICAL_PIXELS 4
1193 #define SHAKE_HORIZONTAL_PIXELS 4
1194
1195 // Sierra used some EGA port trickery to do it, we have to do it by copying pixels around
1196 //
1197 // Shaking locations:
1198 // - Fanmade "Enclosure" right during the intro
1199 // - Space Quest 2 almost right at the start when getting captured (after walking into the space ship)
shakeScreen(int16 repeatCount)1200 void GfxMgr::shakeScreen(int16 repeatCount) {
1201 int shakeNr, shakeCount;
1202 uint8 *blackSpace;
1203 int16 shakeHorizontalPixels = SHAKE_HORIZONTAL_PIXELS * (2 + _displayWidthMulAdjust);
1204 int16 shakeVerticalPixels = SHAKE_VERTICAL_PIXELS * (1 + _displayHeightMulAdjust);
1205
1206 if ((blackSpace = (uint8 *)calloc(shakeHorizontalPixels * _displayScreenWidth, 1)) == NULL)
1207 return;
1208
1209 shakeCount = repeatCount * 8; // effectively 4 shakes per repeat
1210
1211 // it's 4 pixels down and 8 pixels to the right
1212 // and it's also filling the remaining space with black
1213 for (shakeNr = 0; shakeNr < shakeCount; shakeNr++) {
1214 if (shakeNr & 1) {
1215 // move back
1216 copyDisplayToScreen();
1217 } else {
1218 g_system->copyRectToScreen(_displayScreen, _displayScreenWidth, shakeHorizontalPixels, shakeVerticalPixels, _displayScreenWidth - shakeHorizontalPixels, _displayScreenHeight - shakeVerticalPixels);
1219 // additionally fill the remaining space with black
1220 g_system->copyRectToScreen(blackSpace, _displayScreenWidth, 0, 0, _displayScreenWidth, shakeVerticalPixels);
1221 g_system->copyRectToScreen(blackSpace, shakeHorizontalPixels, 0, 0, shakeHorizontalPixels, _displayScreenHeight);
1222 }
1223 g_system->updateScreen();
1224 g_system->delayMillis(66); // Sierra waited for 4 V'Syncs, which is around 66 milliseconds
1225 }
1226
1227 free(blackSpace);
1228 }
1229
updateScreen()1230 void GfxMgr::updateScreen() {
1231 g_system->updateScreen();
1232 }
1233
initPriorityTable()1234 void GfxMgr::initPriorityTable() {
1235 _priorityTableSet = false;
1236
1237 createDefaultPriorityTable(_priorityTable);
1238 }
1239
createDefaultPriorityTable(uint8 * priorityTable)1240 void GfxMgr::createDefaultPriorityTable(uint8 *priorityTable) {
1241 int16 priority, step;
1242 int16 yPos = 0;
1243
1244 for (priority = 1; priority < 15; priority++) {
1245 for (step = 0; step < 12; step++) {
1246 priorityTable[yPos++] = priority < 4 ? 4 : priority;
1247 }
1248 }
1249 }
1250
setPriorityTable(int16 priorityBase)1251 void GfxMgr::setPriorityTable(int16 priorityBase) {
1252 int16 x, priorityY, priority;
1253
1254 _priorityTableSet = true;
1255 x = (SCRIPT_HEIGHT - priorityBase) * SCRIPT_HEIGHT / 10;
1256
1257 for (priorityY = 0; priorityY < SCRIPT_HEIGHT; priorityY++) {
1258 priority = (priorityY - priorityBase) < 0 ? 4 : (priorityY - priorityBase) * SCRIPT_HEIGHT / x + 5;
1259 if (priority > 15)
1260 priority = 15;
1261 _priorityTable[priorityY] = priority;
1262 }
1263 }
1264
1265 // used for saving
saveLoadGetPriority(int16 yPos)1266 int16 GfxMgr::saveLoadGetPriority(int16 yPos) {
1267 assert(yPos < SCRIPT_HEIGHT);
1268 return _priorityTable[yPos];
1269 }
saveLoadWasPriorityTableModified()1270 bool GfxMgr::saveLoadWasPriorityTableModified() {
1271 return _priorityTableSet;
1272 }
1273
1274 // used for restoring
saveLoadSetPriority(int16 yPos,int16 priority)1275 void GfxMgr::saveLoadSetPriority(int16 yPos, int16 priority) {
1276 assert(yPos < SCRIPT_HEIGHT);
1277 _priorityTable[yPos] = priority;
1278 }
saveLoadSetPriorityTableModifiedBool(bool wasModified)1279 void GfxMgr::saveLoadSetPriorityTableModifiedBool(bool wasModified) {
1280 _priorityTableSet = wasModified;
1281 }
saveLoadFigureOutPriorityTableModifiedBool()1282 void GfxMgr::saveLoadFigureOutPriorityTableModifiedBool() {
1283 uint8 defaultPriorityTable[SCRIPT_HEIGHT]; /**< priority table */
1284
1285 createDefaultPriorityTable(defaultPriorityTable);
1286
1287 if (memcmp(defaultPriorityTable, _priorityTable, sizeof(_priorityTable)) == 0) {
1288 // Match, it is the default table, so reset the flag
1289 _priorityTableSet = false;
1290 } else {
1291 _priorityTableSet = true;
1292 }
1293 }
1294
1295 /**
1296 * Convert sprite priority to y value.
1297 */
priorityToY(int16 priority)1298 int16 GfxMgr::priorityToY(int16 priority) {
1299 int16 currentY;
1300
1301 if (!_priorityTableSet) {
1302 // priority table wasn't set by scripts? calculate directly
1303 return (priority - 5) * 12 + 48;
1304 }
1305
1306 // Dynamic priority bands were introduced in 2.425, but removed again until 2.936 (effectively last version of AGI2)
1307 // They are available from 2.936 onwards.
1308 // It seems there was a glitch, that caused priority bands to not get calculated properly.
1309 // It was caused by this function starting with Y = 168 instead of 167, which meant it always
1310 // returned with 168 as result.
1311 // This glitch is required in King's Quest 4 2.0, otherwise in room 54 ego will get drawn over
1312 // the last dwarf, that enters the house.
1313 // Dwarf is screen object 13 (view 152), gets fixed priority of 8, which would normally
1314 // result in a Y of 101. Ego is priority (non-fixed) 8, which would mean that dwarf is
1315 // drawn first, followed by ego, which would then draw ego over the dwarf.
1316 // For more information see bug #3182 (dwarf sprite priority)
1317 //
1318 // This glitch is definitely present in 2.425, 2.936 and 3.002.086.
1319 //
1320 // Priority bands were working properly in: 3.001.098 (Black Cauldron)
1321 uint16 agiVersion = _vm->getVersion();
1322
1323 if (agiVersion <= 0x3086) {
1324 return 168; // Buggy behavior, see above
1325 }
1326
1327 currentY = 167;
1328 while (_priorityTable[currentY] >= priority) {
1329 currentY--;
1330 if (currentY < 0) // Original AGI didn't do this, we abort in that case and return -1
1331 break;
1332 }
1333 return currentY;
1334 }
1335
priorityFromY(int16 yPos)1336 int16 GfxMgr::priorityFromY(int16 yPos) {
1337 assert(yPos < SCRIPT_HEIGHT);
1338 return _priorityTable[yPos];
1339 }
1340
1341
1342 /**
1343 * Initialize the color palette
1344 * This function initializes the color palette using the specified
1345 * RGB palette.
1346 * @param p A pointer to the source RGB palette.
1347 * @param colorCount Count of colors in the source palette.
1348 * @param fromBits Bits per source color component.
1349 * @param toBits Bits per destination color component.
1350 */
initPalette(uint8 * destPalette,const uint8 * paletteData,uint colorCount,uint fromBits,uint toBits)1351 void GfxMgr::initPalette(uint8 *destPalette, const uint8 *paletteData, uint colorCount, uint fromBits, uint toBits) {
1352 const uint srcMax = (1 << fromBits) - 1;
1353 const uint destMax = (1 << toBits) - 1;
1354 for (uint colorNr = 0; colorNr < colorCount; colorNr++) {
1355 for (uint componentNr = 0; componentNr < 3; componentNr++) { // Convert RGB components
1356 destPalette[colorNr * 3 + componentNr] = (paletteData[colorNr * 3 + componentNr] * destMax) / srcMax;
1357 }
1358 }
1359 }
1360
1361 // Converts CLUT data to a palette, that we can use
initPaletteCLUT(uint8 * destPalette,const uint16 * paletteCLUTData,uint colorCount)1362 void GfxMgr::initPaletteCLUT(uint8 *destPalette, const uint16 *paletteCLUTData, uint colorCount) {
1363 for (uint colorNr = 0; colorNr < colorCount; colorNr++) {
1364 for (uint componentNr = 0; componentNr < 3; componentNr++) { // RGB component
1365 byte component = (paletteCLUTData[colorNr * 3 + componentNr] >> 8);
1366 // Adjust gamma (1.8 to 2.2)
1367 component = (byte)(255 * pow(component / 255.0f, 0.8181f));
1368 destPalette[colorNr * 3 + componentNr] = component;
1369 }
1370 }
1371 }
1372
setPalette(bool gfxModePalette)1373 void GfxMgr::setPalette(bool gfxModePalette) {
1374 if (gfxModePalette) {
1375 g_system->getPaletteManager()->setPalette(_paletteGfxMode, 0, 256);
1376 } else {
1377 g_system->getPaletteManager()->setPalette(_paletteTextMode, 0, 256);
1378 }
1379 }
1380
1381 //Gets AGIPAL Data
setAGIPal(int p0)1382 void GfxMgr::setAGIPal(int p0) {
1383 //If 0 from savefile, do not use
1384 if (p0 == 0)
1385 return;
1386
1387 char filename[15];
1388 sprintf(filename, "pal.%d", p0);
1389
1390 Common::File agipal;
1391 if (!agipal.open(filename)) {
1392 warning("Couldn't open AGIPAL palette file '%s'. Not changing palette", filename);
1393 return; // Needed at least by Naturette 3 which uses AGIPAL but provides no palette files
1394 }
1395
1396 //Chunk0 holds colors 0-7
1397 agipal.read(&_agipalPalette[0], 24);
1398
1399 //Chunk1 is the same as the chunk0
1400
1401 //Chunk2 chunk holds colors 8-15
1402 agipal.seek(24, SEEK_CUR);
1403 agipal.read(&_agipalPalette[24], 24);
1404
1405 //Chunk3 is the same as the chunk2
1406
1407 //Chunks4-7 are duplicates of chunks0-3
1408
1409 if (agipal.eos() || agipal.err()) {
1410 warning("Couldn't read AGIPAL palette from '%s'. Not changing palette", filename);
1411 return;
1412 }
1413
1414 // Use only the lowest 6 bits of each color component (Red, Green and Blue)
1415 // because VGA used only 6 bits per color component (i.e. VGA had 18-bit colors).
1416 // This should now be identical to the original AGIPAL-hack's behavior.
1417 bool validVgaPalette = true;
1418 for (int i = 0; i < 16 * 3; i++) {
1419 if (_agipalPalette[i] >= (1 << 6)) {
1420 _agipalPalette[i] &= 0x3F; // Leave only the lowest 6 bits of each color component
1421 validVgaPalette = false;
1422 }
1423 }
1424
1425 if (!validVgaPalette)
1426 warning("Invalid AGIPAL palette (Over 6 bits per color component) in '%s'. Using only the lowest 6 bits per color component", filename);
1427
1428 _agipalFileNum = p0;
1429
1430 initPalette(_paletteGfxMode, _agipalPalette);
1431 setPalette(true); // set gfx-mode palette
1432
1433 debug(1, "Using AGIPAL palette from '%s'", filename);
1434 }
1435
getAGIPalFileNum()1436 int GfxMgr::getAGIPalFileNum() {
1437 return _agipalFileNum;
1438 }
1439
initMouseCursor(MouseCursorData * mouseCursor,const byte * bitmapData,uint16 width,uint16 height,int hotspotX,int hotspotY)1440 void GfxMgr::initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY) {
1441 switch (_upscaledHires) {
1442 case DISPLAY_UPSCALED_DISABLED:
1443 mouseCursor->bitmapData = bitmapData;
1444 break;
1445 case DISPLAY_UPSCALED_640x400: {
1446 mouseCursor->bitmapDataAllocated = (byte *)malloc(width * height * 4);
1447 mouseCursor->bitmapData = mouseCursor->bitmapDataAllocated;
1448
1449 // Upscale mouse cursor
1450 byte *upscaledData = mouseCursor->bitmapDataAllocated;
1451
1452 for (uint16 y = 0; y < height; y++) {
1453 for (uint16 x = 0; x < width; x++) {
1454 byte curColor = *bitmapData++;
1455 upscaledData[x * 2 + 0] = curColor;
1456 upscaledData[x * 2 + 1] = curColor;
1457 upscaledData[x * 2 + (width * 2) + 0] = curColor;
1458 upscaledData[x * 2 + (width * 2) + 1] = curColor;
1459 }
1460 upscaledData += width * 2 * 2;
1461 }
1462
1463 width *= 2;
1464 height *= 2;
1465 hotspotX *= 2;
1466 hotspotY *= 2;
1467 break;
1468 }
1469 default:
1470 assert(0);
1471 break;
1472 }
1473 mouseCursor->width = width;
1474 mouseCursor->height = height;
1475 mouseCursor->hotspotX = hotspotX;
1476 mouseCursor->hotspotY = hotspotY;
1477 }
1478
setMouseCursor(bool busy)1479 void GfxMgr::setMouseCursor(bool busy) {
1480 MouseCursorData *mouseCursor = nullptr;
1481
1482 if (!busy) {
1483 mouseCursor = &_mouseCursor;
1484 } else {
1485 mouseCursor = &_mouseCursorBusy;
1486 }
1487
1488 if (mouseCursor) {
1489 CursorMan.replaceCursor(mouseCursor->bitmapData, mouseCursor->width, mouseCursor->height, mouseCursor->hotspotX, mouseCursor->hotspotY, 0);
1490 }
1491 }
1492
1493 #if 0
1494 void GfxMgr::setCursor(bool amigaStyleCursor, bool busy) {
1495 if (busy) {
1496 CursorMan.replaceCursorPalette(MOUSECURSOR_AMIGA_PALETTE, 1, ARRAYSIZE(MOUSECURSOR_AMIGA_PALETTE) / 3);
1497 CursorMan.replaceCursor(MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8, 0);
1498 return;
1499 }
1500
1501 if (!amigaStyleCursor) {
1502 CursorMan.replaceCursorPalette(sciMouseCursorPalette, 1, ARRAYSIZE(sciMouseCursorPalette) / 3);
1503 CursorMan.replaceCursor(sciMouseCursor, 11, 16, 1, 1, 0);
1504 } else { // amigaStyleCursor
1505 CursorMan.replaceCursorPalette(amigaMouseCursorPalette, 1, ARRAYSIZE(amigaMouseCursorPalette) / 3);
1506 CursorMan.replaceCursor(amigaMouseCursor, 8, 11, 1, 1, 0);
1507 }
1508 }
1509
1510 void GfxMgr::setCursorPalette(bool amigaStyleCursor) {
1511 if (!amigaStyleCursor) {
1512 if (_currentCursorPalette != 1) {
1513 CursorMan.replaceCursorPalette(sciMouseCursorPalette, 1, ARRAYSIZE(sciMouseCursorPalette) / 3);
1514 _currentCursorPalette = 1;
1515 }
1516 } else { // amigaStyleCursor
1517 if (_currentCursorPalette != 2) {
1518 CursorMan.replaceCursorPalette(amigaMouseCursorPalette, 1, ARRAYSIZE(amigaMouseCursorPalette) / 3);
1519 _currentCursorPalette = 2;
1520 }
1521 }
1522 }
1523 #endif
1524
1525 } // End of namespace Agi
1526