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