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