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 * Low level graphics interface.
22 */
23
24 #include "tinsel/graphics.h"
25 #include "tinsel/handle.h" // LockMem()
26 #include "tinsel/object.h"
27 #include "tinsel/palette.h"
28 #include "tinsel/scene.h"
29 #include "tinsel/tinsel.h"
30 #include "tinsel/scn.h"
31
32 #include "common/textconsole.h"
33
34 namespace Tinsel {
35
36 //----------------- LOCAL DEFINES --------------------
37
38 // Defines used in graphic drawing
39 #define CHARPTR_OFFSET 16
40 #define CHAR_WIDTH 4
41 #define CHAR_HEIGHT 4
42
43 extern uint8 g_transPalette[MAX_COLORS];
44
45 //----------------- SUPPORT FUNCTIONS ---------------------
46
47 // using ScummVM pixel format functions is too slow on some ports because of runtime overhead, let the compiler do the optimizations instead
t3getRGB(uint16 color,uint8 & r,uint8 & g,uint8 & b)48 static inline void t3getRGB(uint16 color, uint8 &r, uint8 &g, uint8 &b) {
49 r = (color >> 11) & 0x1F;
50 g = (color >> 5) & 0x3F;
51 b = (color ) & 0x1F;
52 }
53
t3getColor(uint8 r,uint8 g,uint8 b)54 static inline uint16 t3getColor(uint8 r, uint8 g, uint8 b) {
55 return ((r & 0x1F) << 11) | ((g & 0x3F) << 5) | (b & 0x1F);
56 }
57
58 /**
59 * PSX Block list unwinder.
60 * Chunk type 0x0003 (CHUNK_CHARPTR) in PSX version of DW 1 & 2 is compressed (original code
61 * calls the compression PJCRLE), thus we need to decompress it before passing data to drawing functions
62 */
psxPJCRLEUnwinder(uint16 imageWidth,uint16 imageHeight,uint8 * srcIdx)63 uint8* psxPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
64 uint32 remainingBlocks = 0;
65
66 uint16 compressionType = 0; // Kind of decompression to apply
67 uint16 controlBits = 0; // Control bits used to calculate the number of decompressed indexes
68 uint16 baseIndex = 0; // Base index to be repeated / incremented.
69
70 uint16 controlData;
71
72 uint8* dstIdx = nullptr;
73 uint8* destinationBuffer = nullptr;
74
75 if (!imageWidth || !imageHeight)
76 return NULL;
77
78 // Calculate needed index numbers, align width and height not next multiple of four
79 imageWidth = (imageWidth % 4) ? ((imageWidth / 4) + 1) * 4 : imageWidth;
80 imageHeight = (imageHeight % 4) ? ((imageHeight / 4) + 1) * 4 : imageHeight;
81 destinationBuffer = (uint8 *)malloc((imageWidth * imageHeight) / 8);
82 dstIdx = destinationBuffer;
83 remainingBlocks = (imageWidth * imageHeight) / 16;
84
85 while (remainingBlocks) { // Repeat until all blocks are decompressed
86 if (!controlBits) {
87 controlData = READ_16(srcIdx);
88 srcIdx += 2;
89
90 // If bit 15 of controlData is enabled, compression data is type 1.
91 // else if controlData has bit 16 enabled, compression type is 2,
92 // else there is no compression.
93 if (controlData & 0x4000)
94 compressionType = 2;
95 else if (controlData & 0x8000)
96 compressionType = 1;
97 else
98 compressionType = 0;
99
100 // Fetch control bits from controlData
101 controlBits = controlData & 0x3FFF;
102
103 // If there is compression, we need to fetch an index
104 // to be treated as "base" for compression.
105 if (compressionType != 0) {
106 controlData = READ_16(srcIdx);
107 srcIdx += 2;
108 baseIndex = controlData;
109 }
110 }
111
112 uint32 decremTiles; // Number of blocks that will be decompressed
113 if (remainingBlocks < controlBits) {
114 controlBits = controlBits - remainingBlocks;
115 decremTiles = remainingBlocks;
116 } else {
117 decremTiles = controlBits;
118 controlBits = 0;
119 }
120
121 // Decrement remaining blocks
122 remainingBlocks -= decremTiles;
123
124 // Manage different compressions
125 switch (compressionType) {
126 case 0: // No compression, plain copy of indexes
127 while (decremTiles) {
128 WRITE_LE_UINT16(dstIdx, READ_16(srcIdx));
129 srcIdx += 2;
130 dstIdx += 2;
131 decremTiles--;
132 }
133 break;
134 case 1: // Compression type 1, repeat a base index
135 while (decremTiles) {
136 WRITE_LE_UINT16(dstIdx, baseIndex);
137 dstIdx += 2;
138 decremTiles--;
139 }
140 break;
141 case 2: // Compression type 2, increment a base index
142 while (decremTiles) {
143 WRITE_LE_UINT16(dstIdx, baseIndex);
144 baseIndex++;
145 dstIdx += 2;
146 decremTiles--;
147 }
148 break;
149 default:
150 break;
151 }
152 }
153
154 // End.
155 return destinationBuffer;
156 }
157
158 /**
159 * Straight rendering of uncompressed data
160 */
t0WrtNonZero(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping)161 static void t0WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
162 int yClip = 0;
163
164 if (applyClipping) {
165 // Adjust the height down to skip any bottom clipping
166 pObj->height -= pObj->botClip;
167 yClip = pObj->topClip;
168 }
169
170 // Vertical loop
171 for (int y = 0; y < pObj->height; ++y) {
172 // Get the start of the next line output
173 uint8 *tempDest = destP;
174
175 int leftClip = applyClipping ? pObj->leftClip : 0;
176 int rightClip = applyClipping ? pObj->rightClip : 0;
177
178 // Horizontal loop
179 for (int x = 0; x < pObj->width; ) {
180 uint32 numBytes = READ_LE_UINT32(srcP);
181 srcP += sizeof(uint32);
182 bool repeatFlag = (numBytes & 0x80000000L) != 0;
183 numBytes &= 0x7fffffff;
184
185 uint clipAmount = MIN((int)numBytes & 0xff, leftClip);
186 leftClip -= clipAmount;
187 x += clipAmount;
188
189 if (repeatFlag) {
190 // Repeat of a given color
191 uint8 color = (numBytes >> 8) & 0xff;
192 int runLength = (numBytes & 0xff) - clipAmount;
193
194 int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
195 if (yClip == 0) {
196 if (color != 0)
197 memset(tempDest, color, rptLength);
198 tempDest += rptLength;
199 }
200
201 x += runLength;
202 } else {
203 // Copy a specified sequence length of pixels
204 srcP += clipAmount;
205
206 int runLength = numBytes - clipAmount;
207 int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
208 if (yClip == 0) {
209 memmove(tempDest, srcP, rptLength);
210 tempDest += rptLength;
211 }
212
213 int overflow = (numBytes % 4) == 0 ? 0 : 4 - (numBytes % 4);
214 x += runLength;
215 srcP += runLength + overflow;
216 }
217 }
218
219 // Move to next line
220 if (yClip > 0)
221 --yClip;
222 else
223 destP += SCREEN_WIDTH;
224 }
225 }
226
227 /**
228 * Straight rendering with transparency support, Mac variant
229 */
MacDrawTiles(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping)230 static void MacDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
231 int yClip = 0;
232
233 if (applyClipping) {
234 // Adjust the height down to skip any bottom clipping
235 pObj->height -= pObj->botClip;
236 yClip = pObj->topClip;
237 }
238
239 // Simple RLE-like scheme: the two first bytes of each data chunk determine
240 // if bytes should be repeated or copied.
241 // Example: 10 00 00 20 will repeat byte 0x0 0x10 times, and will copy 0x20
242 // bytes from the input stream afterwards
243
244 // Vertical loop
245 for (int y = 0; y < pObj->height; ++y) {
246 // Get the start of the next line output
247 uint8 *tempDest = destP;
248
249 int leftClip = applyClipping ? pObj->leftClip : 0;
250 int rightClip = applyClipping ? pObj->rightClip : 0;
251
252 // Horizontal loop
253 for (int x = 0; x < pObj->width; ) {
254 byte repeatBytes = *srcP++;
255
256 if (repeatBytes) {
257 uint clipAmount = MIN<int>(repeatBytes, leftClip);
258 leftClip -= clipAmount;
259 x += clipAmount;
260
261 // Repeat of a given color
262 byte color = *srcP++;
263 int runLength = repeatBytes - clipAmount;
264 int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
265 if (yClip == 0) {
266 if (color != 0)
267 memset(tempDest, color, rptLength);
268 tempDest += rptLength;
269 }
270
271 x += runLength;
272 } else {
273 // Copy a specified sequence length of pixels
274 byte copyBytes = *srcP++;
275
276 uint clipAmount = MIN<int>(copyBytes, leftClip);
277 leftClip -= clipAmount;
278 x += clipAmount;
279 srcP += clipAmount;
280
281 int runLength = copyBytes - clipAmount;
282 int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
283 if (yClip == 0) {
284 memmove(tempDest, srcP, rptLength);
285 tempDest += rptLength;
286 }
287
288 int overflow = (copyBytes & 1);
289 x += runLength;
290 srcP += runLength + overflow;
291 }
292 } // horizontal loop
293
294 // Move to next line
295 if (yClip > 0)
296 --yClip;
297 else
298 destP += SCREEN_WIDTH;
299 } // vertical loop
300 }
301
302
303 /**
304 * Straight rendering with transparency support, PSX variant supporting also 4-BIT clut data
305 */
PsxDrawTiles(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping,bool fourBitClut,uint32 psxSkipBytes,byte * psxMapperTable,bool transparency)306 static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool fourBitClut, uint32 psxSkipBytes, byte *psxMapperTable, bool transparency) {
307 // Set up the offset between destination blocks
308 int rightClip = applyClipping ? pObj->rightClip : 0;
309 Common::Rect boxBounds;
310
311 if (applyClipping) {
312 // Adjust the height down to skip any bottom clipping
313 pObj->height -= pObj->botClip;
314
315 // Make adjustment for the top clipping row
316 srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
317 pObj->height -= pObj->topClip;
318 pObj->topClip %= 4;
319 }
320
321 // Vertical loop
322 while (pObj->height > 0) {
323 // Get the start of the next line output
324 uint8 *tempDest = destP;
325
326 // Get the line width, and figure out which row range within the 4 row high blocks
327 // will be displayed if clipping is to be taken into account
328 int width = pObj->width;
329
330 if (!applyClipping) {
331 // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
332 boxBounds.top = 0;
333 boxBounds.bottom = 3;
334 boxBounds.left = 0;
335 } else {
336 // Handle any possible clipping at the top of the char block.
337 // We already handled topClip partially at the beginning of this function.
338 // Hence the only non-zero values it can assume at this point are 1,2,3,
339 // and that only during the very first iteration (i.e. when the top char
340 // block is drawn only partially). In particular, we set topClip to zero,
341 // as all following blocks are not to be top clipped.
342 boxBounds.top = pObj->topClip;
343 pObj->topClip = 0;
344
345 boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
346
347 // Handle any possible clipping at the start of the line
348 boxBounds.left = pObj->leftClip;
349 if (boxBounds.left >= 4) {
350 srcP += sizeof(uint16) * (boxBounds.left >> 2);
351 width -= boxBounds.left & 0xfffc;
352 boxBounds.left %= 4;
353 }
354
355 width -= boxBounds.left;
356 }
357
358 // Horizontal loop
359 while (width > rightClip) {
360 boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
361 assert(boxBounds.bottom >= boxBounds.top);
362 assert(boxBounds.right >= boxBounds.left);
363
364 int16 indexVal = READ_LE_UINT16(srcP);
365 srcP += sizeof(uint16);
366
367 // Draw a 4x4 block based on the opcode as in index into the block list
368 // In case we have a 4-bit CLUT image, blocks are 2x4 bytes, then expanded into 4x4
369 const uint8 *p;
370 if (fourBitClut)
371 p = (uint8 *)pObj->charBase + psxSkipBytes + (indexVal << 3);
372 else
373 p = (uint8 *)pObj->charBase + psxSkipBytes + (indexVal << 4);
374
375 p += boxBounds.top * (fourBitClut ? sizeof(uint16) : sizeof(uint32));
376 for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += (fourBitClut ? sizeof(uint16) : sizeof(uint32))) {
377 if (!fourBitClut) {
378 if (!transparency)
379 Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
380 else
381 for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp) {
382 if (*(p + xp))
383 *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *(p + xp);
384 }
385 } else {
386 for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp) {
387 // Extract pixel value from byte
388 byte pixValue = (*(p + (xp / 2)) & ((xp % 2) ? 0xf0 : 0x0f)) >> ((xp % 2) ? 4 : 0);
389 if (pixValue || !transparency)
390 *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = psxMapperTable[pixValue];
391 }
392 }
393 }
394
395 tempDest += boxBounds.right - boxBounds.left + 1;
396 width -= 3 - boxBounds.left + 1;
397
398 // None of the remaining horizontal blocks should be left clipped
399 boxBounds.left = 0;
400 }
401
402 // If there is any width remaining, there must be a right edge clipping
403 if (width >= 0)
404 srcP += sizeof(uint16) * ((width + 3) >> 2);
405
406 // Move to next line line
407 pObj->height -= boxBounds.bottom - boxBounds.top + 1;
408 destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
409 }
410 }
411
412 /**
413 * Straight rendering with transparency support
414 */
WrtNonZero(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping)415 static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
416 // Set up the offset between destination blocks
417 int rightClip = applyClipping ? pObj->rightClip : 0;
418 Common::Rect boxBounds;
419
420 if (applyClipping) {
421 // Adjust the height down to skip any bottom clipping
422 pObj->height -= pObj->botClip;
423
424 // Make adjustment for the top clipping row
425 srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
426 pObj->height -= pObj->topClip;
427 pObj->topClip %= 4;
428 }
429
430 // Vertical loop
431 while (pObj->height > 0) {
432 // Get the start of the next line output
433 uint8 *tempDest = destP;
434
435 // Get the line width, and figure out which row range within the 4 row high blocks
436 // will be displayed if clipping is to be taken into account
437 int width = pObj->width;
438
439 if (!applyClipping) {
440 // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
441 boxBounds.top = 0;
442 boxBounds.bottom = 3;
443 boxBounds.left = 0;
444 } else {
445 // Handle any possible clipping at the top of the char block.
446 // We already handled topClip partially at the beginning of this function.
447 // Hence the only non-zero values it can assume at this point are 1,2,3,
448 // and that only during the very first iteration (i.e. when the top char
449 // block is drawn only partially). In particular, we set topClip to zero,
450 // as all following blocks are not to be top clipped.
451 boxBounds.top = pObj->topClip;
452 pObj->topClip = 0;
453
454 boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
455
456 // Handle any possible clipping at the start of the line
457 boxBounds.left = pObj->leftClip;
458 if (boxBounds.left >= 4) {
459 srcP += sizeof(uint16) * (boxBounds.left >> 2);
460 width -= boxBounds.left & 0xfffc;
461 boxBounds.left %= 4;
462 }
463
464 width -= boxBounds.left;
465 }
466
467 // Horizontal loop
468 while (width > rightClip) {
469 boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
470 assert(boxBounds.bottom >= boxBounds.top);
471 assert(boxBounds.right >= boxBounds.left);
472
473 int16 indexVal = READ_LE_UINT16(srcP);
474 srcP += sizeof(uint16);
475
476 if (indexVal >= 0) {
477 // Draw a 4x4 block based on the opcode as in index into the block list
478 const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4);
479 p += boxBounds.top * sizeof(uint32);
480 for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) {
481 Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
482 }
483
484 } else {
485 // Draw a 4x4 block with transparency support
486 indexVal &= 0x7fff;
487
488 // If index is zero, then skip drawing the block completely
489 if (indexVal > 0) {
490 // Use the index along with the object's translation offset
491 const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4);
492
493 // Loop through each pixel - only draw a pixel if it's non-zero
494 p += boxBounds.top * sizeof(uint32);
495 for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) {
496 p += boxBounds.left;
497 for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) {
498 if (*p)
499 *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p;
500 }
501 p += 3 - boxBounds.right;
502 }
503 }
504 }
505
506 tempDest += boxBounds.right - boxBounds.left + 1;
507 width -= 3 - boxBounds.left + 1;
508
509 // None of the remaining horizontal blocks should be left clipped
510 boxBounds.left = 0;
511 }
512
513 // If there is any width remaining, there must be a right edge clipping
514 if (width >= 0)
515 srcP += sizeof(uint16) * ((width + 3) >> 2);
516
517 // Move to next line line
518 pObj->height -= boxBounds.bottom - boxBounds.top + 1;
519 destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
520 }
521 }
522
523 /**
524 * Tinsel 2 Straight rendering with transparency support
525 */
t2WrtNonZero(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping,bool horizFlipped)526 static void t2WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool horizFlipped) {
527 // Setup for correct clipping of object edges
528 int yClip = applyClipping ? pObj->topClip : 0;
529 if (applyClipping)
530 pObj->height -= pObj->botClip;
531 int numBytes;
532 int clipAmount;
533
534 // WORKAROUND: One of the mortician frames has several corrupt bytes in the Russian version
535 if ((pObj->hBits == 2517583660UL) && (_vm->getLanguage() == Common::RU_RUS)) {
536 uint8 correctBytes[5] = {0xA3, 0x00, 0x89, 0xC0, 0xA6};
537 Common::copy(&correctBytes[0], &correctBytes[5], srcP);
538 }
539
540 for (int y = 0; y < pObj->height; ++y) {
541 // Get the position to start writing out from
542 uint8 *tempP = !horizFlipped ? destP :
543 destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1;
544 int leftClip = applyClipping ? pObj->leftClip : 0;
545 int rightClip = applyClipping ? pObj->rightClip : 0;
546 if (horizFlipped)
547 SWAP(leftClip, rightClip);
548
549 int x = 0;
550 while (x < pObj->width) {
551 // Get the next opcode
552 numBytes = *srcP++;
553 if (numBytes & 0x80) {
554 // Run length following
555 numBytes &= 0x7f;
556 clipAmount = MIN(numBytes, leftClip);
557 leftClip -= clipAmount;
558 x+= clipAmount;
559
560 int runLength = numBytes - clipAmount;
561 uint8 color = *srcP++;
562
563 if ((yClip == 0) && (runLength > 0) && (color != 0)) {
564 runLength = MIN(runLength, pObj->width - rightClip - x);
565
566 if (runLength > 0) {
567 // Non-transparent run length
568 color += pObj->constant;
569 if (horizFlipped)
570 Common::fill(tempP - runLength + 1, tempP + 1, color);
571 else
572 Common::fill(tempP, tempP + runLength, color);
573 }
574 }
575
576 if (horizFlipped)
577 tempP -= runLength;
578 else
579 tempP += runLength;
580
581 x += numBytes - clipAmount;
582
583 } else {
584 // Dump a length of pixels
585 clipAmount = MIN(numBytes, leftClip);
586 leftClip -= clipAmount;
587 srcP += clipAmount;
588 int runLength = numBytes - clipAmount;
589 x += numBytes - runLength;
590
591 for (int xp = 0; xp < runLength; ++xp) {
592 if ((yClip > 0) || (x >= (pObj->width - rightClip)))
593 ++srcP;
594 else if (horizFlipped)
595 *tempP-- = pObj->constant + *srcP++;
596 else
597 *tempP++ = pObj->constant + *srcP++;
598 ++x;
599 }
600 }
601 }
602 assert(x == pObj->width);
603
604 if (yClip > 0)
605 --yClip;
606 else
607 destP += SCREEN_WIDTH;
608 }
609 }
610
611
t3WrtNonZero(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP)612 static void t3WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP) {
613 bool applyClipping = (pObj->flags & DMA_CLIP) != 0;
614 bool horizFlipped = (pObj->flags & DMA_FLIPH) != 0;
615
616 if (pObj->isRLE)
617 {
618 int yClip = applyClipping ? pObj->topClip : 0;
619 if (applyClipping) {
620 pObj->height -= pObj->botClip;
621 }
622
623 for (int y = 0; y < pObj->height; ++y) {
624 uint8 *tempP = destP;
625 int leftClip = applyClipping ? pObj->leftClip : 0;
626 int rightClip = applyClipping ? pObj->rightClip : 0;
627
628 if (horizFlipped) {
629 SWAP(leftClip, rightClip);
630 }
631
632 int x = 0;
633 while (x < pObj->width) {
634 int numPixels = READ_LE_UINT16(srcP);
635 srcP += 2;
636
637 if (numPixels & 0x8000) {
638 numPixels &= 0x7FFF;
639
640 int clipAmount = MIN(numPixels, leftClip);
641 leftClip -= clipAmount;
642 x += clipAmount;
643
644 int runLength = numPixels - clipAmount;
645
646 uint16 color = READ_LE_UINT16(srcP);
647 srcP += 2;
648
649 if ((yClip == 0) && (runLength > 0)) {
650 runLength = MIN(runLength, pObj->width - rightClip - x);
651
652 for (int xp = 0; xp < runLength; ++xp) {
653 if (color != 0xF81F) { // "zero" for Tinsel 3 - magenta in 565
654 WRITE_UINT16(tempP, color);
655 }
656 tempP += (horizFlipped ? -2 : 2);
657 }
658 }
659
660 x += numPixels - clipAmount;
661 } else {
662 int clipAmount = MIN(numPixels, leftClip);
663 leftClip -= clipAmount;
664 srcP += clipAmount * 2;
665 int runLength = numPixels - clipAmount;
666 x += numPixels - runLength;
667
668 for (int xp = 0; xp < runLength; ++xp) {
669 if ((yClip == 0) && (x < (pObj->width - rightClip))) {
670 uint16 color = READ_LE_UINT16(srcP);
671
672 if (color != 0xF81F) { // "zero" for Tinsel 3 - magenta in 565
673 WRITE_UINT16(tempP, color);
674 }
675
676 tempP += (horizFlipped ? -2 : 2);
677 }
678 srcP += 2;
679 ++x;
680 }
681 }
682 }
683 // assert(x == pObj->width);
684
685 if (yClip > 0) {
686 --yClip;
687 } else {
688 destP += SCREEN_WIDTH * 2;
689 }
690 }
691 return;
692 }
693
694 if (applyClipping) {
695 srcP += (pObj->topClip * pObj->width * 2);
696
697 pObj->height -= pObj->topClip + pObj->botClip;
698 pObj->width -= pObj->leftClip + pObj->rightClip;
699 }
700
701 for (int y = 0; y < pObj->height; ++y) {
702 uint8 *tempP = destP;
703 srcP += pObj->leftClip * 2;
704 for (int x = 0; x < pObj->width; ++x) {
705 uint16 color = READ_LE_UINT16(srcP);
706 srcP += 2;
707
708 if (color != 0xF81F) { // "zero" for Tinsel 3 - magenta in 565
709 WRITE_UINT16(tempP, color);
710 }
711
712 tempP += 2;
713 }
714 srcP += pObj->rightClip * 2;
715 destP += SCREEN_WIDTH * 2;
716 }
717 }
718
719 /**
720 * Fill the destination area with a constant color
721 */
WrtConst(DRAWOBJECT * pObj,uint8 * destP,bool applyClipping)722 static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
723 if (applyClipping) {
724 pObj->height -= pObj->topClip + pObj->botClip;
725 pObj->width -= pObj->leftClip + pObj->rightClip;
726
727 if (pObj->width <= 0)
728 return;
729 }
730
731 // Loop through any remaining lines
732 while (pObj->height > 0) {
733 Common::fill(destP, destP + pObj->width, pObj->constant);
734
735 --pObj->height;
736 destP += SCREEN_WIDTH;
737 }
738 }
739
740 /**
741 * Tinsel 3 Rendering with transparency support, run-length is not supported
742 */
t3TransWNZ(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP)743 static void t3TransWNZ(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP) {
744 bool applyClipping = (pObj->flags & DMA_CLIP) != 0;
745
746 if (applyClipping) {
747 srcP += (pObj->topClip * pObj->width * 2);
748
749 pObj->height -= pObj->topClip + pObj->botClip;
750 pObj->width -= pObj->leftClip + pObj->rightClip;
751 }
752
753 for (int y = 0; y < pObj->height; ++y) {
754 // Get the position to start writing out from
755 uint8 *tempP = destP;
756 srcP += pObj->leftClip * 2;
757 for (int x = 0; x < pObj->width; ++x) {
758 uint32 color = READ_LE_UINT16(srcP); //uint32 for checking overflow in blending
759 if (color != 0xF81F) { // "zero" for Tinsel 3 - magenta in 565
760 uint8 srcR, srcG, srcB;
761 t3getRGB(color, srcR, srcG, srcB);
762
763 uint16 dstColor = READ_LE_UINT16(tempP);
764 uint8 dstR, dstG, dstB;
765 t3getRGB(dstColor, dstR, dstG, dstB);
766
767 if ((pObj->colorFlags & 4) != 0) { // additive blending
768 // orginal algo:
769 // color &= 0b1111011111011111;
770 // color += dstColor & 0b1111011111011111;
771 // if (color > 0xFFFF) {
772 // color |= 0b1111000000000000;
773 // }
774 // if (color & 0b0000100000000000) {
775 // color |= 0b0000011111000000;
776 // }
777 // if (color & 0b0000000000100000) {
778 // color |= 0b0000000000011111;
779 // }
780 // color &= 0b1111011111011111;
781 color = t3getColor(
782 MIN(srcR + dstR, 0x1F),
783 MIN(srcG + dstG, 0x3F),
784 MIN(srcB + dstB, 0x1F)
785 );
786 } else {
787 // original algo looks simple but does not check for overflow
788 // color += (dstColor & 0b1111011111011111) >> 1;
789 color = t3getColor(
790 MIN(srcR + (dstR / 2), 0x1F),
791 MIN(srcG + (dstG / 2), 0x3F),
792 MIN(srcB + (dstB / 2), 0x1F)
793 );
794 }
795 WRITE_UINT16(tempP, color);
796 }
797 tempP += 2;
798 srcP += 2;
799 }
800 srcP += pObj->rightClip * 2;
801 destP += SCREEN_WIDTH * 2;
802 }
803 }
804
805 /**
806 * Translates the destination surface within the object's bounds using the transparency
807 * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp)
808 */
WrtTrans(DRAWOBJECT * pObj,uint8 * destP,bool applyClipping)809 static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
810 if (applyClipping) {
811 pObj->height -= pObj->topClip + pObj->botClip;
812 pObj->width -= pObj->leftClip + pObj->rightClip;
813
814 if (pObj->width <= 0)
815 return;
816 }
817
818 // Set up the offset between destination lines
819 int lineOffset = SCREEN_WIDTH - pObj->width;
820
821 // Loop through any remaining lines
822 while (pObj->height > 0) {
823 for (int i = 0; i < pObj->width; ++i, ++destP)
824 *destP = g_transPalette[*destP];
825
826 --pObj->height;
827 destP += lineOffset;
828 }
829 }
830
831 /**
832 * Copies an uncompressed block of data straight to the screen
833 */
WrtAll(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping)834 static void WrtAll(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
835 int objWidth = pObj->width;
836
837 if (applyClipping) {
838 srcP += (pObj->topClip * pObj->width) + pObj->leftClip;
839
840 pObj->height -= pObj->topClip + pObj->botClip;
841 pObj->width -= pObj->leftClip + pObj->rightClip;
842
843 if (pObj->width <= 0)
844 return;
845 }
846
847 for (int y = 0; y < pObj->height; ++y) {
848 Common::copy(srcP, srcP + pObj->width, destP);
849 srcP += objWidth;
850 destP += SCREEN_WIDTH;
851 }
852 }
853
854 /**
855 * Copies an uncompressed block of data straight to the screen, Tinsel 3 is using 16bpp, hence "* 2"
856 */
t3WrtAll(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP)857 static void t3WrtAll(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP) {
858 bool applyClipping = (pObj->flags & DMA_CLIP) != 0;
859 int objWidth = pObj->width;
860
861 if (applyClipping) {
862 srcP += (pObj->topClip * pObj->width * 2) + (pObj->leftClip * 2);
863
864 pObj->height -= pObj->topClip + pObj->botClip;
865 pObj->width -= pObj->leftClip + pObj->rightClip;
866
867 if (pObj->width <= 0)
868 return;
869 }
870
871 for (int y = 0; y < pObj->height; ++y) {
872 Common::copy(srcP, srcP + (pObj->width * 2), destP);
873 srcP += objWidth * 2;
874 destP += SCREEN_WIDTH * 2;
875 }
876 }
877
878
879 /**
880 * Renders a packed data stream with a variable sized palette
881 */
PackedWrtNonZero(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP,bool applyClipping,bool horizFlipped,int packingType)882 static void PackedWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP,
883 bool applyClipping, bool horizFlipped, int packingType) {
884 uint8 numColors = 0;
885 uint8 *colorTable = nullptr;
886 int topClip = 0;
887 int xOffset = 0;
888 int numBytes, color;
889 int v;
890
891 if (_vm->getLanguage() == Common::RU_RUS) {
892 // WORKAROUND: One of the mortician frames has several corrupt bytes in the Russian version
893 if (pObj->hBits == 2517583393UL) {
894 uint8 correctBytes[5] = {0x00, 0x00, 0x17, 0x01, 0x00};
895 Common::copy(&correctBytes[0], &correctBytes[5], srcP + 267);
896 }
897 // WORKAROUND: One of Dibbler's frames in the end sequence has corrupt bytes in the Russian version
898 if (pObj->hBits == 33651742) {
899 uint8 correctBytes[40] = {
900 0x06, 0xc0, 0xd6, 0xc1, 0x09, 0xce, 0x0d, 0x24, 0x02, 0x12, 0x01, 0x00, 0x00, 0x23, 0x21, 0x32,
901 0x12, 0x00, 0x00, 0x20, 0x01, 0x11, 0x32, 0x12, 0x01, 0x00, 0x00, 0x1b, 0x02, 0x11, 0x34, 0x11,
902 0x00, 0x00, 0x18, 0x01, 0x11, 0x35, 0x21, 0x01
903 };
904 Common::copy(&correctBytes[0], &correctBytes[40], srcP);
905 }
906 }
907
908 if (applyClipping) {
909 pObj->height -= pObj->botClip;
910 topClip = pObj->topClip;
911 }
912
913 if (packingType == 3) {
914 // Variable colors
915 numColors = *srcP++;
916 colorTable = srcP;
917 srcP += numColors;
918 }
919
920 for (int y = 0; y < pObj->height; ++y) {
921 // Get the position to start writing out from
922 uint8 *tempP = !horizFlipped ? destP :
923 destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1;
924 int leftClip = applyClipping ? pObj->leftClip : 0;
925 int rightClip = applyClipping ? pObj->rightClip : 0;
926 if (horizFlipped)
927 SWAP(leftClip, rightClip);
928 bool eolFlag = false;
929
930 // Get offset for first pixels in next line
931 xOffset = *srcP++;
932
933 int x = 0;
934 while (x < pObj->width) {
935 // Get next run size and color to use
936 for (;;) {
937 if (xOffset > 0) {
938 x += xOffset;
939
940 // Reduce offset amount by any remaining left clipping
941 v = MIN(xOffset, leftClip);
942 xOffset -= v;
943 leftClip -= v;
944
945 if (horizFlipped) tempP -= xOffset; else tempP += xOffset;
946 xOffset = 0;
947 }
948
949 v = *srcP++;
950 numBytes = v & 0xf; // No. bytes 1-15
951 if (packingType == 3)
952 color = colorTable[v >> 4];
953 else
954 color = pObj->baseCol + (v >> 4);
955
956 if (numBytes != 0)
957 break;
958
959 numBytes = *srcP++;
960 if (numBytes >= 16)
961 break;
962
963 xOffset = numBytes + v;
964 if (xOffset == 0) {
965 // End of line encountered
966 eolFlag = true;
967 break;
968 }
969 }
970
971 if (eolFlag)
972 break;
973
974 // Apply clipping on byte sequence
975 v = MIN(numBytes, leftClip);
976 leftClip -= v;
977 numBytes -= v;
978 x += v;
979
980 while (numBytes-- > 0) {
981 if ((topClip == 0) && (x < (pObj->width - rightClip))) {
982 *tempP = color;
983 if (horizFlipped) --tempP; else ++tempP;
984 }
985 ++x;
986 }
987 }
988 assert(x <= pObj->width);
989
990 if (!eolFlag) {
991 // Assert that the next bytes signal a line end
992 uint8 d = *srcP++;
993 assert((d & 0xf) == 0);
994 d = *srcP++;
995 assert(d == 0);
996 }
997
998 if (topClip > 0)
999 --topClip;
1000 else
1001 destP += SCREEN_WIDTH;
1002 }
1003 }
1004
t3WrtText(DRAWOBJECT * pObj,uint8 * srcP,uint8 * destP)1005 static void t3WrtText(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP) {
1006 bool applyClipping = (pObj->flags & DMA_CLIP) != 0;
1007
1008 if (applyClipping) {
1009 srcP += (pObj->topClip * pObj->width * 2);
1010
1011 pObj->height -= pObj->topClip + pObj->botClip;
1012 pObj->width -= pObj->leftClip + pObj->rightClip;
1013 }
1014
1015 uint32 baseColor = t3GetBaseColor();
1016
1017 for (int y = 0; y < pObj->height; ++y) {
1018 // Get the position to start writing out from
1019 uint8 *tempP = destP;
1020 srcP += pObj->leftClip * 2;
1021 for (int x = 0; x < pObj->width; ++x) {
1022 uint32 color = READ_LE_UINT16(srcP);
1023 if (color != 0xF81F) { // "zero" for Tinsel 3 - magenta in 565
1024 if (color == baseColor) {
1025 color = pObj->constant;
1026 }
1027 WRITE_UINT16(tempP, color);
1028 }
1029 tempP += 2;
1030 srcP += 2;
1031 }
1032 srcP += pObj->rightClip * 2;
1033 destP += SCREEN_WIDTH * 2;
1034 }
1035 }
1036
1037 //----------------- MAIN FUNCTIONS ---------------------
1038
1039 /**
1040 * Clears both the screen surface buffer and screen to the specified value
1041 */
ClearScreen()1042 void ClearScreen() {
1043 byte blackColorIndex = (!TinselV1Mac) ? 0 : 255;
1044 void *pDest = _vm->screen().getPixels();
1045 memset(pDest, blackColorIndex, SCREEN_WIDTH * SCREEN_HEIGHT);
1046 g_system->fillScreen(blackColorIndex);
1047 g_system->updateScreen();
1048 }
1049
1050 /**
1051 * Updates the screen surface within the following rectangle
1052 */
UpdateScreenRect(const Common::Rect & pClip)1053 void UpdateScreenRect(const Common::Rect &pClip) {
1054 int yOffset = TinselV2 ? (g_system->getHeight() - SCREEN_HEIGHT) / 2 : 0;
1055 byte *pSrc = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top);
1056 g_system->copyRectToScreen(pSrc, _vm->screen().pitch, pClip.left, pClip.top + yOffset,
1057 pClip.width(), pClip.height());
1058 }
1059
1060 /**
1061 * Draws the specified object onto the screen surface buffer
1062 */
DrawObject(DRAWOBJECT * pObj)1063 void DrawObject(DRAWOBJECT *pObj) {
1064 uint8 *srcPtr = nullptr;
1065 uint8 *destPtr;
1066 byte psxMapperTable[16];
1067
1068 bool psxFourBitClut = false; // Used by Tinsel PSX, true if an image using a 4bit CLUT is rendered
1069 bool psxRLEindex = false; // Used by Tinsel PSX, true if an image is using PJCRLE compressed indexes
1070 uint32 psxSkipBytes = 0; // Used by Tinsel PSX, number of bytes to skip before counting indexes for image tiles
1071
1072 if ((pObj->width <= 0) || (pObj->height <= 0))
1073 // Empty image, so return immediately
1074 return;
1075
1076 // If writing constant data, don't bother locking the data pointer and reading src details
1077 if (((pObj->flags & DMA_CONST) == 0) || (TinselV3 && ((pObj->flags & 0x05) == 0x05))) {
1078 if (TinselV2) {
1079 srcPtr = (byte *)_vm->_handle->LockMem(pObj->hBits);
1080 pObj->charBase = nullptr;
1081 pObj->transOffset = 0;
1082 } else {
1083 byte *p = (byte *)_vm->_handle->LockMem(pObj->hBits & HANDLEMASK);
1084
1085 srcPtr = p + (pObj->hBits & OFFSETMASK);
1086 pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
1087 pObj->transOffset = READ_LE_UINT32(p + 0x14);
1088
1089 // Decompress block indexes for Discworld PSX
1090 if (TinselV1PSX) {
1091 uint8 paletteType = *(srcPtr); // if 0x88 we are using an 8bit palette type, if 0x44 we are using a 4 bit PSX CLUT
1092 uint8 indexType = *(srcPtr + 1); // if 0xCC indexes for this image are compressed with PCJRLE, if 0xDD indexes are not compressed
1093
1094 switch (paletteType) {
1095 case 0x88: // Normal 8-bit palette
1096 psxFourBitClut = false;
1097 psxSkipBytes = 0;
1098 switch (indexType) {
1099 case 0xDD: // Normal uncompressed indexes
1100 psxRLEindex = false;
1101 srcPtr += sizeof(uint16); // Get to the beginning of index data
1102 break;
1103 case 0xCC: // PJCRLE compressed indexes
1104 psxRLEindex = true;
1105 srcPtr = psxPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16));
1106 break;
1107 default:
1108 error("Unknown PSX index type 0x%.2X", indexType);
1109 break;
1110 }
1111 break;
1112 case 0x44: // PSX 4-bit CLUT
1113 psxPaletteMapper(pObj->pPal, srcPtr + sizeof(uint16), psxMapperTable);
1114
1115 psxFourBitClut = true;
1116 psxSkipBytes = READ_LE_UINT32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip
1117 switch (indexType) {
1118 case 0xDD: // Normal uncompressed indexes
1119 psxRLEindex = false;
1120 srcPtr += sizeof(uint16) * 17; // Skip image type and clut, and get to beginning of index data
1121 break;
1122 case 0xCC: // PJCRLE compressed indexes
1123 psxRLEindex = true;
1124 srcPtr = psxPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16) * 17);
1125 break;
1126 default:
1127 error("Unknown PSX index type 0x%.2X", indexType);
1128 break;
1129 }
1130 break;
1131 default:
1132 error("Unknown PSX palette type 0x%.2X", paletteType);
1133 break;
1134 }
1135 }
1136 }
1137
1138 }
1139
1140 // Get destination starting point
1141 destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos);
1142
1143 // Handle various draw types
1144 uint8 typeId = pObj->flags & 0xff;
1145 int packType = pObj->flags >> 14; // TinselV2
1146
1147 if (TinselV2 && packType != 0) {
1148 // Color packing for TinselV2
1149
1150 if (packType == 1)
1151 pObj->baseCol = 0xF0; // 16 from 240
1152 else if (packType == 2)
1153 pObj->baseCol = 0xE0; // 16 from 224
1154 // 3 = variable color
1155
1156 PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0,
1157 (pObj->flags & DMA_FLIPH), packType);
1158 } else {
1159 switch (typeId) {
1160 case 0x01: // all versions, draw sprite without clipping
1161 case 0x41: // all versions, draw sprite with clipping
1162 case 0x02: // TinselV2, draw sprite without clipping
1163 case 0x11: // TinselV2, draw sprite without clipping, flipped horizontally
1164 case 0x42: // TinselV2, draw sprite with clipping
1165 case 0x51: // TinselV2, draw sprite with clipping, flipped horizontally
1166 assert(TinselV2 || (typeId == 0x01 || typeId == 0x41));
1167
1168 if (TinselV3)
1169 t3WrtNonZero(pObj, srcPtr, destPtr);
1170 else if (TinselV2)
1171 t2WrtNonZero(pObj, srcPtr, destPtr, (typeId & DMA_CLIP) != 0, (typeId & DMA_FLIPH) != 0);
1172 else if (TinselV1PSX)
1173 PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
1174 else if (TinselV1Mac)
1175 MacDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41);
1176 else if (TinselV1)
1177 WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41);
1178 else if (TinselV0)
1179 t0WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41);
1180 break;
1181 case 0x08: // draw background without clipping
1182 case 0x48: // draw background with clipping
1183 if (TinselV3)
1184 t3WrtAll(pObj, srcPtr, destPtr);
1185 else if (TinselV2 || TinselV1Mac || TinselV0)
1186 WrtAll(pObj, srcPtr, destPtr, typeId == 0x48);
1187 else if (TinselV1PSX)
1188 PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x48, psxFourBitClut, psxSkipBytes, psxMapperTable, false);
1189 else if (TinselV1)
1190 WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x48);
1191 break;
1192 case 0x04: // fill with constant color without clipping
1193 case 0x44: // fill with constant color with clipping
1194 WrtConst(pObj, destPtr, typeId == 0x44);
1195 break;
1196 case 0x81: // TinselV3, draw sprite with transparency
1197 case 0xC1: // TinselV3, draw sprite with transparency & clipping
1198 if (TinselV3)
1199 t3TransWNZ(pObj, srcPtr, destPtr);
1200 else if (TinselV2)
1201 t2WrtNonZero(pObj, srcPtr, destPtr, (typeId & DMA_CLIP) != 0, (typeId & DMA_FLIPH) != 0);
1202 break;
1203 case 0x84: // draw transparent surface without clipping
1204 case 0xC4: // draw transparent surface with clipping
1205 WrtTrans(pObj, destPtr, typeId == 0xC4);
1206 break;
1207 case 0x05: // TinselV3, draw text with color replacement without clipping
1208 case 0x45: // TinselV3, draw text with color replacement with clipping
1209 assert(TinselV3);
1210 t3WrtText(pObj, srcPtr, destPtr);
1211 break;
1212 default:
1213 error("Unknown drawing type %d", typeId);
1214 }
1215 }
1216
1217 // If we were using Discworld PSX, free the memory allocated
1218 // for decompressed block indexes.
1219 if (TinselV1PSX && psxRLEindex)
1220 free(srcPtr);
1221 }
1222
1223 } // End of namespace Tinsel
1224