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 "sci/resource/resource.h"
24 #include "sci/engine/features.h"
25 #include "sci/engine/seg_manager.h"
26 #include "sci/engine/state.h"
27 #include "sci/graphics/celobj32.h"
28 #include "sci/graphics/frameout.h"
29 #include "sci/graphics/palette32.h"
30 #include "sci/graphics/remap32.h"
31 #include "sci/graphics/text32.h"
32 #include "sci/engine/workarounds.h"
33 #include "sci/util.h"
34 #include "graphics/larryScale.h"
35 #include "common/config-manager.h"
36 #include "common/gui_options.h"
37 
38 namespace Sci {
39 #pragma mark CelScaler
40 
41 CelScaler *CelObj::_scaler = nullptr;
42 
activateScaleTables(const Ratio & scaleX,const Ratio & scaleY)43 void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
44 	for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
45 		if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
46 			_activeIndex = i;
47 			return;
48 		}
49 	}
50 
51 	const int i = 1 - _activeIndex;
52 	_activeIndex = i;
53 	CelScalerTable &table = _scaleTables[i];
54 
55 	if (table.scaleX != scaleX) {
56 		buildLookupTable(table.valuesX, scaleX, kCelScalerTableSize);
57 		table.scaleX = scaleX;
58 	}
59 
60 	if (table.scaleY != scaleY) {
61 		buildLookupTable(table.valuesY, scaleY, kCelScalerTableSize);
62 		table.scaleY = scaleY;
63 	}
64 }
65 
buildLookupTable(int * table,const Ratio & ratio,const int size)66 void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
67 	int value = 0;
68 	int remainder = 0;
69 	const int num = ratio.getNumerator();
70 	for (int i = 0; i < size; ++i) {
71 		*table++ = value;
72 		remainder += ratio.getDenominator();
73 		if (remainder >= num) {
74 			value += remainder / num;
75 			remainder %= num;
76 		}
77 	}
78 }
79 
getScalerTable(const Ratio & scaleX,const Ratio & scaleY)80 const CelScalerTable &CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) {
81 	activateScaleTables(scaleX, scaleY);
82 	return _scaleTables[_activeIndex];
83 }
84 
85 #pragma mark -
86 #pragma mark CelObj
87 bool CelObj::_drawBlackLines = false;
88 
init()89 void CelObj::init() {
90 	CelObj::deinit();
91 	_drawBlackLines = false;
92 	_nextCacheId = 1;
93 	_scaler = new CelScaler();
94 	_cache = new CelCache(100);
95 }
96 
deinit()97 void CelObj::deinit() {
98 	delete _scaler;
99 	_scaler = nullptr;
100 	delete _cache;
101 	_cache = nullptr;
102 }
103 
104 #pragma mark -
105 #pragma mark CelObj - Scalers
106 
107 template<bool FLIP, typename READER>
108 struct SCALER_NoScale {
109 #ifndef NDEBUG
110 	const byte *_rowEdge;
111 #endif
112 	const byte *_row;
113 	READER _reader;
114 	const int16 _lastIndex;
115 	const int16 _sourceX;
116 	const int16 _sourceY;
117 
SCALER_NoScaleSci::SCALER_NoScale118 	SCALER_NoScale(const CelObj &celObj, const int16 maxWidth, const Common::Point &scaledPosition) :
119 	_row(nullptr),
120 	_reader(celObj, FLIP ? celObj._width : maxWidth),
121 	_lastIndex(celObj._width - 1),
122 	_sourceX(scaledPosition.x),
123 	_sourceY(scaledPosition.y) {}
124 
setTargetSci::SCALER_NoScale125 	inline void setTarget(const int16 x, const int16 y) {
126 		_row = _reader.getRow(y - _sourceY);
127 
128 		if (FLIP) {
129 #ifndef NDEBUG
130 			_rowEdge = _row - 1;
131 #endif
132 			_row += _lastIndex - (x - _sourceX);
133 			assert(_row > _rowEdge);
134 		} else {
135 #ifndef NDEBUG
136 			_rowEdge = _row + _lastIndex + 1;
137 #endif
138 			_row += x - _sourceX;
139 			assert(_row < _rowEdge);
140 		}
141 	}
142 
readSci::SCALER_NoScale143 	inline byte read() {
144 		assert(_row != _rowEdge);
145 
146 		if (FLIP) {
147 			return *_row--;
148 		} else {
149 			return *_row++;
150 		}
151 	}
152 };
153 
154 template<bool FLIP, typename READER>
155 struct SCALER_Scale {
156 #ifndef NDEBUG
157 	int16 _minX;
158 	int16 _maxX;
159 #endif
160 	const byte *_row;
161 	READER _reader;
162 	// If _sourceBuffer is set, it contains the full (possibly scaled) source
163 	// image and takes precedence over _reader.
164 	Common::SharedPtr<Buffer> _sourceBuffer;
165 	int16 _x;
166 	static int16 _valuesX[kCelScalerTableSize];
167 	static int16 _valuesY[kCelScalerTableSize];
168 
SCALER_ScaleSci::SCALER_Scale169 	SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
170 	_row(nullptr),
171 #ifndef NDEBUG
172 	_minX(targetRect.left),
173 	_maxX(targetRect.right - 1),
174 #endif
175 	// The maximum width of the scaled object may not be as wide as the source
176 	// data it requires if downscaling, so just always make the reader
177 	// decompress an entire line of source data when scaling
178 	_reader(celObj, celObj._width),
179 	_sourceBuffer() {
180 #ifndef NDEBUG
181 		assert(_minX <= _maxX);
182 #endif
183 
184 		// In order for scaling ratios to apply equally across objects that
185 		// start at different positions on the screen (like the cels of a
186 		// picture), the pixels that are read from the source bitmap must all
187 		// use the same pattern of division. In other words, cels must follow
188 		// a global scaling pattern as if they were always drawn starting at an
189 		// even multiple of the scaling ratio, even if they are not.
190 		//
191 		// To get the correct source pixel when reading out through the scaler,
192 		// the engine creates a lookup table for each axis that translates
193 		// directly from target positions to the indexes of source pixels using
194 		// the global cadence for the given scaling ratio.
195 		//
196 		// Note, however, that not all games use the global scaling mode.
197 		//
198 		// SQ6 definitely uses the global scaling mode (an easy visual
199 		// comparison is to leave Implants N' Stuff and then look at Roger);
200 		// Torin definitely does not (scaling subtitle backgrounds will cause it
201 		// to attempt a read out of bounds and crash). They are both SCI
202 		// "2.1mid" games, so currently the common denominator looks to be that
203 		// games which use global scaling are the ones that use low-resolution
204 		// script coordinates too.
205 
206 		const CelScalerTable &table = CelObj::_scaler->getScalerTable(scaleX, scaleY);
207 
208 		const bool useLarryScale = Common::checkGameGUIOption(GAMEOPTION_LARRYSCALE, ConfMan.get("guioptions")) && ConfMan.getBool("enable_larryscale");
209 		if (useLarryScale) {
210 			// LarryScale is an alternative, high-quality cel scaler implemented
211 			// for ScummVM. Due to the nature of smooth upscaling, it does *not*
212 			// respect the global scaling pattern. Instead, it simply scales the
213 			// cel to the extent of targetRect.
214 
215 			class Copier: public Graphics::RowReader, public Graphics::RowWriter {
216 				READER &_souceReader;
217 				Buffer &_targetBuffer;
218 			public:
219 				Copier(READER& souceReader, Buffer& targetBuffer) :
220 					_souceReader(souceReader),
221 					_targetBuffer(targetBuffer) {}
222 				const Graphics::LarryScaleColor* readRow(int y) override {
223 					return _souceReader.getRow(y);
224 				}
225 				void writeRow(int y, const Graphics::LarryScaleColor* row) override {
226 					memcpy(_targetBuffer.getBasePtr(0, y), row, _targetBuffer.w);
227 				}
228 			};
229 
230 			// Scale the cel using LarryScale and write it to _sourceBuffer
231 			// scaledImageRect is not necessarily identical to targetRect
232 			// because targetRect may be cropped to render only a segment.
233 			Common::Rect scaledImageRect(
234 				scaledPosition.x,
235 				scaledPosition.y,
236 				scaledPosition.x + (celObj._width * scaleX).toInt(),
237 				scaledPosition.y + (celObj._height * scaleY).toInt());
238 			_sourceBuffer = Common::SharedPtr<Buffer>(new Buffer(), Graphics::SurfaceDeleter());
239 			_sourceBuffer->create(
240 				scaledImageRect.width(), scaledImageRect.height(),
241 				Graphics::PixelFormat::createFormatCLUT8());
242 			Copier copier(_reader, *_sourceBuffer);
243 			Graphics::larryScale(
244 				celObj._width, celObj._height, celObj._skipColor, copier,
245 				scaledImageRect.width(), scaledImageRect.height(), copier);
246 
247 			// Set _valuesX and _valuesY to reference the scaled image without additional scaling
248 			for (int16 x = targetRect.left; x < targetRect.right; ++x) {
249 				const int16 unsafeValue = FLIP
250 					? scaledImageRect.right - x - 1
251 					: x - scaledImageRect.left;
252 				_valuesX[x] = CLIP<int16>(unsafeValue, 0, scaledImageRect.width() - 1);
253 			}
254 			for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
255 				const int16 unsafeValue = y - scaledImageRect.top;
256 				_valuesY[y] = CLIP<int16>(unsafeValue, 0, scaledImageRect.height() - 1);
257 			}
258 		} else {
259 			const bool useGlobalScaling = g_sci->_gfxFrameout->getScriptWidth() == kLowResX;
260 			if (useGlobalScaling) {
261 				const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
262 				if (FLIP) {
263 					const int lastIndex = celObj._width - 1;
264 					for (int16 x = targetRect.left; x < targetRect.right; ++x) {
265 						_valuesX[x] = lastIndex - (table.valuesX[x] - unscaledX);
266 					}
267 				} else {
268 					for (int16 x = targetRect.left; x < targetRect.right; ++x) {
269 						_valuesX[x] = table.valuesX[x] - unscaledX;
270 					}
271 				}
272 
273 				const int16 unscaledY = (scaledPosition.y / scaleY).toInt();
274 				for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
275 					_valuesY[y] = table.valuesY[y] - unscaledY;
276 				}
277 			} else {
278 				if (FLIP) {
279 					const int lastIndex = celObj._width - 1;
280 					for (int16 x = targetRect.left; x < targetRect.right; ++x) {
281 						_valuesX[x] = lastIndex - table.valuesX[x - scaledPosition.x];
282 					}
283 				} else {
284 					for (int16 x = targetRect.left; x < targetRect.right; ++x) {
285 						_valuesX[x] = table.valuesX[x - scaledPosition.x];
286 					}
287 				}
288 
289 				for (int16 y = targetRect.top; y < targetRect.bottom; ++y) {
290 					_valuesY[y] = table.valuesY[y - scaledPosition.y];
291 				}
292 			}
293 		}
294 	}
295 
setTargetSci::SCALER_Scale296 	inline void setTarget(const int16 x, const int16 y) {
297 		_row = _sourceBuffer
298 			? static_cast<const byte *>( _sourceBuffer->getBasePtr(0, _valuesY[y]))
299 			: _reader.getRow(_valuesY[y]);
300 		_x = x;
301 		assert(_x >= _minX && _x <= _maxX);
302 	}
303 
readSci::SCALER_Scale304 	inline byte read() {
305 		assert(_x >= _minX && _x <= _maxX);
306 		return _row[_valuesX[_x++]];
307 	}
308 };
309 
310 template<bool FLIP, typename READER>
311 int16 SCALER_Scale<FLIP, READER>::_valuesX[kCelScalerTableSize];
312 template<bool FLIP, typename READER>
313 int16 SCALER_Scale<FLIP, READER>::_valuesY[kCelScalerTableSize];
314 
315 #pragma mark -
316 #pragma mark CelObj - Resource readers
317 
318 struct READER_Uncompressed {
319 private:
320 #ifndef NDEBUG
321 	int16 _sourceHeight;
322 #endif
323 	const byte *_pixels;
324 	const int16 _sourceWidth;
325 
326 public:
READER_UncompressedSci::READER_Uncompressed327 	READER_Uncompressed(const CelObj &celObj, const int16) :
328 #ifndef NDEBUG
329 	_sourceHeight(celObj._height),
330 #endif
331 	_sourceWidth(celObj._width) {
332 		const SciSpan<const byte> resource = celObj.getResPointer();
333 		const uint32 pixelsOffset = resource.getUint32SEAt(celObj._celHeaderOffset + 24);
334 		const int32 numPixels = MIN<int32>(resource.size() - pixelsOffset, celObj._width * celObj._height);
335 
336 		if (numPixels < celObj._width * celObj._height) {
337 			warning("%s is truncated", celObj._info.toString().c_str());
338 #ifndef NDEBUG
339 			_sourceHeight = numPixels / celObj._width;
340 #endif
341 		}
342 
343 		_pixels = resource.getUnsafeDataAt(pixelsOffset, numPixels);
344 	}
345 
getRowSci::READER_Uncompressed346 	inline const byte *getRow(const int16 y) const {
347 		assert(y >= 0 && y < _sourceHeight);
348 		return _pixels + y * _sourceWidth;
349 	}
350 };
351 
352 struct READER_Compressed {
353 private:
354 	const SciSpan<const byte> _resource;
355 	byte _buffer[kCelScalerTableSize];
356 	uint32 _controlOffset;
357 	uint32 _dataOffset;
358 	uint32 _uncompressedDataOffset;
359 	int16 _y;
360 	const int16 _sourceHeight;
361 	const uint8 _skipColor;
362 	const int16 _maxWidth;
363 
364 public:
READER_CompressedSci::READER_Compressed365 	READER_Compressed(const CelObj &celObj, const int16 maxWidth) :
366 	_resource(celObj.getResPointer()),
367 	_y(-1),
368 	_sourceHeight(celObj._height),
369 	_skipColor(celObj._skipColor),
370 	_maxWidth(maxWidth) {
371 		assert(maxWidth <= celObj._width);
372 
373 		const SciSpan<const byte> celHeader = _resource.subspan(celObj._celHeaderOffset);
374 		_dataOffset = celHeader.getUint32SEAt(24);
375 		_uncompressedDataOffset = celHeader.getUint32SEAt(28);
376 		_controlOffset = celHeader.getUint32SEAt(32);
377 	}
378 
getRowSci::READER_Compressed379 	inline const byte *getRow(const int16 y) {
380 		assert(y >= 0 && y < _sourceHeight);
381 		if (y != _y) {
382 			// compressed data segment for row
383 			const uint32 rowOffset = _resource.getUint32SEAt(_controlOffset + y * sizeof(uint32));
384 
385 			uint32 rowCompressedSize;
386 			if (y + 1 < _sourceHeight) {
387 				rowCompressedSize = _resource.getUint32SEAt(_controlOffset + (y + 1) * sizeof(uint32)) - rowOffset;
388 			} else {
389 				rowCompressedSize = _resource.size() - rowOffset - _dataOffset;
390 			}
391 
392 			const byte *row = _resource.getUnsafeDataAt(_dataOffset + rowOffset, rowCompressedSize);
393 
394 			// uncompressed data segment for row
395 			const uint32 literalOffset = _resource.getUint32SEAt(_controlOffset + _sourceHeight * sizeof(uint32) + y * sizeof(uint32));
396 
397 			uint32 literalRowSize;
398 			if (y + 1 < _sourceHeight) {
399 				literalRowSize = _resource.getUint32SEAt(_controlOffset + _sourceHeight * sizeof(uint32) + (y + 1) * sizeof(uint32)) - literalOffset;
400 			} else {
401 				literalRowSize = _resource.size() - literalOffset - _uncompressedDataOffset;
402 			}
403 
404 			const byte *literal = _resource.getUnsafeDataAt(_uncompressedDataOffset + literalOffset, literalRowSize);
405 
406 			uint8 length;
407 			for (int16 i = 0; i < _maxWidth; i += length) {
408 				const byte controlByte = *row++;
409 				length = controlByte;
410 
411 				// Run-length encoded
412 				if (controlByte & 0x80) {
413 					length &= 0x3F;
414 					assert(i + length < (int)sizeof(_buffer));
415 
416 					// Fill with skip color
417 					if (controlByte & 0x40) {
418 						memset(_buffer + i, _skipColor, length);
419 					// Next value is fill color
420 					} else {
421 						memset(_buffer + i, *literal, length);
422 						++literal;
423 					}
424 				// Uncompressed
425 				} else {
426 					assert(i + length < (int)sizeof(_buffer));
427 					memcpy(_buffer + i, literal, length);
428 					literal += length;
429 				}
430 			}
431 			_y = y;
432 		}
433 
434 		return _buffer;
435 	}
436 };
437 
438 #pragma mark -
439 #pragma mark CelObj - Remappers
440 
441 /**
442  * Translation for pixels from Mac pic and view cels to the PC palette.
443  * The Mac OS palette required 0 to be white and 255 to be black, which is the
444  * opposite of the PC palette. Mac cels use the Mac palette but the colors in
445  * scripts are constant between versions. SSCI handles this with many Mac-only
446  * translations throughout the interpreter. We use the PC palette and translate
447  * the cel pixels here, similar to the SCI16 code in GfxView::unpackCel. The
448  * difference is that in SCI32 we decompress while drawing, while in SCI16 cels
449  * are unpacked to a buffer first, making that translation code simpler.
450  */
translateMacColor(bool isMacSource,byte color)451 inline byte translateMacColor(bool isMacSource, byte color) {
452 	if (isMacSource) {
453 		if (color == 0) {
454 			return 255;
455 		} else if (color == 255) {
456 			return 0;
457 		}
458 	}
459 	return color;
460 }
461 
462 /**
463  * Pixel mapper for a CelObj with transparent pixels and no
464  * remapping data.
465  */
466 struct MAPPER_NoMD {
drawSci::MAPPER_NoMD467 	inline void draw(byte *target, const byte pixel, const uint8 skipColor, const bool isMacSource) const {
468 		if (pixel != skipColor) {
469 			*target = translateMacColor(isMacSource, pixel);
470 		}
471 	}
472 };
473 
474 /**
475  * Pixel mapper for a CelObj with no transparent pixels and
476  * no remapping data.
477  */
478 struct MAPPER_NoMDNoSkip {
drawSci::MAPPER_NoMDNoSkip479 	inline void draw(byte *target, const byte pixel, const uint8, const bool isMacSource) const {
480 		*target = translateMacColor(isMacSource, pixel);
481 	}
482 };
483 
484 /**
485  * Pixel mapper for a CelObj with transparent pixels,
486  * remapping data, and remapping enabled.
487  */
488 struct MAPPER_Map {
drawSci::MAPPER_Map489 	inline void draw(byte *target, const byte pixel, const uint8 skipColor, const bool isMacSource) const {
490 		if (pixel != skipColor) {
491 			// For some reason, SSCI never checks if the source pixel is *above*
492 			// the range of remaps, so we do not either.
493 			if (pixel < g_sci->_gfxRemap32->getStartColor()) {
494 				*target = translateMacColor(isMacSource, pixel);
495 			} else if (g_sci->_gfxRemap32->remapEnabled(pixel)) {
496 				*target = g_sci->_gfxRemap32->remapColor(translateMacColor(isMacSource, pixel), *target);
497 			}
498 		}
499 	}
500 };
501 
502 /**
503  * Pixel mapper for a CelObj with transparent pixels,
504  * remapping data, and remapping disabled.
505  */
506 struct MAPPER_NoMap {
drawSci::MAPPER_NoMap507 	inline void draw(byte *target, const byte pixel, const uint8 skipColor, const bool isMacSource) const {
508 		// For some reason, SSCI never checks if the source pixel is *above* the
509 		// range of remaps, so we do not either.
510 		if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) {
511 			*target = translateMacColor(isMacSource, pixel);
512 		}
513 	}
514 };
515 
draw(Buffer & target,const ScreenItem & screenItem,const Common::Rect & targetRect) const516 void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
517 	const Common::Point &scaledPosition = screenItem._scaledPosition;
518 	const Ratio &scaleX = screenItem._ratioX;
519 	const Ratio &scaleY = screenItem._ratioY;
520 	_drawBlackLines = screenItem._drawBlackLines;
521 
522 	if (_remap) {
523 		// In SSCI, this check was `g_Remap_numActiveRemaps && _remap`, but
524 		// since we are already in a `_remap` branch, there is no reason to
525 		// check that again
526 		if (g_sci->_gfxRemap32->getRemapCount()) {
527 			if (scaleX.isOne() && scaleY.isOne()) {
528 				if (_compressionType == kCelCompressionNone) {
529 					if (_drawMirrored) {
530 						drawUncompHzFlipMap(target, targetRect, scaledPosition);
531 					} else {
532 						drawUncompNoFlipMap(target, targetRect, scaledPosition);
533 					}
534 				} else {
535 					if (_drawMirrored) {
536 						drawHzFlipMap(target, targetRect, scaledPosition);
537 					} else {
538 						drawNoFlipMap(target, targetRect, scaledPosition);
539 					}
540 				}
541 			} else {
542 				if (_compressionType == kCelCompressionNone) {
543 					scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
544 				} else {
545 					scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
546 				}
547 			}
548 		} else {
549 			if (scaleX.isOne() && scaleY.isOne()) {
550 				if (_compressionType == kCelCompressionNone) {
551 					if (_drawMirrored) {
552 						drawUncompHzFlip(target, targetRect, scaledPosition);
553 					} else {
554 						drawUncompNoFlip(target, targetRect, scaledPosition);
555 					}
556 				} else {
557 					if (_drawMirrored) {
558 						drawHzFlip(target, targetRect, scaledPosition);
559 					} else {
560 						drawNoFlip(target, targetRect, scaledPosition);
561 					}
562 				}
563 			} else {
564 				if (_compressionType == kCelCompressionNone) {
565 					scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
566 				} else {
567 					scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
568 				}
569 			}
570 		}
571 	} else {
572 		if (scaleX.isOne() && scaleY.isOne()) {
573 			if (_compressionType == kCelCompressionNone) {
574 				if (_transparent) {
575 					if (_drawMirrored) {
576 						drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
577 					} else {
578 						drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
579 					}
580 				} else {
581 					if (_drawMirrored) {
582 						drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
583 					} else {
584 						drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
585 					}
586 				}
587 			} else {
588 				if (_drawMirrored) {
589 					drawHzFlipNoMD(target, targetRect, scaledPosition);
590 				} else {
591 					drawNoFlipNoMD(target, targetRect, scaledPosition);
592 				}
593 			}
594 		} else {
595 			if (_compressionType == kCelCompressionNone) {
596 				scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
597 			} else {
598 				scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
599 			}
600 		}
601 	}
602 
603 	_drawBlackLines = false;
604 }
605 
draw(Buffer & target,const ScreenItem & screenItem,const Common::Rect & targetRect,bool mirrorX)606 void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
607 	_drawMirrored = mirrorX;
608 	draw(target, screenItem, targetRect);
609 }
610 
draw(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition,const bool mirrorX)611 void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
612 	_drawMirrored = mirrorX;
613 	Ratio square;
614 	drawTo(target, targetRect, scaledPosition, square, square);
615 }
616 
drawTo(Buffer & target,Common::Rect const & targetRect,Common::Point const & scaledPosition,Ratio const & scaleX,Ratio const & scaleY) const617 void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const {
618 	if (_remap) {
619 		if (scaleX.isOne() && scaleY.isOne()) {
620 			if (_compressionType == kCelCompressionNone) {
621 				if (_drawMirrored) {
622 					drawUncompHzFlipMap(target, targetRect, scaledPosition);
623 				} else {
624 					drawUncompNoFlipMap(target, targetRect, scaledPosition);
625 				}
626 			} else {
627 				if (_drawMirrored) {
628 					drawHzFlipMap(target, targetRect, scaledPosition);
629 				} else {
630 					drawNoFlipMap(target, targetRect, scaledPosition);
631 				}
632 			}
633 		} else {
634 			if (_compressionType == kCelCompressionNone) {
635 				scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
636 			} else {
637 				scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
638 			}
639 		}
640 	} else {
641 		if (scaleX.isOne() && scaleY.isOne()) {
642 			if (_compressionType == kCelCompressionNone) {
643 				if (_drawMirrored) {
644 					drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
645 				} else {
646 					drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
647 				}
648 			} else {
649 				if (_drawMirrored) {
650 					drawHzFlipNoMD(target, targetRect, scaledPosition);
651 				} else {
652 					drawNoFlipNoMD(target, targetRect, scaledPosition);
653 				}
654 			}
655 		} else {
656 			if (_compressionType == kCelCompressionNone) {
657 				scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
658 			} else {
659 				scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
660 			}
661 		}
662 	}
663 }
664 
readPixel(uint16 x,const uint16 y,bool mirrorX) const665 uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
666 	if (mirrorX) {
667 		x = _width - x - 1;
668 	}
669 
670 	if (_compressionType == kCelCompressionNone) {
671 		READER_Uncompressed reader(*this, x + 1);
672 		return reader.getRow(y)[x];
673 	} else {
674 		READER_Compressed reader(*this, x + 1);
675 		return reader.getRow(y)[x];
676 	}
677 }
678 
submitPalette() const679 void CelObj::submitPalette() const {
680 	if (_hunkPaletteOffset) {
681 		const SciSpan<const byte> data = getResPointer();
682 		const HunkPalette palette(data.subspan(_hunkPaletteOffset));
683 		g_sci->_gfxPalette32->submit(palette);
684 	}
685 }
686 
687 #pragma mark -
688 #pragma mark CelObj - Caching
689 
690 int CelObj::_nextCacheId = 1;
691 CelCache *CelObj::_cache = nullptr;
692 
searchCache(const CelInfo32 & celInfo,int * const nextInsertIndex) const693 int CelObj::searchCache(const CelInfo32 &celInfo, int *const nextInsertIndex) const {
694 	*nextInsertIndex = -1;
695 	int oldestId = _nextCacheId + 1;
696 	int oldestIndex = 0;
697 
698 	for (int i = 0, len = _cache->size(); i < len; ++i) {
699 		CelCacheEntry &entry = (*_cache)[i];
700 
701 		if (entry.celObj == nullptr) {
702 			if (*nextInsertIndex == -1) {
703 				*nextInsertIndex = i;
704 			}
705 		} else if (entry.celObj->_info == celInfo) {
706 			entry.id = ++_nextCacheId;
707 			return i;
708 		} else if (oldestId > entry.id) {
709 			oldestId = entry.id;
710 			oldestIndex = i;
711 		}
712 	}
713 
714 	if (*nextInsertIndex == -1) {
715 		*nextInsertIndex = oldestIndex;
716 	}
717 
718 	return -1;
719 }
720 
putCopyInCache(const int cacheIndex) const721 void CelObj::putCopyInCache(const int cacheIndex) const {
722 	if (cacheIndex == -1) {
723 		error("Invalid cache index");
724 	}
725 
726 	CelCacheEntry &entry = (*_cache)[cacheIndex];
727 	entry.celObj.reset(duplicate());
728 	entry.id = ++_nextCacheId;
729 }
730 
731 #pragma mark -
732 #pragma mark CelObj - Drawing
733 
734 template<typename MAPPER, typename SCALER, bool DRAW_BLACK_LINES>
735 struct RENDERER {
736 	MAPPER &_mapper;
737 	SCALER &_scaler;
738 	const uint8 _skipColor;
739 	const bool _isMacSource;
740 
RENDERERSci::RENDERER741 	RENDERER(MAPPER &mapper, SCALER &scaler, const uint8 skipColor, const bool isMacSource) :
742 	_mapper(mapper),
743 	_scaler(scaler),
744 	_skipColor(skipColor),
745 	_isMacSource(isMacSource) {}
746 
drawSci::RENDERER747 	inline void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
748 		byte *targetPixel = (byte *)target.getPixels() + target.w * targetRect.top + targetRect.left;
749 
750 		const int16 skipStride = target.w - targetRect.width();
751 		const int16 targetWidth = targetRect.width();
752 		const int16 targetHeight = targetRect.height();
753 		for (int16 y = 0; y < targetHeight; ++y) {
754 			if (DRAW_BLACK_LINES && (y % 2) == 0) {
755 				memset(targetPixel, 0, targetWidth);
756 				targetPixel += targetWidth + skipStride;
757 				continue;
758 			}
759 
760 			_scaler.setTarget(targetRect.left, targetRect.top + y);
761 
762 			for (int16 x = 0; x < targetWidth; ++x) {
763 				_mapper.draw(targetPixel++, _scaler.read(), _skipColor, _isMacSource);
764 			}
765 
766 			targetPixel += skipStride;
767 		}
768 	}
769 };
770 
771 template<typename MAPPER, typename SCALER>
render(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const772 void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
773 
774 	MAPPER mapper;
775 	SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition);
776 	RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _skipColor, _isMacSource);
777 	renderer.draw(target, targetRect, scaledPosition);
778 }
779 
780 template<typename MAPPER, typename SCALER>
render(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition,const Ratio & scaleX,const Ratio & scaleY) const781 void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const {
782 
783 	MAPPER mapper;
784 	SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY);
785 	if (_drawBlackLines) {
786 		RENDERER<MAPPER, SCALER, true> renderer(mapper, scaler, _skipColor, _isMacSource);
787 		renderer.draw(target, targetRect, scaledPosition);
788 	} else {
789 		RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _skipColor, _isMacSource);
790 		renderer.draw(target, targetRect, scaledPosition);
791 	}
792 }
793 
drawHzFlip(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const794 void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
795 	render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
796 }
797 
drawNoFlip(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const798 void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
799 	render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
800 }
801 
drawUncompNoFlip(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const802 void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
803 	render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
804 }
805 
drawUncompHzFlip(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const806 void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
807 	render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
808 }
809 
scaleDraw(Buffer & target,const Ratio & scaleX,const Ratio & scaleY,const Common::Rect & targetRect,const Common::Point & scaledPosition) const810 void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
811 	if (_drawMirrored) {
812 		render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
813 	} else {
814 		render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
815 	}
816 }
817 
scaleDrawUncomp(Buffer & target,const Ratio & scaleX,const Ratio & scaleY,const Common::Rect & targetRect,const Common::Point & scaledPosition) const818 void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
819 	if (_drawMirrored) {
820 		render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
821 	} else {
822 		render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
823 	}
824 }
825 
drawHzFlipMap(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const826 void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
827 	render<MAPPER_Map, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
828 }
829 
drawNoFlipMap(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const830 void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
831 	render<MAPPER_Map, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
832 }
833 
drawUncompNoFlipMap(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const834 void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
835 	render<MAPPER_Map, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
836 }
837 
drawUncompHzFlipMap(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const838 void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
839 	render<MAPPER_Map, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
840 }
841 
scaleDrawMap(Buffer & target,const Ratio & scaleX,const Ratio & scaleY,const Common::Rect & targetRect,const Common::Point & scaledPosition) const842 void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
843 	if (_drawMirrored) {
844 		render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
845 	} else {
846 		render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
847 	}
848 }
849 
scaleDrawUncompMap(Buffer & target,const Ratio & scaleX,const Ratio & scaleY,const Common::Rect & targetRect,const Common::Point & scaledPosition) const850 void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
851 	if (_drawMirrored) {
852 		render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
853 	} else {
854 		render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
855 	}
856 }
857 
drawNoFlipNoMD(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const858 void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
859 	render<MAPPER_NoMD, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);
860 }
861 
drawHzFlipNoMD(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const862 void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
863 	render<MAPPER_NoMD, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);
864 }
865 
drawUncompNoFlipNoMD(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const866 void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
867 	render<MAPPER_NoMD, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
868 }
869 
drawUncompNoFlipNoMDNoSkip(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const870 void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
871 	render<MAPPER_NoMDNoSkip, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);
872 }
873 
drawUncompHzFlipNoMD(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const874 void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
875 	render<MAPPER_NoMD, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
876 }
877 
drawUncompHzFlipNoMDNoSkip(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition) const878 void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
879 	render<MAPPER_NoMDNoSkip, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);
880 }
881 
scaleDrawNoMD(Buffer & target,const Ratio & scaleX,const Ratio & scaleY,const Common::Rect & targetRect,const Common::Point & scaledPosition) const882 void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
883 	// In SSCI the checks are > because their rects are BR-inclusive; our checks
884 	// are >= because our rects are BR-exclusive
885 	if (targetRect.left >= targetRect.right ||
886 		 targetRect.top >= targetRect.bottom) {
887 		return;
888 	}
889 
890 	if (_drawMirrored)
891 		render<MAPPER_NoMD, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
892 	else
893 		render<MAPPER_NoMD, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
894 }
895 
scaleDrawUncompNoMD(Buffer & target,const Ratio & scaleX,const Ratio & scaleY,const Common::Rect & targetRect,const Common::Point & scaledPosition) const896 void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
897 	// In SSCI the checks are > because their rects are BR-inclusive; our checks
898 	// are >= because our rects are BR-exclusive
899 	if (targetRect.left >= targetRect.right ||
900 		 targetRect.top >= targetRect.bottom) {
901 		return;
902 	}
903 
904 	if (_drawMirrored) {
905 		render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
906 	} else {
907 		render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY);
908 	}
909 }
910 
911 #pragma mark -
912 #pragma mark CelObjView
913 
getNumLoops(const GuiResourceId viewId)914 int16 CelObjView::getNumLoops(const GuiResourceId viewId) {
915 	const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
916 
917 	if (!resource) {
918 		return 0;
919 	}
920 
921 	return resource->getUint8At(2);
922 }
923 
getNumCels(const GuiResourceId viewId,int16 loopNo)924 int16 CelObjView::getNumCels(const GuiResourceId viewId, int16 loopNo) {
925 	const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
926 
927 	if (!resource) {
928 		return 0;
929 	}
930 
931 	const SciSpan<const byte> &data = *resource;
932 
933 	const uint16 loopCount = data[2];
934 
935 	// Every version of SSCI has a logic error in this function that causes
936 	// random memory to be read if a script requests the cel count for one past
937 	// the maximum loop index. For example, GK1 room 808 does this, and gets
938 	// stuck in an infinite loop because the game script expects this method to
939 	// return a non-zero value.
940 	// This bug is triggered in basically every SCI32 game and appears to be
941 	// universally fixable simply by always using the next lowest loop instead.
942 	if (loopNo == loopCount) {
943 		const SciCallOrigin origin = g_sci->getEngineState()->getCurrentCallOrigin();
944 		debugC(kDebugLevelWorkarounds, "Workaround: kNumCels loop %d -> loop %d in view %u, %s", loopNo, loopNo - 1, viewId, origin.toString().c_str());
945 		--loopNo;
946 	}
947 
948 	if (loopNo > loopCount || loopNo < 0) {
949 		return 0;
950 	}
951 
952 	const uint16 viewHeaderSize = data.getUint16SEAt(0);
953 	const uint8 loopHeaderSize = data[12];
954 	const uint8 viewHeaderFieldSize = 2;
955 
956 	SciSpan<const byte> loopHeader = data.subspan(viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopNo));
957 
958 	if (loopHeader.getInt8At(0) != -1) {
959 		loopHeader = data.subspan(viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopHeader.getInt8At(0)));
960 	}
961 
962 	return loopHeader[2];
963 }
964 
CelObjView(const GuiResourceId viewId,const int16 loopNo,const int16 celNo)965 CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
966 	_info.type = kCelTypeView;
967 	_info.resourceId = viewId;
968 	_info.loopNo = loopNo;
969 	_info.celNo = celNo;
970 	_mirrorX = false;
971 	_isMacSource = (g_sci->getPlatform() == Common::kPlatformMacintosh);
972 	_compressionType = kCelCompressionInvalid;
973 	_transparent = true;
974 
975 	int cacheInsertIndex;
976 	const int cacheIndex = searchCache(_info, &cacheInsertIndex);
977 	if (cacheIndex != -1) {
978 		CelCacheEntry &entry = (*_cache)[cacheIndex];
979 		const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj.get());
980 		if (cachedCelObj == nullptr) {
981 			error("Expected a CelObjView in cache slot %d", cacheIndex);
982 		}
983 		*this = *cachedCelObj;
984 		entry.id = ++_nextCacheId;
985 		return;
986 	}
987 
988 	const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
989 
990 	// SSCI just silently returns here
991 	if (!resource) {
992 		error("View resource %d not found", viewId);
993 	}
994 
995 	const Resource &data = *resource;
996 
997 	_xResolution = data.getUint16SEAt(14);
998 	_yResolution = data.getUint16SEAt(16);
999 
1000 	if (_xResolution == 0 && _yResolution == 0) {
1001 		byte sizeFlag = data[5];
1002 		if (sizeFlag == 0) {
1003 			_xResolution = kLowResX;
1004 			_yResolution = kLowResY;
1005 		} else if (sizeFlag == 1) {
1006 			_xResolution = 640;
1007 			_yResolution = 480;
1008 		} else if (sizeFlag == 2) {
1009 			_xResolution = 640;
1010 			_yResolution = 400;
1011 		}
1012 	}
1013 
1014 	const uint16 loopCount = data[2];
1015 	if (_info.loopNo >= loopCount) {
1016 		_info.loopNo = loopCount - 1;
1017 	}
1018 
1019 	if (loopNo < 0) {
1020 		error("Loop is less than 0");
1021 	}
1022 
1023 	const uint16 viewHeaderSize = data.getUint16SEAt(0);
1024 	const uint8 loopHeaderSize = data[12];
1025 	const uint8 viewHeaderFieldSize = 2;
1026 
1027 	SciSpan<const byte> loopHeader = data.subspan(viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo));
1028 
1029 	if (loopHeader.getInt8At(0) != -1) {
1030 		if (loopHeader[1] == 1) {
1031 			_mirrorX = true;
1032 		}
1033 
1034 		loopHeader = data.subspan(viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopHeader.getInt8At(0)));
1035 	}
1036 
1037 	uint8 celCount = loopHeader[2];
1038 	if (_info.celNo >= celCount) {
1039 		_info.celNo = celCount - 1;
1040 	}
1041 
1042 	// A celNo can be negative and still valid. At least PQ4CD uses this strange
1043 	// arrangement to load its high-resolution main menu resource. In PQ4CD, the
1044 	// low-resolution menu is at view 23, loop 9, cel 0, and the high-resolution
1045 	// menu is at view 2300, loop 0, cel 0. View 2300 is specially crafted to
1046 	// have 2 loops, with the second loop having 0 cels. When in high-resolution
1047 	// mode, the game scripts only change the view resource ID from 23 to 2300,
1048 	// leaving loop 9 and cel 0 the same. The code in CelObjView constructor
1049 	// auto-corrects loop 9 to loop 1, and then auto-corrects the cel number
1050 	// from 0 to -1, which effectively causes loop 0, cel 0 to be read.
1051 	if (_info.celNo < 0 && _info.loopNo == 0) {
1052 		error("Cel is less than 0 on loop 0");
1053 	}
1054 
1055 	_hunkPaletteOffset = data.getUint32SEAt(8);
1056 	_celHeaderOffset = loopHeader.getUint32SEAt(12) + (data[13] * _info.celNo);
1057 
1058 	const SciSpan<const byte> celHeader = data.subspan(_celHeaderOffset);
1059 
1060 	_width = celHeader.getUint16SEAt(0);
1061 	_height = celHeader.getUint16SEAt(2);
1062 	assert(_width <= kCelScalerTableSize && _height <= kCelScalerTableSize);
1063 	_origin.x = _width / 2 - celHeader.getInt16SEAt(4);
1064 	_origin.y = _height - celHeader.getInt16SEAt(6) - 1;
1065 	_skipColor = celHeader[8];
1066 	_compressionType = (CelCompressionType)celHeader[9];
1067 
1068 	if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
1069 		error("Compression type not supported - V: %d  L: %d  C: %d", _info.resourceId, _info.loopNo, _info.celNo);
1070 	}
1071 
1072 	const uint16 flags = celHeader.getUint16SEAt(10);
1073 	if (flags & 0x80) {
1074 		_transparent = flags & 1 ? true : false;
1075 		_remap = flags & 2 ? true : false;
1076 	} else if (_compressionType == kCelCompressionNone) {
1077 		_remap = analyzeUncompressedForRemap();
1078 	} else {
1079 		_remap = analyzeForRemap();
1080 	}
1081 
1082 	putCopyInCache(cacheInsertIndex);
1083 }
1084 
analyzeUncompressedForRemap() const1085 bool CelObjView::analyzeUncompressedForRemap() const {
1086 	const SciSpan<const byte> data = getResPointer();
1087 	const uint32 pixelsOffset = data.getUint32SEAt(_celHeaderOffset + 24);
1088 	const byte *pixels = data.getUnsafeDataAt(pixelsOffset, _width * _height);
1089 	for (int i = 0; i < _width * _height; ++i) {
1090 		const byte pixel = pixels[i];
1091 		if (
1092 			pixel >= g_sci->_gfxRemap32->getStartColor() &&
1093 			pixel <= g_sci->_gfxRemap32->getEndColor() &&
1094 			pixel != _skipColor
1095 		) {
1096 			return true;
1097 		}
1098 	}
1099 	return false;
1100 }
1101 
analyzeForRemap() const1102 bool CelObjView::analyzeForRemap() const {
1103 	READER_Compressed reader(*this, _width);
1104 	for (int y = 0; y < _height; y++) {
1105 		const byte *const curRow = reader.getRow(y);
1106 		for (int x = 0; x < _width; x++) {
1107 			const byte pixel = curRow[x];
1108 			if (
1109 				pixel >= g_sci->_gfxRemap32->getStartColor() &&
1110 				pixel <= g_sci->_gfxRemap32->getEndColor() &&
1111 				pixel != _skipColor
1112 			) {
1113 				return true;
1114 			}
1115 		}
1116 	}
1117 	return false;
1118 }
1119 
draw(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition,bool mirrorX,const Ratio & scaleX,const Ratio & scaleY)1120 void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) {
1121 	_drawMirrored = mirrorX;
1122 	drawTo(target, targetRect, scaledPosition, scaleX, scaleY);
1123 }
1124 
duplicate() const1125 CelObjView *CelObjView::duplicate() const {
1126 	return new CelObjView(*this);
1127 }
1128 
getResPointer() const1129 const SciSpan<const byte> CelObjView::getResPointer() const {
1130 	Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false);
1131 	if (resource == nullptr) {
1132 		error("Failed to load view %d from resource manager", _info.resourceId);
1133 	}
1134 	return *resource;
1135 }
1136 
getLinkPosition(const int16 linkId) const1137 Common::Point CelObjView::getLinkPosition(const int16 linkId) const {
1138 	const SciSpan<const byte> resource = getResPointer();
1139 
1140 	if (resource[18] < 0x84) {
1141 		error("%s unsupported version %u for Links", _info.toString().c_str(), resource[18]);
1142 	}
1143 
1144 	const SciSpan<const byte> celHeader = resource.subspan(_celHeaderOffset);
1145 	const int16 numLinks = celHeader.getInt16SEAt(40);
1146 
1147 	if (numLinks) {
1148 		const int recordSize = 6;
1149 		SciSpan<const byte> linkTable = resource.subspan(celHeader.getInt32SEAt(36), recordSize * numLinks);
1150 		for (int16 i = 0; i < numLinks; ++i) {
1151 			if (linkTable[4] == linkId) {
1152 				Common::Point point;
1153 				point.x = linkTable.getInt16SEAt(0);
1154 				if (_mirrorX) {
1155 					// SSCI had an off-by-one error here (missing -1)
1156 					point.x = _width - point.x - 1;
1157 				}
1158 				point.y = linkTable.getInt16SEAt(2);
1159 				return point;
1160 			}
1161 
1162 			linkTable += recordSize;
1163 		}
1164 	}
1165 
1166 	return Common::Point(-1, -1);
1167 }
1168 
1169 #pragma mark -
1170 #pragma mark CelObjPic
1171 
CelObjPic(const GuiResourceId picId,const int16 celNo)1172 CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
1173 	_info.type = kCelTypePic;
1174 	_info.resourceId = picId;
1175 	_info.loopNo = 0;
1176 	_info.celNo = celNo;
1177 	_mirrorX = false;
1178 	_isMacSource = (g_sci->getPlatform() == Common::kPlatformMacintosh);
1179 	_compressionType = kCelCompressionInvalid;
1180 	_transparent = true;
1181 	_remap = false;
1182 
1183 	int cacheInsertIndex;
1184 	const int cacheIndex = searchCache(_info, &cacheInsertIndex);
1185 	if (cacheIndex != -1) {
1186 		CelCacheEntry &entry = (*_cache)[cacheIndex];
1187 		const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj.get());
1188 		if (cachedCelObj == nullptr) {
1189 			error("Expected a CelObjPic in cache slot %d", cacheIndex);
1190 		}
1191 		*this = *cachedCelObj;
1192 		entry.id = ++_nextCacheId;
1193 		return;
1194 	}
1195 
1196 	const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
1197 
1198 	// SSCI just silently returns here
1199 	if (!resource) {
1200 		error("Pic resource %d not found", picId);
1201 	}
1202 
1203 	const Resource &data = *resource;
1204 
1205 	_celCount = data.getUint8At(2);
1206 
1207 	if (_info.celNo >= _celCount) {
1208 		error("Cel number %d greater than cel count %d", _info.celNo, _celCount);
1209 	}
1210 
1211 	_celHeaderOffset = data.getUint16SEAt(0) + (data.getUint16SEAt(4) * _info.celNo);
1212 	_hunkPaletteOffset = data.getUint32SEAt(6);
1213 
1214 	const SciSpan<const byte> celHeader = data.subspan(_celHeaderOffset);
1215 
1216 	_width = celHeader.getUint16SEAt(0);
1217 	_height = celHeader.getUint16SEAt(2);
1218 	_origin.x = celHeader.getInt16SEAt(4);
1219 	_origin.y = celHeader.getInt16SEAt(6);
1220 	_skipColor = celHeader[8];
1221 	_compressionType = (CelCompressionType)celHeader[9];
1222 	_priority = celHeader.getInt16SEAt(36);
1223 	_relativePosition.x = celHeader.getInt16SEAt(38);
1224 	_relativePosition.y = celHeader.getInt16SEAt(40);
1225 
1226 	const uint16 sizeFlag1 = data.getUint16SEAt(10);
1227 	const uint16 sizeFlag2 = data.getUint16SEAt(12);
1228 
1229 	if (sizeFlag2) {
1230 		_xResolution = sizeFlag1;
1231 		_yResolution = sizeFlag2;
1232 	} else if (sizeFlag1 == 0) {
1233 		_xResolution = kLowResX;
1234 		_yResolution = kLowResY;
1235 	} else if (sizeFlag1 == 1) {
1236 		_xResolution = 640;
1237 		_yResolution = 480;
1238 	} else if (sizeFlag1 == 2) {
1239 		_xResolution = 640;
1240 		_yResolution = 400;
1241 	}
1242 
1243 
1244 	const uint16 flags = celHeader.getUint16SEAt(10);
1245 	if (flags & 0x80) {
1246 		_transparent = flags & 1 ? true : false;
1247 		_remap = flags & 2 ? true : false;
1248 	} else {
1249 		_transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip();
1250 
1251 		if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
1252 			error("Compression type not supported - P: %d  C: %d", picId, celNo);
1253 		}
1254 	}
1255 
1256 	putCopyInCache(cacheInsertIndex);
1257 }
1258 
analyzeUncompressedForSkip() const1259 bool CelObjPic::analyzeUncompressedForSkip() const {
1260 	const SciSpan<const byte> resource = getResPointer();
1261 	const uint32 pixelsOffset = resource.getUint32SEAt(_celHeaderOffset + 24);
1262 	const int32 numPixels = MIN<int32>(resource.size() - pixelsOffset, _width * _height);
1263 
1264 	if (numPixels < _width * _height) {
1265 		warning("%s is truncated", _info.toString().c_str());
1266 	}
1267 
1268 	const byte *const pixels = resource.getUnsafeDataAt(pixelsOffset, numPixels);
1269 	for (int32 i = 0; i < numPixels; ++i) {
1270 		uint8 pixel = pixels[i];
1271 		if (pixel == _skipColor) {
1272 			return true;
1273 		}
1274 	}
1275 
1276 	return false;
1277 }
1278 
draw(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition,const bool mirrorX)1279 void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
1280 	const Ratio square;
1281 	_drawMirrored = mirrorX;
1282 	drawTo(target, targetRect, scaledPosition, square, square);
1283 }
1284 
duplicate() const1285 CelObjPic *CelObjPic::duplicate() const {
1286 	return new CelObjPic(*this);
1287 }
1288 
getResPointer() const1289 const SciSpan<const byte> CelObjPic::getResPointer() const {
1290 	const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false);
1291 	if (resource == nullptr) {
1292 		error("Failed to load pic %d from resource manager", _info.resourceId);
1293 	}
1294 	return *resource;
1295 }
1296 
1297 #pragma mark -
1298 #pragma mark CelObjMem
1299 
CelObjMem(const reg_t bitmapObject)1300 CelObjMem::CelObjMem(const reg_t bitmapObject) {
1301 	_info.type = kCelTypeMem;
1302 	_info.bitmap = bitmapObject;
1303 	_mirrorX = false;
1304 	_isMacSource = false;
1305 	_compressionType = kCelCompressionNone;
1306 	_celHeaderOffset = 0;
1307 	_transparent = true;
1308 
1309 	SciBitmap *bitmap = g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject);
1310 
1311 	// SSCI did no error checking here at all so would just end up reading
1312 	// garbage or crashing if this ever happened
1313 	if (!bitmap) {
1314 		error("Bitmap %04x:%04x not found", PRINT_REG(bitmapObject));
1315 	}
1316 
1317 	_width = bitmap->getWidth();
1318 	_height = bitmap->getHeight();
1319 	_origin = bitmap->getOrigin();
1320 	_skipColor = bitmap->getSkipColor();
1321 	_xResolution = bitmap->getXResolution();
1322 	_yResolution = bitmap->getYResolution();
1323 	_hunkPaletteOffset = bitmap->getHunkPaletteOffset();
1324 	_remap = bitmap->getRemap();
1325 }
1326 
duplicate() const1327 CelObjMem *CelObjMem::duplicate() const {
1328 	return new CelObjMem(*this);
1329 }
1330 
getResPointer() const1331 const SciSpan<const byte> CelObjMem::getResPointer() const {
1332 	SciBitmap &bitmap = *g_sci->getEngineState()->_segMan->lookupBitmap(_info.bitmap);
1333 	return SciSpan<const byte>(bitmap.getRawData(), bitmap.getRawSize(), Common::String::format("bitmap %04x:%04x", PRINT_REG(_info.bitmap)));
1334 }
1335 
1336 #pragma mark -
1337 #pragma mark CelObjColor
1338 
CelObjColor(const uint8 color,const int16 width,const int16 height)1339 CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
1340 	_info.type = kCelTypeColor;
1341 	_info.color = color;
1342 	_origin.x = 0;
1343 	_origin.y = 0;
1344 	_xResolution = g_sci->_gfxFrameout->getScriptWidth();
1345 	_yResolution = g_sci->_gfxFrameout->getScriptHeight();
1346 	_hunkPaletteOffset = 0;
1347 	_mirrorX = false;
1348 	_isMacSource = (g_sci->getPlatform() == Common::kPlatformMacintosh);
1349 	_remap = false;
1350 	_width = width;
1351 	_height = height;
1352 }
1353 
draw(Buffer & target,const ScreenItem & screenItem,const Common::Rect & targetRect,const bool mirrorX)1354 void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) {
1355 	// One cannot draw a solid color mirrored, but SSCI sets it anyway, so we do
1356 	// too
1357 	_drawMirrored = mirrorX;
1358 	draw(target, targetRect);
1359 }
draw(Buffer & target,const Common::Rect & targetRect,const Common::Point & scaledPosition,bool mirrorX)1360 void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) {
1361 	error("Unsupported method");
1362 }
draw(Buffer & target,const Common::Rect & targetRect) const1363 void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const {
1364 	target.fillRect(targetRect, translateMacColor(_isMacSource, _info.color));
1365 }
1366 
duplicate() const1367 CelObjColor *CelObjColor::duplicate() const {
1368 	return new CelObjColor(*this);
1369 }
1370 
getResPointer() const1371 const SciSpan<const byte> CelObjColor::getResPointer() const {
1372 	error("Unsupported method");
1373 }
1374 } // End of namespace Sci
1375