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