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 "tsage/events.h"
24 #include "tsage/graphics.h"
25 #include "tsage/resources.h"
26 #include "tsage/tsage.h"
27 #include "tsage/core.h"
28 #include "common/algorithm.h"
29 #include "graphics/palette.h"
30 #include "graphics/surface.h"
31 #include "tsage/globals.h"
32 
33 namespace TsAGE {
34 
35 /**
36  * Creates a new graphics surface with the specified area of another surface
37  *
38  * @src Source surface
39  * @bounds Area to backup
40  */
surfaceGetArea(GfxSurface & src,const Rect & bounds)41 GfxSurface *surfaceGetArea(GfxSurface &src, const Rect &bounds) {
42 	assert(bounds.isValidRect());
43 	GfxSurface *dest = new GfxSurface();
44 	dest->create(bounds.width(), bounds.height());
45 
46 	Graphics::Surface srcSurface = src.lockSurface();
47 	Graphics::Surface destSurface = dest->lockSurface();
48 
49 	byte *srcP = (byte *)srcSurface.getBasePtr(bounds.left, bounds.top);
50 	byte *destP = (byte *)destSurface.getPixels();
51 
52 	for (int y = bounds.top; y < bounds.bottom; ++y, srcP += srcSurface.pitch, destP += destSurface.pitch)
53 		Common::copy(srcP, srcP + destSurface.pitch, destP);
54 
55 	src.unlockSurface();
56 	dest->unlockSurface();
57 	return dest;
58 }
59 
60 /**
61  * Translates a raw image resource into a graphics surface. The caller is then responsible
62  * for managing and destroying the surface when done with it
63  *
64  * @imgData Raw image resource
65  * @size Size of the resource
66  */
surfaceFromRes(const byte * imgData)67 GfxSurface surfaceFromRes(const byte *imgData) {
68 	Rect r(0, 0, READ_LE_UINT16(imgData), READ_LE_UINT16(imgData + 2));
69 	GfxSurface s;
70 	s.create(r.width(), r.height());
71 	s._transColor = *(imgData + 8);
72 
73 	byte flags = imgData[9];
74 	s._flags = (g_vm->getGameID() != GType_Ringworld) ? flags : 0;
75 
76 	bool rleEncoded = (flags & 2) != 0;
77 
78 	// Figure out the centroid
79 	s._centroid.x = READ_LE_UINT16(imgData + 4);
80 	s._centroid.y = READ_LE_UINT16(imgData + 6);
81 
82 	const byte *srcP = imgData + 10;
83 	Graphics::Surface destSurface = s.lockSurface();
84 	byte *destP = (byte *)destSurface.getPixels();
85 
86 	if (!rleEncoded) {
87 		Common::copy(srcP, srcP + (r.width() * r.height()), destP);
88 	} else {
89 		Common::fill(destP, destP + (r.width() * r.height()), s._transColor);
90 
91 		for (int yp = 0; yp < r.height(); ++yp) {
92 			int width = r.width();
93 			destP = (byte *)destSurface.getBasePtr(0, yp);
94 
95 			while (width > 0) {
96 				uint8 controlVal = *srcP++;
97 				if ((controlVal & 0x80) == 0) {
98 					// Copy specified number of bytes
99 
100 					Common::copy(srcP, srcP + controlVal, destP);
101 					width -= controlVal;
102 					srcP += controlVal;
103 					destP += controlVal;
104 				} else if ((controlVal & 0x40) == 0) {
105 					// Skip a specified number of output pixels
106 					destP += controlVal & 0x3f;
107 					width -= controlVal & 0x3f;
108 				} else {
109 					// Copy a specified pixel a given number of times
110 					controlVal &= 0x3f;
111 					int pixel = *srcP++;
112 
113 					Common::fill(destP, destP + controlVal, pixel);
114 					destP += controlVal;
115 					width -= controlVal;
116 				}
117 			}
118 			assert(width == 0);
119 		}
120 	}
121 
122 	s.unlockSurface();
123 	return s;
124 }
125 
surfaceFromRes(int resNum,int rlbNum,int subNum)126 GfxSurface surfaceFromRes(int resNum, int rlbNum, int subNum) {
127 	uint size;
128 	byte *imgData = g_resourceManager->getSubResource(resNum, rlbNum, subNum, &size);
129 	GfxSurface surface = surfaceFromRes(imgData);
130 	DEALLOCATE(imgData);
131 
132 	return surface;
133 }
134 /*--------------------------------------------------------------------------*/
135 
set(int16 x1,int16 y1,int16 x2,int16 y2)136 void Rect::set(int16 x1, int16 y1, int16 x2, int16 y2) {
137 	left = x1; top = y1;
138 	right = x2; bottom = y2;
139 }
140 
141 /**
142  * Collapses the rectangle in all four directions by the given x and y amounts
143  *
144  * @dx x amount to collapse x edges by
145  * @dy y amount to collapse y edges by
146  */
collapse(int dx,int dy)147 void Rect::collapse(int dx, int dy) {
148 	left += dx; right -= dx;
149 	top += dy; bottom -= dy;
150 }
151 
152 /**
153  * Centers the rectangle at a given position
154  *
155  * @xp x position for new center
156  * @yp y position for new center
157  */
center(int xp,int yp)158 void Rect::center(int xp, int yp) {
159 	moveTo(xp - (width() / 2), yp - (height() / 2));
160 }
161 
162 /**
163  * Centers the rectangle at the center of a second passed rectangle
164  *
165  * @r Second rectangle whose center to use
166  */
center(const Rect & r)167 void Rect::center(const Rect &r) {
168 	center(r.left + (r.width() / 2), r.top + (r.height() / 2));
169 }
170 
171 /*
172  * Repositions the bounds if necessary so it falls entirely within the passed bounds
173  *
174  * @r The bounds the current rect should be within
175  */
contain(const Rect & r)176 void Rect::contain(const Rect &r) {
177 	if (left < r.left) translate(r.left - left, 0);
178 	if (right > r.right) translate(r.right - right, 0);
179 	if (top < r.top) translate(0, r.top - top);
180 	if (bottom > r.bottom) translate(0, r.bottom - bottom);
181 }
182 
183 /**
184  * Resizes and positions a given rect based on raw image data and a passed scaling percentage
185  *
186  * @frame Raw image frame
187  * @xp New x position
188  * @yp New y position
189  * @percent Scaling percentage
190  */
resize(const GfxSurface & surface,int xp,int yp,int percent)191 void Rect::resize(const GfxSurface &surface, int xp, int yp, int percent) {
192 	const Rect &bounds = surface.getBounds();
193 	int xe = bounds.width() * percent / 100;
194 	int ye = bounds.height() * percent / 100;
195 	this->set(0, 0, xe, ye);
196 
197 	if (!right) ++right;
198 	if (!bottom) ++bottom;
199 
200 	this->moveTo(xp, yp);
201 
202 	int xa = (surface._flags & FRAME_FLIP_CENTROID_X) == 0 ? surface._centroid.x :
203 		bounds.width() - (surface._centroid.x + 1);
204 	int ya = (surface._flags & FRAME_FLIP_CENTROID_Y) == 0 ? surface._centroid.y :
205 		bounds.height() - (surface._centroid.y + 1);
206 
207 	int xd = xa * percent / 100;
208 	int yd = ya * percent / 100;
209 	this->translate(-xd, -yd);
210 }
211 
212 /**
213  * Expands the pane region to contain the specified Rect
214  */
expandPanes()215 void Rect::expandPanes() {
216 	g_globals->_paneRegions[0].uniteRect(*this);
217 	g_globals->_paneRegions[1].uniteRect(*this);
218 }
219 
220 /**
221  * Serialises the given rect
222  */
synchronize(Serializer & s)223 void Rect::synchronize(Serializer &s) {
224 	s.syncAsSint16LE(left);
225 	s.syncAsSint16LE(top);
226 	s.syncAsSint16LE(right);
227 	s.syncAsSint16LE(bottom);
228 }
229 
230 /*--------------------------------------------------------------------------*/
231 
GfxSurface()232 GfxSurface::GfxSurface() : Graphics::Screen(0, 0), _bounds(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) {
233 	free();		// Free the 0x0 surface allocated by Graphics::Screen
234 	_disableUpdates = false;
235 	_lockSurfaceCtr = 0;
236 	_transColor = -1;
237 	_flags = 0;
238 }
239 
GfxSurface(const GfxSurface & s)240 GfxSurface::GfxSurface(const GfxSurface &s): Graphics::Screen(0, 0) {
241 	free();		// Free the 0x0 surface allocated by Graphics::Screen
242 	_lockSurfaceCtr = 0;
243 
244 	operator=(s);
245 }
246 
~GfxSurface()247 GfxSurface::~GfxSurface() {
248 	// Sanity check.. GfxSurface should always be just referencing _rawSurface,
249 	// and not directly managing it's own surface
250 	assert(disposeAfterUse() == DisposeAfterUse::NO);
251 }
252 
create(uint16 width,uint16 height)253 void GfxSurface::create(uint16 width, uint16 height) {
254 	free();
255 
256 	_rawSurface.create(width, height);
257 	setBounds(Rect(0, 0, width, height));
258 }
259 
setBounds(const Rect & bounds)260 void GfxSurface::setBounds(const Rect &bounds) {
261 	_bounds = bounds;
262 	Graphics::ManagedSurface::create(_rawSurface, bounds);
263 }
264 
265 /**
266  * Locks the surface for access, and returns a raw ScummVM surface to manipulate it
267  */
lockSurface()268 Graphics::ManagedSurface &GfxSurface::lockSurface() {
269 	++_lockSurfaceCtr;
270 	return *this;
271 }
272 
273 /**
274  * Unlocks the surface after having accessed it with the lockSurface method
275  */
unlockSurface()276 void GfxSurface::unlockSurface() {
277 	assert(_lockSurfaceCtr > 0);
278 	--_lockSurfaceCtr;
279 }
280 
synchronize(Serializer & s)281 void GfxSurface::synchronize(Serializer &s) {
282 	assert(!_lockSurfaceCtr);
283 
284 	s.syncAsByte(_disableUpdates);
285 	_bounds.synchronize(s);
286 	s.syncAsSint16LE(_centroid.x);
287 	s.syncAsSint16LE(_centroid.y);
288 	s.syncAsSint16LE(_transColor);
289 
290 	if (s.isSaving()) {
291 		// Save contents of the surface
292 		if (disposeAfterUse() == DisposeAfterUse::YES) {
293 			s.syncAsSint16LE(this->w);
294 			s.syncAsSint16LE(this->h);
295 			s.syncBytes((byte *)getPixels(), this->w * this->h);
296 		} else {
297 			int zero = 0;
298 			s.syncAsSint16LE(zero);
299 			s.syncAsSint16LE(zero);
300 		}
301 	} else {
302 		int xSize = 0, ySize = 0;
303 		s.syncAsSint16LE(xSize);
304 		s.syncAsSint16LE(ySize);
305 
306 		if (xSize == 0 || ySize == 0) {
307 			free();
308 		} else {
309 			create(xSize, ySize);
310 			s.syncBytes((byte *)getPixels(), xSize * ySize);
311 		}
312 	}
313 }
314 
operator =(const GfxSurface & s)315 GfxSurface &GfxSurface::operator=(const GfxSurface &s) {
316 	assert(_lockSurfaceCtr == 0);
317 	assert(s._lockSurfaceCtr == 0);
318 
319 	_disableUpdates = s._disableUpdates;
320 	_bounds = s._bounds;
321 	_centroid = s._centroid;
322 	_transColor = s._transColor;
323 	_flags = s._flags;
324 
325 	// Copy the source's surface
326 	create(s.w, s.h);
327 	blitFrom(s);
328 	setBounds(s.getBounds());
329 
330 	return *this;
331 }
332 
333 /**
334  * Displays a message on-screen until either a mouse or keypress
335  */
displayText(const Common::String & msg,const Common::Point & pt)336 bool GfxSurface::displayText(const Common::String &msg, const Common::Point &pt) {
337 	// Set up a new graphics manager
338 	GfxManager gfxManager;
339 	gfxManager.activate();
340 	gfxManager._font._colors.background = 0;
341 	gfxManager._font._colors.foreground = 7;
342 	gfxManager._font.setFontNumber(2);
343 
344 	// Get the area for text display
345 	Rect textRect;
346 	gfxManager.getStringBounds(msg.c_str(), textRect, 200);
347 	textRect.center(pt.x, pt.y);
348 
349 	// Make a backup copy of the area the text will occupy
350 	Rect saveRect = textRect;
351 	saveRect.collapse(-20, -8);
352 	GfxSurface *savedArea = surfaceGetArea(gfxManager.getSurface(), saveRect);
353 
354 	// Display the text
355 	gfxManager._font.writeLines(msg.c_str(), textRect, ALIGN_LEFT);
356 
357 	// Wait for a mouse or keypress
358 	Event event;
359 	while (!g_globals->_events.getEvent(event, EVENT_BUTTON_DOWN | EVENT_KEYPRESS) && !g_vm->shouldQuit())
360 		;
361 
362 	// Restore the display area
363 	gfxManager.copyFrom(*savedArea, saveRect.left, saveRect.top);
364 	delete savedArea;
365 
366 	gfxManager.deactivate();
367 	return (event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_RETURN);
368 }
369 
370 /**
371  * Loads a quarter of a screen from a resource
372  */
loadScreenSection(Graphics::ManagedSurface & dest,int xHalf,int yHalf,int xSection,int ySection)373 void GfxSurface::loadScreenSection(Graphics::ManagedSurface &dest, int xHalf, int yHalf, int xSection, int ySection) {
374 	int screenNum = g_globals->_sceneManager._scene->_activeScreenNumber;
375 	Rect updateRect(0, 0, 160, 100);
376 	updateRect.translate(xHalf * 160, yHalf * 100);
377 	int xHalfCount = (g_globals->_sceneManager._scene->_backgroundBounds.right + 159) / 160;
378 	int yHalfCount = (g_globals->_sceneManager._scene->_backgroundBounds.bottom + 99) / 100;
379 
380 	if (xSection < xHalfCount && ySection < yHalfCount) {
381 		int rlbNum = xSection * yHalfCount + ySection;
382 		byte *data = g_resourceManager->getResource(RES_BITMAP, screenNum, rlbNum);
383 
384 		for (int y = 0; y < updateRect.height(); ++y) {
385 			byte *pSrc = data + y * 160;
386 			byte *pDest = (byte *)dest.getBasePtr(updateRect.left, updateRect.top + y);
387 
388 			for (int x = 0; x < updateRect.width(); ++x, ++pSrc, ++pDest) {
389 				*pDest = *pSrc;
390 			}
391 		}
392 
393 		DEALLOCATE(data);
394 	}
395 }
396 
397 /**
398  * Returns an array indicating which pixels of a source image horizontally or vertically get
399  * included in a scaled image
400  */
scaleLine(int size,int srcSize)401 static int *scaleLine(int size, int srcSize) {
402 	const int PRECISION_FACTOR = 1000;
403 	int scale = PRECISION_FACTOR * size / srcSize;
404 	assert(scale >= 0);
405 	int *v = new int[size];
406 	Common::fill(v, &v[size], -1);
407 
408 	int distCtr = PRECISION_FACTOR / 2;
409 	int *destP = v;
410 	for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
411 		distCtr += scale;
412 		while (distCtr > PRECISION_FACTOR) {
413 			assert(destP < &v[size]);
414 			*destP++ = distIndex;
415 			distCtr -= PRECISION_FACTOR;
416 		}
417 	}
418 
419 	return v;
420 }
421 
422 /**
423  * Scales a passed surface, creating a new surface with the result
424  * @param srcImage		Source image to scale
425  * @param NewWidth		New width for scaled image
426  * @param NewHeight		New height for scaled image
427  * @remarks Caller is responsible for freeing the returned surface
428  */
ResizeSurface(GfxSurface & src,int xSize,int ySize,int transIndex)429 static GfxSurface ResizeSurface(GfxSurface &src, int xSize, int ySize, int transIndex) {
430 	GfxSurface s;
431 	s.create(xSize, ySize);
432 
433 	Graphics::Surface srcImage = src.lockSurface();
434 	Graphics::Surface destImage = s.lockSurface();
435 
436 	int *horizUsage = scaleLine(xSize, srcImage.w);
437 	int *vertUsage = scaleLine(ySize, srcImage.h);
438 
439 	// Loop to create scaled version
440 	for (int yp = 0; yp < ySize; ++yp) {
441 		byte *destP = (byte *)destImage.getBasePtr(0, yp);
442 
443 		if (vertUsage[yp] == -1) {
444 			Common::fill(destP, destP + xSize, transIndex);
445 		} else {
446 			const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
447 
448 			for (int xp = 0; xp < xSize; ++xp) {
449 				if (horizUsage[xp] != -1) {
450 					const byte *tempSrcP = srcP + horizUsage[xp];
451 					*destP++ = *tempSrcP++;
452 				} else {
453 					// Pixel overrun at the end of the line
454 					*destP++ = transIndex;
455 				}
456 			}
457 		}
458 	}
459 
460 	// Unlock surfaces
461 	src.unlockSurface();
462 	s.unlockSurface();
463 
464 	// Delete arrays and return surface
465 	delete[] horizUsage;
466 	delete[] vertUsage;
467 	return s;
468 }
469 
470 /**
471  * Copys an area from one GfxSurface to another.
472  *
473  */
copyFrom(GfxSurface & src,Rect srcBounds,Rect destBounds,Region * priorityRegion,const byte * shadowMap)474 void GfxSurface::copyFrom(GfxSurface &src, Rect srcBounds, Rect destBounds,
475 		Region *priorityRegion, const byte *shadowMap) {
476 	GfxSurface srcImage;
477 	if (srcBounds.isEmpty())
478 		return;
479 
480 	if (srcBounds == src.getBounds())
481 		srcImage = src;
482 	else {
483 		// Set the source image to be the subset specified by the source bounds
484 		Graphics::Surface srcSurface = src.lockSurface();
485 
486 		srcImage.create(srcBounds.width(), srcBounds.height());
487 		Graphics::Surface destSurface = srcImage.lockSurface();
488 
489 		const byte *srcP = (const byte *)srcSurface.getBasePtr(srcBounds.left, srcBounds.top);
490 		byte *destP = (byte *)destSurface.getPixels();
491 		for (int yp = srcBounds.top; yp < srcBounds.bottom; ++yp, srcP += srcSurface.pitch, destP += destSurface.pitch) {
492 			Common::copy(srcP, srcP + srcBounds.width(), destP);
493 		}
494 
495 		srcImage.unlockSurface();
496 		src.unlockSurface();
497 	}
498 
499 	if ((destBounds.width() != srcBounds.width()) || (destBounds.height() != srcBounds.height()))
500 		srcImage = ResizeSurface(srcImage, destBounds.width(), destBounds.height(), src._transColor);
501 
502 	Graphics::Surface srcSurface = srcImage.lockSurface();
503 	Graphics::Surface destSurface = lockSurface();
504 
505 	// Get clipping area
506 	Rect clipRect = !_clipRect.isEmpty() ? _clipRect :
507 		Rect(0, 0, destSurface.w, destSurface.h);
508 
509 	// Adjust bounds to ensure destination will be on-screen
510 	int srcX = 0, srcY = 0;
511 	if (destBounds.left < clipRect.left) {
512 		srcX = clipRect.left - destBounds.left;
513 		destBounds.left = clipRect.left;
514 	}
515 	if (destBounds.top < clipRect.top) {
516 		srcY = clipRect.top - destBounds.top;
517 		destBounds.top = clipRect.top;
518 	}
519 	if (destBounds.right > clipRect.right)
520 		destBounds.right = clipRect.right;
521 	if (destBounds.bottom > clipRect.bottom)
522 		destBounds.bottom = clipRect.bottom;
523 
524 	if (destBounds.isValidRect() && !((destBounds.right < 0) || (destBounds.bottom < 0)
525 		|| (destBounds.left >= destSurface.w) || (destBounds.top >= destSurface.h))) {
526 		// Register the affected area as dirty
527 		addDirtyRect(destBounds);
528 
529 		const byte *pSrc = (const byte *)srcSurface.getBasePtr(srcX, srcY);
530 		byte *pDest = (byte *)destSurface.getBasePtr(destBounds.left, destBounds.top);
531 
532 		for (int y = 0; y < destBounds.height(); ++y, pSrc += srcSurface.pitch, pDest += destSurface.pitch) {
533 
534 			if (!priorityRegion && (src._transColor == -1))
535 				Common::copy(pSrc, pSrc + destBounds.width(), pDest);
536 			else {
537 				const byte *tempSrc = pSrc;
538 				byte *tempDest = pDest;
539 				int xp = destBounds.left;
540 
541 				while (tempSrc < (pSrc + destBounds.width())) {
542 					if (!priorityRegion || !priorityRegion->contains(Common::Point(
543 							xp + g_globals->_sceneManager._scene->_sceneBounds.left,
544 							destBounds.top + y + g_globals->_sceneManager._scene->_sceneBounds.top))) {
545 						if (*tempSrc != src._transColor) {
546 							if (shadowMap) {
547 								// Using a shadow map, so translate the dest pixel using the mapping array
548 								*tempDest = shadowMap[*tempDest];
549 							} else {
550 								// Otherwise, it's a standard pixel copy
551 								*tempDest = *tempSrc;
552 							}
553 						}
554 					}
555 					++tempSrc;
556 					++tempDest;
557 					++xp;
558 				}
559 			}
560 		}
561 	}
562 
563 	unlockSurface();
564 	srcImage.unlockSurface();
565 }
566 
draw(const Common::Point & pt,Rect * rect)567 void GfxSurface::draw(const Common::Point &pt, Rect *rect) {
568 	Rect tempRect = getBounds();
569 	tempRect.translate(-_centroid.x, -_centroid.y);
570 	tempRect.translate(pt.x, pt.y);
571 
572 	if (rect) {
573 		// Only copy needed rect out without drawing
574 		*rect = tempRect;
575 	} else {
576 		// Draw image
577 		g_globals->gfxManager().copyFrom(*this, tempRect, NULL);
578 	}
579 }
580 
581 /*--------------------------------------------------------------------------*/
582 
GfxElement()583 GfxElement::GfxElement() {
584 	_owner = NULL;
585 	_keycode = 0;
586 	_flags = 0;
587 
588 	_fontNumber = 0;
589 	_color1 = 0;
590 	_color2 = 0;
591 	_color3 = 0;
592 }
593 
setDefaults()594 void GfxElement::setDefaults() {
595 	_flags = 0;
596 	_fontNumber = g_globals->_gfxFontNumber;
597 	_colors = g_globals->_gfxColors;
598 	_fontColors = g_globals->_fontColors;
599 	_color1 = g_globals->_color1;
600 	_color2 = g_globals->_color2;
601 	_color3 = g_globals->_color3;
602 }
603 
604 /**
605  * Highlights the specified graphics element
606  */
highlight()607 void GfxElement::highlight() {
608 	// Get a lock on the surface
609 	GfxManager &gfxManager = g_globals->gfxManager();
610 	Graphics::Surface surface = gfxManager.lockSurface();
611 
612 	// Scan through the contents of the element, switching any occurances of the foreground
613 	// color with the background color and vice versa
614 	Rect tempRect(_bounds);
615 	tempRect.collapse(g_globals->_gfxEdgeAdjust - 1, g_globals->_gfxEdgeAdjust - 1);
616 
617 	Graphics::Surface dest = surface.getSubArea(tempRect);
618 
619 	for (int yp = 0; yp < dest.h; ++yp) {
620 		byte *lineP = (byte *)dest.getBasePtr(0, yp);
621 		for (int xp = 0; xp < tempRect.right; ++xp, ++lineP) {
622 			if (*lineP == _colors.background) *lineP = _colors.foreground;
623 			else if (*lineP == _colors.foreground) *lineP = _colors.background;
624 		}
625 	}
626 
627 	// Release the surface
628 	gfxManager.unlockSurface();
629 }
630 
631 /**
632  * Fills the background of the specified element with a border frame
633  */
drawFrame()634 void GfxElement::drawFrame() {
635 	// Get a lock on the surface and save the active font
636 	GfxManager &gfxManager = g_globals->gfxManager();
637 	gfxManager.lockSurface();
638 
639 	uint8 bgColor, fgColor;
640 	if (_flags & GFXFLAG_THICK_FRAME) {
641 		bgColor = 0;
642 		fgColor = 0;
643 	} else {
644 		bgColor = _fontColors.background;
645 		fgColor = _fontColors.foreground;
646 	}
647 
648 	Rect tempRect = _bounds;
649 	tempRect.collapse(g_globals->_gfxEdgeAdjust, g_globals->_gfxEdgeAdjust);
650 	tempRect.collapse(-1, -1);
651 
652 	if (g_vm->getGameID() == GType_Ringworld2) {
653 		// For Return to Ringworld, use palette shading
654 
655 		// Get the current palette and determining a shading translation list
656 		ScenePalette tempPalette;
657 		tempPalette.getPalette(0, 256);
658 		int transList[256];
659 
660 		for (int i = 0; i < 256; ++i) {
661 			uint r, g, b, v;
662 			tempPalette.getEntry(i, &r, &g, &b);
663 			v = ((r >> 1) + (g >> 1) + (b >> 1)) / 4;
664 
665 			transList[i] = tempPalette.indexOf(v, v, v);
666 		}
667 
668 		// Loop through the surface area to replace each pixel
669 		// with its proper shaded replacement
670 		Graphics::Surface dest = gfxManager.getSurface().getSubArea(tempRect);
671 
672 		for (int y = 0; y < dest.h; ++y) {
673 			byte *lineP = (byte *)dest.getBasePtr(0, y);
674 			for (int x = 0; x < dest.w; ++x) {
675 				*lineP = transList[*lineP];
676 				lineP++;
677 			}
678 		}
679 
680 		// Draw the edge frame
681 		// Outer frame border
682 		dest.hLine(2, 0, dest.w - 2, 0);
683 		dest.hLine(2, dest.h - 1, dest.w - 2, 0);
684 		dest.vLine(0, 2, dest.h - 2, 0);
685 		dest.vLine(tempRect.right, 2, dest.h - 2, 0);
686 		*((byte *)dest.getBasePtr(1, 1)) = 0;
687 		*((byte *)dest.getBasePtr(dest.w - 1, 1)) = 0;
688 		*((byte *)dest.getBasePtr(1, dest.h - 1)) = 0;
689 		*((byte *)dest.getBasePtr(dest.w - 1, dest.h - 1)) = 0;
690 
691 		// Inner frame border
692 		dest.hLine(2, 1, dest.w - 2, R2_GLOBALS._frameEdgeColor);
693 		dest.hLine(2, dest.h - 1, dest.w - 2, R2_GLOBALS._frameEdgeColor);
694 		dest.vLine(1, 2, dest.h - 2, R2_GLOBALS._frameEdgeColor);
695 		dest.vLine(dest.w - 1, 2, dest.h - 2, R2_GLOBALS._frameEdgeColor);
696 		*((byte *)dest.getBasePtr(2, 2)) = R2_GLOBALS._frameEdgeColor;
697 		*((byte *)dest.getBasePtr(dest.w - 2, 2)) = R2_GLOBALS._frameEdgeColor;
698 		*((byte *)dest.getBasePtr(2, dest.h - 2)) = R2_GLOBALS._frameEdgeColor;
699 		*((byte *)dest.getBasePtr(dest.w - 2, dest.h - 2)) = R2_GLOBALS._frameEdgeColor;
700 
701 	} else {
702 		// Fill dialog content with specified background color
703 		gfxManager.fillRect(tempRect, _colors.background);
704 
705 		--tempRect.bottom; --tempRect.right;
706 		gfxManager.fillArea(tempRect.left, tempRect.top, bgColor);
707 		gfxManager.fillArea(tempRect.left, tempRect.bottom, fgColor);
708 		gfxManager.fillArea(tempRect.right, tempRect.top, fgColor);
709 		gfxManager.fillArea(tempRect.right, tempRect.bottom, fgColor);
710 
711 		tempRect.collapse(-1, -1);
712 		gfxManager.fillRect2(tempRect.left + 1, tempRect.top, tempRect.width() - 1, 1, bgColor);
713 		gfxManager.fillRect2(tempRect.left, tempRect.top + 1, 1, tempRect.height() - 1, bgColor);
714 		gfxManager.fillRect2(tempRect.left + 1, tempRect.bottom, tempRect.width() - 1, 1, fgColor);
715 		gfxManager.fillRect2(tempRect.right, tempRect.top + 1, 1, tempRect.height() - 1, fgColor);
716 
717 		gfxManager.fillArea(tempRect.left, tempRect.top, 0);
718 		gfxManager.fillArea(tempRect.left, tempRect.bottom, 0);
719 		gfxManager.fillArea(tempRect.right, tempRect.top, 0);
720 		gfxManager.fillArea(tempRect.right, tempRect.bottom, 0);
721 
722 		tempRect.collapse(-1, -1);
723 		gfxManager.fillRect2(tempRect.left + 2, tempRect.top, tempRect.width() - 3, 1, 0);
724 		gfxManager.fillRect2(tempRect.left, tempRect.top + 2, 1, tempRect.height() - 3, 0);
725 		gfxManager.fillRect2(tempRect.left + 2, tempRect.bottom, tempRect.width() - 3, 1, 0);
726 		gfxManager.fillRect2(tempRect.right, tempRect.top + 2, 1, tempRect.height() - 3, 0);
727 	}
728 
729 	gfxManager.unlockSurface();
730 }
731 
732 /**
733  * Handles events when the control has focus
734  *
735  * @event Event to process
736  */
focusedEvent(Event & event)737 bool GfxElement::focusedEvent(Event &event) {
738 	Common::Point mousePos = event.mousePos;
739 	bool highlightFlag = false;
740 
741 	// HACK: It should use the GfxManager object to figure out the relative
742 	// position, but for now this seems like the easiest way.
743 	int xOffset = mousePos.x - g_globals->_events._mousePos.x;
744 	int yOffset = mousePos.y - g_globals->_events._mousePos.y;
745 
746 	while (event.eventType != EVENT_BUTTON_UP && !g_vm->shouldQuit()) {
747 		g_system->delayMillis(10);
748 
749 		if (_bounds.contains(mousePos)) {
750 			if (!highlightFlag) {
751 				// First highlight call to show the highlight
752 				highlightFlag = true;
753 				highlight();
754 			}
755 		} else if (highlightFlag) {
756 			// Mouse is outside the element, so remove the highlight
757 			highlightFlag = false;
758 			highlight();
759 		}
760 
761 		if (g_globals->_events.getEvent(event, EVENT_MOUSE_MOVE | EVENT_BUTTON_UP)) {
762 			if (event.eventType == EVENT_MOUSE_MOVE) {
763 				mousePos.x = event.mousePos.x + xOffset;
764 				mousePos.y = event.mousePos.y + yOffset;
765 			}
766 		}
767 	}
768 
769 	if (highlightFlag) {
770 		// Mouse is outside the element, so remove the highlight
771 		highlight();
772 	}
773 
774 	return highlightFlag;
775 }
776 
777 /*--------------------------------------------------------------------------*/
778 
GfxImage()779 GfxImage::GfxImage() : GfxElement() {
780 	_resNum = 0;
781 	_rlbNum = 0;
782 	_cursorNum = 0;
783 }
784 
setDetails(int resNum,int rlbNum,int cursorNum)785 void GfxImage::setDetails(int resNum, int rlbNum, int cursorNum) {
786 	_resNum = resNum;
787 	_rlbNum = rlbNum;
788 	_cursorNum = cursorNum;
789 	setDefaults();
790 }
791 
setDefaults()792 void GfxImage::setDefaults() {
793 	GfxElement::setDefaults();
794 
795 	// Decode the image
796 	uint size;
797 	byte *imgData = g_resourceManager->getSubResource(_resNum, _rlbNum, _cursorNum, &size);
798 	_surface = surfaceFromRes(imgData);
799 	DEALLOCATE(imgData);
800 
801 	// Set up the display bounds
802 	Rect imgBounds = _surface.getBounds();
803 	imgBounds.moveTo(_bounds.left, _bounds.top);
804 	_bounds = imgBounds;
805 }
806 
draw()807 void GfxImage::draw() {
808 	Rect tempRect = _bounds;
809 	tempRect.translate(g_globals->gfxManager()._topLeft.x, g_globals->gfxManager()._topLeft.y);
810 
811 	g_globals->gfxManager().copyFrom(_surface, tempRect);
812 }
813 
814 /*--------------------------------------------------------------------------*/
815 
GfxMessage()816 GfxMessage::GfxMessage() : GfxElement() {
817 	_textAlign = ALIGN_LEFT;
818 	_width = 0;
819 }
820 
set(const Common::String & s,int width,TextAlign textAlign)821 void GfxMessage::set(const Common::String &s, int width, TextAlign textAlign) {
822 	_message = s;
823 	_width = width;
824 	_textAlign = textAlign;
825 
826 	setDefaults();
827 }
828 
setDefaults()829 void GfxMessage::setDefaults() {
830 	GfxElement::setDefaults();
831 
832 	GfxFontBackup font;
833 	GfxManager &gfxManager = g_globals->gfxManager();
834 	Rect tempRect;
835 
836 	gfxManager._font.setFontNumber(this->_fontNumber);
837 	gfxManager.getStringBounds(_message.c_str(), tempRect, _width);
838 
839 	tempRect.collapse(-1, -1);
840 	tempRect.moveTo(_bounds.left, _bounds.top);
841 	_bounds = tempRect;
842 }
843 
draw()844 void GfxMessage::draw() {
845 	GfxFontBackup font;
846 	GfxManager &gfxManager = g_globals->gfxManager();
847 
848 	// Set the font and color
849 	gfxManager.setFillFlag(false);
850 	gfxManager._font.setFontNumber(_fontNumber);
851 
852 	gfxManager._font._colors.foreground = this->_color1;
853 	gfxManager._font._colors2.background = this->_color2;
854 	gfxManager._font._colors2.foreground = this->_color3;
855 
856 	// Display the text
857 	gfxManager._font.writeLines(_message.c_str(), _bounds, _textAlign);
858 }
859 
860 /*--------------------------------------------------------------------------*/
861 
setDefaults()862 void GfxButton::setDefaults() {
863 	GfxElement::setDefaults();
864 
865 	GfxFontBackup font;
866 	GfxManager &gfxManager = g_globals->gfxManager();
867 	Rect tempRect;
868 
869 	// Get the string bounds and round up the x end to a multiple of 16
870 	gfxManager._font.setFontNumber(this->_fontNumber);
871 	gfxManager._font.getStringBounds(_message.c_str(), tempRect, 240);
872 	tempRect.right = ((tempRect.right + 15) / 16) * 16;
873 
874 	// Set the button bounds
875 	tempRect.collapse(-g_globals->_gfxEdgeAdjust, -g_globals->_gfxEdgeAdjust);
876 	if (g_vm->getFeatures() & GF_CD)
877 		--tempRect.top;
878 	tempRect.moveTo(_bounds.left, _bounds.top);
879 	_bounds = tempRect;
880 }
881 
draw()882 void GfxButton::draw() {
883 	// Get a lock on the surface and save the active font
884 	GfxFontBackup font;
885 	GfxManager &gfxManager = g_globals->gfxManager();
886 	gfxManager.lockSurface();
887 
888 	// Draw a basic frame for the button
889 	drawFrame();
890 
891 	// Set the font and color
892 	gfxManager._font.setFontNumber(_fontNumber);
893 
894 	//
895 	gfxManager._font._colors.foreground = this->_color1;
896 	gfxManager._font._colors2.background = this->_color2;
897 	gfxManager._font._colors2.foreground = this->_color3;
898 
899 	// Display the button's text
900 	Rect tempRect(_bounds);
901 	tempRect.collapse(g_globals->_gfxEdgeAdjust, g_globals->_gfxEdgeAdjust);
902 	if (g_vm->getFeatures() & GF_CD)
903 		++tempRect.top;
904 	gfxManager._font.writeLines(_message.c_str(), tempRect, ALIGN_CENTER);
905 
906 	gfxManager.unlockSurface();
907 }
908 
process(Event & event)909 bool GfxButton::process(Event &event) {
910 	switch (event.eventType) {
911 	case EVENT_BUTTON_DOWN:
912 		if (!event.handled) {
913 			if (_bounds.contains(event.mousePos)) {
914 				bool result = focusedEvent(event);
915 				event.handled = true;
916 				return result;
917 			}
918 		}
919 		break;
920 
921 	case EVENT_KEYPRESS:
922 		if (!event.handled && (event.kbd.keycode == _keycode)) {
923 			// Highlight the button momentarily
924 			highlight();
925 			g_system->delayMillis(20);
926 			highlight();
927 
928 			event.handled = true;
929 			return true;
930 		}
931 
932 	default:
933 		break;
934 	}
935 
936 	return false;
937 }
938 
939 /*--------------------------------------------------------------------------*/
940 
GfxDialog()941 GfxDialog::GfxDialog() {
942 	_savedArea = NULL;
943 	_defaultButton = NULL;
944 }
945 
~GfxDialog()946 GfxDialog::~GfxDialog() {
947 	remove();
948 }
949 
setDefaults()950 void GfxDialog::setDefaults() {
951 	GfxElement::setDefaults();
952 
953 	// Initialize the embedded graphics manager
954 	_gfxManager.setDefaults();
955 
956 	// Figure out a rect needed for all the added elements
957 	GfxElementList::iterator i;
958 	Rect tempRect;
959 	for (i = _elements.begin(); i != _elements.end(); ++i)
960 		tempRect.extend((*i)->_bounds);
961 
962 	// Set the dialog boundaries
963 	_gfxManager._bounds = tempRect;
964 	tempRect.collapse(-g_globals->_gfxEdgeAdjust * 2, -g_globals->_gfxEdgeAdjust * 2);
965 	_bounds = tempRect;
966 }
967 
remove()968 void GfxDialog::remove() {
969 	if (_savedArea) {
970 		// Restore the area the dialog covered
971 		g_globals->_gfxManagerInstance.copyFrom(*_savedArea, _bounds.left, _bounds.top);
972 
973 		delete _savedArea;
974 		_savedArea = NULL;
975 	}
976 }
977 
draw()978 void GfxDialog::draw() {
979 	Rect tempRect(_bounds);
980 
981 	// Make a backup copy of the area the dialog will occupy
982 	_savedArea = surfaceGetArea(g_globals->_gfxManagerInstance.getSurface(), _bounds);
983 
984 	// Set the palette for use in the dialog
985 	setPalette();
986 
987 	_gfxManager.activate();
988 
989 	// Fill in the contents of the entire dialog
990 	_gfxManager._bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
991 	drawFrame();
992 
993 	// Reset the dialog's graphics manager to only draw within the dialog boundaries
994 	tempRect.translate(g_globals->_gfxEdgeAdjust * 2, g_globals->_gfxEdgeAdjust * 2);
995 	_gfxManager._bounds = tempRect;
996 
997 	// Draw each element in the dialog in order
998 	GfxElementList::iterator i;
999 	for (i = _elements.begin(); i != _elements.end(); ++i) {
1000 		(*i)->draw();
1001 	}
1002 
1003 	// If there's a default button, then draw it
1004 	if (_defaultButton) {
1005 		_defaultButton->_flags |= GFXFLAG_THICK_FRAME;
1006 		_defaultButton->draw();
1007 	}
1008 
1009 	_gfxManager.deactivate();
1010 }
1011 
add(GfxElement * element)1012 void GfxDialog::add(GfxElement *element) {
1013 	_elements.push_back(element);
1014 	element->_owner = this;
1015 }
1016 
addElements(GfxElement * ge,...)1017 void GfxDialog::addElements(GfxElement *ge, ...) {
1018 	va_list va;
1019 	va_start(va, ge);
1020 	GfxElement *gfxElement = ge;
1021 	while (gfxElement) {
1022 		add(gfxElement);
1023 
1024 		gfxElement = va_arg(va, GfxElement *);
1025 	}
1026 
1027 	va_end(va);
1028 }
1029 
setTopLeft(int xp,int yp)1030 void GfxDialog::setTopLeft(int xp, int yp) {
1031 	_bounds.moveTo(xp - g_globals->_gfxEdgeAdjust * 2, yp - g_globals->_gfxEdgeAdjust * 2);
1032 }
1033 
setCenter(int xp,int yp)1034 void GfxDialog::setCenter(int xp, int yp) {
1035 	setTopLeft(xp - (_bounds.width() / 2), yp - (_bounds.height() / 2));
1036 }
1037 
execute(GfxButton * defaultButton)1038 GfxButton *GfxDialog::execute(GfxButton *defaultButton) {
1039 	_gfxManager.activate();
1040 
1041 	if (defaultButton != _defaultButton) {
1042 		if (_defaultButton) {
1043 			_defaultButton->_flags &= ~GFXFLAG_THICK_FRAME;
1044 			_defaultButton->draw();
1045 		}
1046 		_defaultButton = defaultButton;
1047 	}
1048 	if (_defaultButton) {
1049 		_defaultButton->_flags |= GFXFLAG_THICK_FRAME;
1050 		_defaultButton->draw();
1051 	}
1052 
1053 	// Event loop
1054 	GfxButton *selectedButton = NULL;
1055 
1056 	bool breakFlag = false;
1057 	while (!g_vm->shouldQuit() && !breakFlag) {
1058 		Event event;
1059 		while (g_globals->_events.getEvent(event) && !breakFlag) {
1060 			// Adjust mouse positions to be relative within the dialog
1061 			event.mousePos.x -= _gfxManager._bounds.left;
1062 			event.mousePos.y -= _gfxManager._bounds.top;
1063 
1064 			for (GfxElementList::iterator i = _elements.begin(); i != _elements.end(); ++i) {
1065 				if ((*i)->process(event))
1066 					selectedButton = static_cast<GfxButton *>(*i);
1067 			}
1068 
1069 			if (selectedButton) {
1070 				breakFlag = true;
1071 				break;
1072 			} else if (!event.handled) {
1073 				if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
1074 					selectedButton = NULL;
1075 					breakFlag = true;
1076 					break;
1077 				} else if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_RETURN)) {
1078 					selectedButton = defaultButton;
1079 					breakFlag = true;
1080 					break;
1081 				} else if (event.eventType == EVENT_KEYPRESS && handleKeypress(event, selectedButton)) {
1082 					breakFlag = true;
1083 				}
1084 			}
1085 		}
1086 
1087 		g_system->delayMillis(10);
1088 		GLOBALS._screen.update();
1089 	}
1090 
1091 	_gfxManager.deactivate();
1092 	if (_defaultButton)
1093 		_defaultButton->_flags &= ~GFXFLAG_THICK_FRAME;
1094 
1095 	return selectedButton;
1096 }
1097 
setPalette()1098 void GfxDialog::setPalette() {
1099 	if (g_vm->getGameID() != GType_Ringworld) {
1100 		if (g_vm->getGameID() == GType_BlueForce)
1101 			g_globals->_scenePalette.loadPalette(2);
1102 		g_globals->_scenePalette.setPalette(0, 1);
1103 		g_globals->_scenePalette.setPalette(g_globals->_gfxColors.background, 1);
1104 		g_globals->_scenePalette.setPalette(g_globals->_gfxColors.foreground, 1);
1105 		g_globals->_scenePalette.setPalette(g_globals->_fontColors.background, 1);
1106 		g_globals->_scenePalette.setPalette(g_globals->_fontColors.foreground, 1);
1107 		g_globals->_scenePalette.setEntry(255, 0xff, 0xff, 0xff);
1108 		g_globals->_scenePalette.setPalette(255, 1);
1109 	} else {
1110 		g_globals->_scenePalette.loadPalette(0);
1111 		g_globals->_scenePalette.setPalette(0, 1);
1112 		g_globals->_scenePalette.setPalette(g_globals->_scenePalette._colors.foreground, 1);
1113 		g_globals->_scenePalette.setPalette(g_globals->_fontColors.background, 1);
1114 		g_globals->_scenePalette.setPalette(g_globals->_fontColors.foreground, 1);
1115 		g_globals->_scenePalette.setPalette(255, 1);
1116 	}
1117 }
1118 
1119 /*--------------------------------------------------------------------------*/
1120 
GfxManager()1121 GfxManager::GfxManager() : _surface(g_globals->_screen), _oldManager(NULL) {
1122 	_font.setOwner(this);
1123 	_font._fillFlag = false;
1124 	_bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
1125 }
1126 
GfxManager(GfxSurface & s)1127 GfxManager::GfxManager(GfxSurface &s) : _surface(s), _oldManager(NULL) {
1128 	_font.setOwner(this);
1129 	_font._fillFlag = false;
1130 }
1131 
setDefaults()1132 void GfxManager::setDefaults() {
1133 	Rect screenBounds(0, 0, g_system->getWidth(), g_system->getHeight());
1134 
1135 	_surface.setBounds(screenBounds);
1136 	_bounds = screenBounds;
1137 	_pane0Rect4 = screenBounds;
1138 
1139 	_font._edgeSize = Common::Point(1, 1);
1140 	_font._colors = g_globals->_fontColors;
1141 	if (g_globals->_gfxFontNumber >= 0)
1142 		_font.setFontNumber(g_globals->_gfxFontNumber);
1143 }
1144 
activate()1145 void GfxManager::activate() {
1146 	assert(!contains(g_globals->_gfxManagers, this));
1147 	g_globals->_gfxManagers.push_front(this);
1148 }
1149 
deactivate()1150 void GfxManager::deactivate() {
1151 	// Assert that there will still be another manager, and we're correctly removing our own
1152 	assert((g_globals->_gfxManagers.size() > 1) && (&g_globals->gfxManager() == this));
1153 	g_globals->_gfxManagers.pop_front();
1154 }
1155 
getStringWidth(const char * s,int numChars)1156 int GfxManager::getStringWidth(const char *s, int numChars) {
1157 	return _font.getStringWidth(s, numChars);
1158 }
1159 
getStringWidth(const char * s)1160 int GfxManager::getStringWidth(const char *s) {
1161 	return _font.getStringWidth(s);
1162 }
1163 
getStringBounds(const char * s,Rect & bounds,int maxWidth)1164 void GfxManager::getStringBounds(const char *s, Rect &bounds, int maxWidth) {
1165 	_font.getStringBounds(s, bounds, maxWidth);
1166 }
1167 
fillArea(int xp,int yp,int color)1168 void GfxManager::fillArea(int xp, int yp, int color) {
1169 	_surface.setBounds(_bounds);
1170 	Rect tempRect(xp, yp, xp + _font._edgeSize.x, yp + _font._edgeSize.y);
1171 	_surface.fillRect(tempRect, color);
1172 }
1173 
fillRect(const Rect & bounds,int color)1174 void GfxManager::fillRect(const Rect &bounds, int color) {
1175 	_surface.setBounds(_bounds);
1176 	_surface.fillRect(bounds, color);
1177 }
1178 
fillRect2(int xs,int ys,int width,int height,int color)1179 void GfxManager::fillRect2(int xs, int ys, int width, int height, int color) {
1180 	_surface.setBounds(_bounds);
1181 	_surface.fillRect(Rect(xs, ys, xs + width, ys + height), color);
1182 }
1183 
1184 /**
1185  * Sets up the standard palette for dialog displays
1186  */
setDialogPalette()1187 void GfxManager::setDialogPalette() {
1188 	// Get the main palette information
1189 	byte palData[256 * 3];
1190 	uint count, start;
1191 	g_resourceManager->getPalette(0, &palData[0], &start, &count);
1192 	g_system->getPaletteManager()->setPalette(&palData[0], start, count);
1193 
1194 	// Miscellaneous
1195 	uint32 white = 0xffffffff;
1196 	g_system->getPaletteManager()->setPalette((const byte *)&white, 255, 1);
1197 }
1198 
1199 /**
1200  * Returns the angle of line connecting two points
1201  */
getAngle(const Common::Point & p1,const Common::Point & p2)1202 int GfxManager::getAngle(const Common::Point &p1, const Common::Point &p2) {
1203 	int xDiff = p2.x - p1.x, yDiff = p1.y - p2.y;
1204 
1205 	if (!xDiff && !yDiff)
1206 		return -1;
1207 	else if (!xDiff)
1208 		return (p2.y >= p1.y) ? 180 : 0;
1209 	else if (!yDiff)
1210 		return (p2.x >= p1.x) ? 90 : 270;
1211 	else {
1212 		int result = (((xDiff * 100) / ((abs(xDiff) + abs(yDiff))) * 90) / 100);
1213 
1214 		if (yDiff < 0)
1215 			result = 180 - result;
1216 		else if (xDiff < 0)
1217 			result += 360;
1218 
1219 		return result;
1220 	}
1221 }
1222 
copyFrom(GfxSurface & src,Rect destBounds,Region * priorityRegion)1223 void GfxManager::copyFrom(GfxSurface &src, Rect destBounds, Region *priorityRegion) {
1224 	_surface.setBounds(_bounds);
1225 
1226 	_surface.copyFrom(src, destBounds, priorityRegion);
1227 }
1228 
copyFrom(GfxSurface & src,int destX,int destY)1229 void GfxManager::copyFrom(GfxSurface &src, int destX, int destY) {
1230 	_surface.setBounds(_bounds);
1231 
1232 	_surface.copyFrom(src, destX, destY);
1233 }
1234 
copyFrom(GfxSurface & src,const Rect & srcBounds,const Rect & destBounds)1235 void GfxManager::copyFrom(GfxSurface &src, const Rect &srcBounds, const Rect &destBounds) {
1236 	_surface.setBounds(_bounds);
1237 
1238 	_surface.copyFrom(src, srcBounds, destBounds);
1239 }
1240 
1241 /*--------------------------------------------------------------------------*/
1242 
1243 
GfxFont()1244 GfxFont::GfxFont() {
1245 	if ((g_vm->getGameID() == GType_Ringworld) && (g_vm->getFeatures() & GF_DEMO))
1246 		_fontNumber = 0;
1247 	else
1248 		_fontNumber = 50;
1249 	_numChars = 0;
1250 	_bpp = 0;
1251 	_fontData = NULL;
1252 	_fillFlag = false;
1253 
1254 	_gfxManager = nullptr;
1255 }
1256 
~GfxFont()1257 GfxFont::~GfxFont() {
1258 	DEALLOCATE(_fontData);
1259 }
1260 
1261 /**
1262  * Sets the current active font number
1263  *
1264  * @fontNumber New font number
1265  */
setFontNumber(uint32 fontNumber)1266 void GfxFont::setFontNumber(uint32 fontNumber) {
1267 	if ((_fontNumber == fontNumber) && (_fontData))
1268 		return;
1269 
1270 	DEALLOCATE(_fontData);
1271 
1272 	_fontNumber = fontNumber;
1273 
1274 	_fontData = g_resourceManager->getResource(RES_FONT, _fontNumber, 0, true);
1275 	if (!_fontData)
1276 		_fontData = g_resourceManager->getResource(RES_FONT, _fontNumber, 0);
1277 
1278 	// Since some TsAGE game versions don't have a valid character count at offset 4, use the offset of the
1279 	// first charactre data to calculate the number of characters in the offset table preceeding it
1280 	_numChars = (READ_LE_UINT32(_fontData + 12) - 12) / 4;
1281 	assert(_numChars <= 256);
1282 
1283 	_fontSize.y = READ_LE_UINT16(_fontData + 6);
1284 	_fontSize.x = READ_LE_UINT16(_fontData + 8);
1285 	_bpp = READ_LE_UINT16(_fontData + 10);
1286 }
1287 
1288 /**
1289  * Returns the width of the given specified character
1290  *
1291  * @ch Character to return width of
1292  */
getCharWidth(char ch)1293 int GfxFont::getCharWidth(char ch) {
1294 	assert(_numChars > 0);
1295 	uint32 charOffset = READ_LE_UINT32(_fontData + 12 + (uint8)ch * 4);
1296 	return _fontData[charOffset] & 0x1f;
1297 }
1298 
1299 /**
1300  * Returns the width of the given string in the current font
1301  *
1302  * @s String to return the width of
1303  * @numChars Number of characters within the string to use
1304  */
getStringWidth(const char * s,int numChars)1305 int GfxFont::getStringWidth(const char *s, int numChars) {
1306 	assert(_numChars > 0);
1307 	int width = 0;
1308 
1309 	for (; numChars > 0; --numChars, ++s) {
1310 		uint32 charOffset = READ_LE_UINT32(_fontData + 12 + (uint8)*s * 4);
1311 		int charWidth = _fontData[charOffset] & 0x1f;
1312 
1313 		width += charWidth;
1314 	}
1315 
1316 	return width;
1317 }
1318 
1319 /**
1320  * Returns the width of the given string in the current font
1321  *
1322  * @s String to return the width of
1323  */
getStringWidth(const char * s)1324 int GfxFont::getStringWidth(const char *s) {
1325 	return getStringWidth(s, strlen(s));
1326 }
1327 
1328 /**
1329  * Returns the maximum number of characters for words that will fit into a given width
1330  *
1331  * @s Message to be analyzed
1332  * @maxWidth Maximum allowed width
1333  */
getStringFit(const char * & s,int maxWidth)1334 int GfxFont::getStringFit(const char *&s, int maxWidth) {
1335 	const char *nextWord = NULL;
1336 	const char *sStart = s;
1337 	int numChars = 1;
1338 	char nextChar;
1339 
1340 	for (;;) {
1341 		nextChar = *s++;
1342 
1343 		if ((nextChar == '\r') || (nextChar == '\0'))
1344 			break;
1345 
1346 		// Check if it's a word end
1347 		if (nextChar == ' ') {
1348 			nextWord = s;
1349 		}
1350 
1351 		int strWidth = getStringWidth(sStart, numChars);
1352 		if (strWidth > maxWidth) {
1353 			if (nextWord) {
1354 				s = nextWord;
1355 				nextChar = ' ';
1356 			}
1357 			break;
1358 		}
1359 
1360 		++numChars;
1361 	}
1362 
1363 	int totalChars = s - sStart;
1364 	if (nextChar == '\0')
1365 		--s;
1366 	if ((nextChar == ' ') || (nextChar == '\r') || (nextChar == '\0'))
1367 		--totalChars;
1368 
1369 	return totalChars;
1370 }
1371 
1372 /**
1373  * Fills out the passed rect with the dimensions of a given string word-wrapped to a
1374  * maximum specified width
1375  *
1376  * @s Message to be analyzed
1377  * @bounds Rectangle to put output size into
1378  * @maxWidth Maximum allowed line width in pixels
1379  */
getStringBounds(const char * s,Rect & bounds,int maxWidth)1380 void GfxFont::getStringBounds(const char *s, Rect &bounds, int maxWidth) {
1381 	if (maxWidth == 0) {
1382 		// No maximum width, so set bounds for a single line
1383 		bounds.set(0, 0, getStringWidth(s), getHeight());
1384 	} else {
1385 		int numLines = 0;
1386 		int lineWidth = 0;
1387 
1388 		// Loop to figure out the number of lines required, and the maximum line width
1389 		while (*s) {
1390 			const char *msg = s;
1391 			int numChars = getStringFit(msg, maxWidth);
1392 			lineWidth = MAX(lineWidth, getStringWidth(s, numChars));
1393 
1394 			s = msg;
1395 			++numLines;
1396 		}
1397 
1398 		bounds.set(0, 0, lineWidth, numLines * getHeight());
1399 	}
1400 }
1401 
1402 /**
1403  * Writes out a character at the currently set position using the active font
1404  *
1405  * @ch Character to display
1406  */
writeChar(const char ch)1407 int GfxFont::writeChar(const char ch) {
1408 	assert((_fontData != NULL) && ((uint8)ch < _numChars));
1409 	uint32 charOffset = READ_LE_UINT32(_fontData + 12 + (uint8)ch * 4);
1410 	int charWidth = _fontData[charOffset] & 0x1f;
1411 	int charHeight = (READ_LE_UINT16(_fontData + charOffset) >> 5) & 0x3f;
1412 	int yOffset = (_fontData[charOffset + 1] >> 3) & 0x1f;
1413 	const uint8 *dataP = &_fontData[charOffset + 2];
1414 
1415 	Rect charRect;
1416 	charRect.set(0, 0, charWidth, _fontSize.y);
1417 	charRect.translate(_topLeft.x + _position.x, _topLeft.y + _position.y + yOffset);
1418 
1419 	// Get the sub-section of the screen to update
1420 	Graphics::Surface dest = _gfxManager->getSurface().getSubArea(charRect);
1421 
1422 	if (_fillFlag)
1423 		dest.fillRect(charRect, _colors.background);
1424 
1425 	charRect.bottom = charRect.top + charHeight;
1426 	assert(charRect.height() <= dest.h);
1427 
1428 	// Display the character
1429 	int bitCtr = 0;
1430 	uint8 v = 0;
1431 	for (int yp = 0; yp < charHeight; ++yp) {
1432 		byte *destP = (byte *)dest.getBasePtr(0, yp);
1433 
1434 		for (int xs = 0; xs < charRect.width(); ++xs, ++destP) {
1435 			// Get the next color index to use
1436 			if ((bitCtr % 8) == 0) v = *dataP++;
1437 			int colIndex = 0;
1438 			for (int subCtr = 0; subCtr < _bpp; ++subCtr, ++bitCtr) {
1439 				colIndex = (colIndex << 1) | (v & 0x80 ? 1 : 0);
1440 				v <<= 1;
1441 			}
1442 
1443 			switch (colIndex) {
1444 			//case 0: *destP = _colors.background; break;
1445 			case 1: *destP = _colors.foreground; break;
1446 			case 2: *destP = _colors2.background; break;
1447 			case 3: *destP = _colors2.foreground; break;
1448 			}
1449 		}
1450 	}
1451 
1452 	// Move the text writing position
1453 	_position.x += charWidth;
1454 
1455 	return charWidth;
1456 }
1457 
1458 /**
1459  * Writes the specified number of characters from the specified string at the current text position
1460  *
1461  * @s String to display
1462  * @numChars Number of characters to print
1463  */
writeString(const char * s,int numChars)1464 void GfxFont::writeString(const char *s, int numChars) {
1465 	// Lock the surface for access
1466 	_gfxManager->lockSurface();
1467 
1468 	while ((numChars-- > 0) && (*s != '\0')) {
1469 		writeChar(*s);
1470 		++s;
1471 	}
1472 
1473 	// Release the surface lock
1474 	_gfxManager->unlockSurface();
1475 }
1476 
1477 /**
1478  * Writes the the specified string at the current text position
1479  *
1480  * @s String to display
1481  */
writeString(const char * s)1482 void GfxFont::writeString(const char *s) {
1483 	writeString(s, strlen(s));
1484 }
1485 
1486 /**
1487  * Writes a specified string within a given area with support for word wrapping and text alignment types
1488  *
1489  * @s String to display
1490  * @bounds Bounds to display the text within
1491  * @align Text alignment mode
1492  */
writeLines(const char * s,const Rect & bounds,TextAlign align)1493 void GfxFont::writeLines(const char *s, const Rect &bounds, TextAlign align) {
1494 	int lineNum = 0;
1495 
1496 	// Lock the surface for access
1497 	_gfxManager->lockSurface();
1498 
1499 	while (*s) {
1500 		const char *msgP = s;
1501 		int numChars = getStringFit(msgP, bounds.width());
1502 
1503 		_position.y = bounds.top + lineNum * getHeight();
1504 
1505 		switch (align) {
1506 		case ALIGN_RIGHT:
1507 			// Right aligned text
1508 			_position.x = bounds.right - getStringWidth(s, numChars);
1509 			writeString(s, numChars);
1510 			break;
1511 
1512 		case ALIGN_CENTER:
1513 			// Center aligned text
1514 			_position.x = bounds.left + (bounds.width() / 2) - (getStringWidth(s, numChars) / 2);
1515 			writeString(s, numChars);
1516 			break;
1517 
1518 		case ALIGN_JUSTIFIED: {
1519 			// Justified text
1520 			// Get the number of words in the string portion
1521 			int charCtr = 0, numWords = 0;
1522 			while (charCtr < numChars) {
1523 				if (s[charCtr] == ' ')
1524 					++numWords;
1525 				++charCtr;
1526 			}
1527 			// If end of string, count final word
1528 			if (*msgP == '\0')
1529 				++numWords;
1530 
1531 			// Display the words of the string
1532 			int spareWidth = bounds.width() - getStringWidth(s, numChars);
1533 			charCtr = 0;
1534 			_position.x = bounds.left;
1535 
1536 			while (charCtr < numChars) {
1537 				writeChar(s[charCtr]);
1538 				if ((numWords > 0) && (s[charCtr] == ' ')) {
1539 					int separationWidth = spareWidth / numWords;
1540 					spareWidth -= separationWidth;
1541 					--numWords;
1542 					_position.x += separationWidth;
1543 				}
1544 
1545 				++charCtr;
1546 			}
1547 			break;
1548 		}
1549 
1550 		case ALIGN_LEFT:
1551 		default:
1552 			// Standard text
1553 			_position.x = bounds.left;
1554 			writeString(s, numChars);
1555 			break;
1556 		}
1557 
1558 		// Next line
1559 		s = msgP;
1560 		++lineNum;
1561 	}
1562 
1563 	// Release the surface lock
1564 	_gfxManager->unlockSurface();
1565 }
1566 
1567 /*--------------------------------------------------------------------------*/
1568 
GfxFontBackup()1569 GfxFontBackup::GfxFontBackup() {
1570 	_edgeSize = g_globals->gfxManager()._font._edgeSize;
1571 	_position = g_globals->gfxManager()._font._position;
1572 	_colors = g_globals->gfxManager()._font._colors;
1573 	_fontNumber = g_globals->gfxManager()._font._fontNumber;
1574 }
1575 
~GfxFontBackup()1576 GfxFontBackup::~GfxFontBackup() {
1577 	g_globals->gfxManager()._font.setFontNumber(_fontNumber);
1578 	g_globals->gfxManager()._font._edgeSize = _edgeSize;
1579 	g_globals->gfxManager()._font._position = _position;
1580 	g_globals->gfxManager()._font._colors = _colors;
1581 }
1582 
1583 
1584 } // End of namespace TsAGE
1585