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 "common/scummsys.h"
24 #include "common/archive.h"
25 #include "common/memstream.h"
26 #include "common/textconsole.h"
27 #include "xeen/xeen.h"
28 #include "xeen/screen.h"
29 #include "xeen/sprites.h"
30
31 #include "graphics/palette.h"
32
33 namespace Xeen {
34
35 #define SCENE_CLIP_LEFT 8
36 #define SCENE_CLIP_RIGHT 223
37
38 int SpriteResource::_clippedBottom;
39
SpriteResource()40 SpriteResource::SpriteResource() {
41 _filesize = 0;
42 _data = nullptr;
43 _scaledWidth = _scaledHeight = 0;
44 }
45
SpriteResource(const Common::String & filename)46 SpriteResource::SpriteResource(const Common::String &filename) {
47 _data = nullptr;
48 _scaledWidth = _scaledHeight = 0;
49 load(filename);
50 }
51
SpriteResource(const Common::String & filename,int ccMode)52 SpriteResource::SpriteResource(const Common::String &filename, int ccMode) {
53 _data = nullptr;
54 _scaledWidth = _scaledHeight = 0;
55 load(filename, ccMode);
56 }
57
~SpriteResource()58 SpriteResource::~SpriteResource() {
59 clear();
60 }
61
operator =(const SpriteResource & src)62 SpriteResource &SpriteResource::operator=(const SpriteResource &src) {
63 delete[] _data;
64 _index.clear();
65
66 _filesize = src._filesize;
67 _data = new byte[_filesize];
68 Common::copy(src._data, src._data + _filesize, _data);
69
70 _index.resize(src._index.size());
71 for (uint i = 0; i < src._index.size(); ++i)
72 _index[i] = src._index[i];
73
74 return *this;
75 }
76
load(const Common::String & filename)77 void SpriteResource::load(const Common::String &filename) {
78 _filename = filename;
79 File f(filename);
80 load(f);
81 }
82
load(const Common::String & filename,int ccMode)83 void SpriteResource::load(const Common::String &filename, int ccMode) {
84 _filename = filename;
85 File f(filename, ccMode);
86 load(f);
87 }
88
load(Common::SeekableReadStream & f)89 void SpriteResource::load(Common::SeekableReadStream &f) {
90 // Read in a copy of the file
91 _filesize = f.size();
92 delete[] _data;
93 _data = new byte[_filesize];
94 f.read(_data, _filesize);
95
96 // Read in the index
97 f.seek(0);
98 int count = f.readUint16LE();
99 _index.resize(count);
100
101 for (int i = 0; i < count; ++i) {
102 _index[i]._offset1 = f.readUint16LE();
103 _index[i]._offset2 = f.readUint16LE();
104 }
105 }
106
clear()107 void SpriteResource::clear() {
108 delete[] _data;
109 _data = nullptr;
110 _filesize = 0;
111 _index.clear();
112 }
113
draw(XSurface & dest,int frame,const Common::Point & destPos,uint flags,int scale)114 void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
115 uint flags, int scale) {
116 draw(dest, frame, destPos, Common::Rect(0, 0, dest.w, dest.h), flags, scale);
117 }
118
draw(Window & dest,int frame,const Common::Point & destPos,uint flags,int scale)119 void SpriteResource::draw(Window &dest, int frame, const Common::Point &destPos,
120 uint flags, int scale) {
121 draw(dest, frame, destPos, dest.getBounds(), flags, scale);
122 }
123
draw(int windowIndex,int frame,const Common::Point & destPos,uint flags,int scale)124 void SpriteResource::draw(int windowIndex, int frame, const Common::Point &destPos,
125 uint flags, int scale) {
126 Window &win = (*g_vm->_windows)[windowIndex];
127 draw(win, frame, destPos, flags, scale);
128 }
129
draw(XSurface & dest,int frame,const Common::Point & destPos,const Common::Rect & bounds,uint flags,int scale)130 void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
131 const Common::Rect &bounds, uint flags, int scale) {
132 Common::Rect r = bounds;
133 if (flags & SPRFLAG_BOTTOM_CLIPPED)
134 r.clip(SCREEN_WIDTH, _clippedBottom);
135
136 // Create drawer to handle the rendering
137 SpriteDrawer *drawer;
138 switch (flags & SPRFLAG_MODE_MASK) {
139 case SPRFLAG_DRAWER1:
140 drawer = new SpriteDrawer1(_data, _filesize, flags & 0x1F);
141 break;
142 case SPRFLAG_DRAWER2:
143 error("TODO: Sprite drawer mode 2");
144 case SPRFLAG_DRAWER3:
145 drawer = new SpriteDrawer3(_data, _filesize, flags & 0x1F);
146 break;
147 case SPRFLAG_DRAWER5:
148 drawer = new SpriteDrawer5(_data, _filesize, flags & 0x1F);
149 break;
150 case SPRFLAG_DRAWER6:
151 drawer = new SpriteDrawer6(_data, _filesize, flags & 0x1F);
152 break;
153 default:
154 drawer = new SpriteDrawer(_data, _filesize);
155 break;
156 }
157
158 // Sprites can consist of separate background & foreground
159 drawer->draw(dest, _index[frame]._offset1, destPos, r, flags, scale);
160 if (_index[frame]._offset2)
161 drawer->draw(dest, _index[frame]._offset2, destPos, r, flags, scale);
162
163 delete drawer;
164 }
165
draw(XSurface & dest,int frame)166 void SpriteResource::draw(XSurface &dest, int frame) {
167 draw(dest, frame, Common::Point());
168 }
169
draw(int windowIndex,int frame)170 void SpriteResource::draw(int windowIndex, int frame) {
171 draw((*g_vm->_windows)[windowIndex], frame, Common::Point());
172 }
173
getFrameSize(int frame) const174 Common::Point SpriteResource::getFrameSize(int frame) const {
175 Common::MemoryReadStream f(_data, _filesize);
176 Common::Point frameSize;
177
178 for (int idx = 0; idx < (_index[frame]._offset2 ? 2 : 1); ++idx) {
179 f.seek((idx == 0) ? _index[frame]._offset1 : _index[frame]._offset2);
180 int xOffset = f.readUint16LE();
181 int width = f.readUint16LE();
182 int yOffset = f.readUint16LE();
183 int height = f.readUint16LE();
184
185 frameSize.x = MAX((int)frameSize.x, xOffset + width);
186 frameSize.y = MAX((int)frameSize.y, yOffset + height);
187 }
188
189 return frameSize;
190 }
191
192 /*------------------------------------------------------------------------*/
193
draw(XSurface & dest,uint16 offset,const Common::Point & pt,const Common::Rect & clipRect,uint flags,int scale)194 void SpriteDrawer::draw(XSurface &dest, uint16 offset, const Common::Point &pt,
195 const Common::Rect &clipRect, uint flags, int scale) {
196 static const uint SCALE_TABLE[] = {
197 0xFFFF, 0xFFEF, 0xEFEF, 0xEFEE, 0xEEEE, 0xEEAE, 0xAEAE, 0xAEAA,
198 0xAAAA, 0xAA8A, 0x8A8A, 0x8A88, 0x8888, 0x8880, 0x8080, 0x8000
199 };
200 static const int PATTERN_STEPS[] = { 0, 1, 1, 1, 2, 2, 3, 3, 0, -1, -1, -1, -2, -2, -3, -3 };
201
202 assert((scale & SCALE_MASK) < 16);
203 uint16 scaleMask = SCALE_TABLE[scale & SCALE_MASK];
204 uint16 scaleMaskX = scaleMask, scaleMaskY = scaleMask;
205 bool flipped = (flags & SPRFLAG_HORIZ_FLIPPED) != 0;
206 int xInc = flipped ? -1 : 1;
207 bool enlarge = (scale & SCALE_ENLARGE) != 0;
208
209 // Get cell header
210 Common::MemoryReadStream f(_data, _filesize);
211 f.seek(offset);
212 int xOffset = f.readUint16LE();
213 int width = f.readUint16LE();
214 int yOffset = f.readUint16LE();
215 int height = f.readUint16LE();
216
217 // Figure out drawing x, y
218 Common::Point destPos;
219 destPos.x = pt.x + getScaledVal(xOffset, scaleMaskX);
220 destPos.x += (width - getScaledVal(width, scaleMaskX)) / 2;
221
222 destPos.y = pt.y + getScaledVal(yOffset, scaleMaskY);
223
224 // If the flags allow the dest surface to be resized, ensure dest surface is big enough
225 Common::Rect bounds = clipRect;
226 if (flags & SPRFLAG_RESIZE) {
227 if (dest.w < (xOffset + width) || dest.h < (yOffset + height))
228 dest.create(xOffset + width, yOffset + height);
229 bounds = Common::Rect(0, 0, dest.w, dest.h);
230 }
231 if (flags & SPRFLAG_SCENE_CLIPPED) {
232 bounds.clip(Common::Rect(8, 8, 223, 141));
233 }
234
235 uint16 scaleMaskXCopy = scaleMaskX;
236 Common::Rect drawBounds;
237 drawBounds.left = SCREEN_WIDTH;
238 drawBounds.top = SCREEN_HEIGHT;
239 drawBounds.right = drawBounds.bottom = 0;
240
241 // Main loop
242 for (int yCtr = height; yCtr > 0; --yCtr) {
243 // The number of bytes in this scan line
244 int lineLength = f.readByte();
245
246 if (lineLength == 0) {
247 // Skip the specified number of scan lines
248 int numLines = f.readByte();
249 destPos.y += getScaledVal(numLines + 1, scaleMaskY);
250 yCtr -= numLines;
251 continue;
252 }
253
254 // Roll the scale mask
255 uint bit = (scaleMaskY >> 15) & 1;
256 scaleMaskY = ((scaleMaskY & 0x7fff) << 1) + bit;
257
258 if (!bit) {
259 // Not a line to be drawn due to scaling down
260 f.skip(lineLength);
261 } else if (destPos.y < bounds.top || destPos.y >= bounds.bottom) {
262 // Skip over the bytes of the line
263 f.skip(lineLength);
264 destPos.y++;
265 } else {
266 scaleMaskX = scaleMaskXCopy;
267 xOffset = f.readByte();
268
269 // Initialize the array to hold the temporary data for the line. We do this to make it simpler
270 // to handle both deciding which pixels to draw in a scaled image, as well as when images
271 // have been horizontally flipped. Note that we allocate an extra line for before and after our
272 // work line, just in case the sprite is screwed up and overruns the line
273 int tempLine[SCREEN_WIDTH * 3];
274 Common::fill(&tempLine[SCREEN_WIDTH], &tempLine[SCREEN_WIDTH * 3], -1);
275 int *lineP = flipped ? &tempLine[SCREEN_WIDTH + width - 1 - xOffset] : &tempLine[SCREEN_WIDTH + xOffset];
276
277 // Build up the line
278 int byteCount, opr1, opr2;
279 int32 pos;
280 for (byteCount = 1; byteCount < lineLength; ) {
281 // The next byte is an opcode that determines what operators are to follow and how to interpret them.
282 int opcode = f.readByte(); ++byteCount;
283
284 // Decode the opcode
285 int len = opcode & 0x1F;
286 int cmd = (opcode & 0xE0) >> 5;
287
288 switch (cmd) {
289 case 0: // The following len + 1 bytes are stored as indexes into the color table.
290 case 1: // The following len + 33 bytes are stored as indexes into the color table.
291 for (int i = 0; i < opcode + 1; ++i, ++byteCount) {
292 byte b = f.readByte();
293 *lineP = b;
294 lineP += xInc;
295 }
296 break;
297
298 case 2: // The following byte is an index into the color table, draw it len + 3 times.
299 opr1 = f.readByte(); ++byteCount;
300 for (int i = 0; i < len + 3; ++i) {
301 *lineP = opr1;
302 lineP += xInc;
303 }
304 break;
305
306 case 3: // Stream copy command.
307 opr1 = f.readUint16LE(); byteCount += 2;
308 pos = f.pos();
309 f.seek(-opr1, SEEK_CUR);
310
311 for (int i = 0; i < len + 4; ++i) {
312 *lineP = f.readByte();
313 lineP += xInc;
314 }
315
316 f.seek(pos, SEEK_SET);
317 break;
318
319 case 4: // The following two bytes are indexes into the color table, draw the pair len + 2 times.
320 opr1 = f.readByte(); ++byteCount;
321 opr2 = f.readByte(); ++byteCount;
322 for (int i = 0; i < len + 2; ++i) {
323 *lineP = opr1;
324 lineP += xInc;
325 *lineP = opr2;
326 lineP += xInc;
327 }
328 break;
329
330 case 5: // Skip len + 1 pixels
331 lineP += (len + 1) * xInc;
332 break;
333
334 case 6: // Pattern command.
335 case 7:
336 // The pattern command has a different opcode format
337 len = opcode & 0x07;
338 cmd = (opcode >> 2) & 0x0E;
339
340 opr1 = f.readByte(); ++byteCount;
341 for (int i = 0; i < len + 3; ++i) {
342 *lineP = opr1;
343 lineP += xInc;
344 opr1 += PATTERN_STEPS[cmd + (i % 2)];
345 }
346 break;
347
348 default:
349 break;
350 }
351 }
352 assert(byteCount == lineLength);
353
354 drawBounds.top = MIN(drawBounds.top, destPos.y);
355 drawBounds.bottom = MAX((int)drawBounds.bottom, destPos.y + 1);
356
357 // Handle drawing out the line
358 byte *destP = (byte *)dest.getBasePtr(destPos.x, destPos.y);
359 int16 xp = destPos.x;
360 lineP = &tempLine[SCREEN_WIDTH];
361
362 for (int xCtr = 0; xCtr < width; ++xCtr, ++lineP) {
363 bit = (scaleMaskX >> 15) & 1;
364 scaleMaskX = ((scaleMaskX & 0x7fff) << 1) + bit;
365
366 if (bit) {
367 // Check whether there's a pixel to write, and we're within the allowable bounds. Note that for
368 // the SPRFLAG_SCENE_CLIPPED or when enlarging, we also have an extra horizontal bounds check
369 if (*lineP != -1 && xp >= bounds.left && xp < bounds.right) {
370 drawBounds.left = MIN(drawBounds.left, xp);
371 drawBounds.right = MAX((int)drawBounds.right, xp + 1);
372 drawPixel(destP, (byte)*lineP);
373 if (enlarge) {
374 drawPixel(destP + SCREEN_WIDTH, (byte)*lineP);
375 drawPixel(destP + 1, (byte)*lineP);
376 drawPixel(destP + 1 + SCREEN_WIDTH, (byte)*lineP);
377 }
378 }
379
380 ++xp;
381 ++destP;
382 if (enlarge) {
383 ++destP;
384 ++xp;
385 }
386 }
387 }
388
389 ++destPos.y;
390 if (enlarge)
391 ++destPos.y;
392 }
393 }
394
395 if (drawBounds.isValidRect()) {
396 drawBounds.clip(Common::Rect(0, 0, dest.w, dest.h));
397 if (!drawBounds.isEmpty())
398 dest.addDirtyRect(drawBounds);
399 }
400 }
401
getScaledVal(int xy,uint16 & scaleMask)402 uint SpriteDrawer::getScaledVal(int xy, uint16 &scaleMask) {
403 if (!xy)
404 return 0;
405
406 uint result = 0;
407 for (int idx = 0; idx < xy; ++idx) {
408 uint bit = (scaleMask >> 15) & 1;
409 scaleMask = ((scaleMask & 0x7fff) << 1) + bit;
410 result += bit;
411 }
412
413 return result;
414 }
415
drawPixel(byte * dest,byte pixel)416 void SpriteDrawer::drawPixel(byte *dest, byte pixel) {
417 *dest = pixel;
418 }
419
420 /*------------------------------------------------------------------------*/
421
422 const byte DRAWER1_OFFSET[24] = {
423 0x30, 0xC0, 0xB0, 0x10, 0x41, 0x20, 0x40, 0x21, 0x48, 0x46, 0x43, 0x40,
424 0xD0, 0xD3, 0xD6, 0xD8, 0x01, 0x04, 0x07, 0x0A, 0xEA, 0xEE, 0xF2, 0xF6
425 };
426
427 const byte DRAWER1_MASK[24] = {
428 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x0F, 0x07, 0x07, 0x07, 0x07,
429 0x07, 0x07, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x07, 0x07
430 };
431
SpriteDrawer1(byte * data,size_t filesize,int index)432 SpriteDrawer1::SpriteDrawer1(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
433 _offset = DRAWER1_OFFSET[index];
434 _mask = DRAWER1_MASK[index];
435 }
436
drawPixel(byte * dest,byte pixel)437 void SpriteDrawer1::drawPixel(byte *dest, byte pixel) {
438 *dest = (pixel & _mask) + _offset;
439 }
440
441 /*------------------------------------------------------------------------*/
442
443 const uint16 DRAWER3_MASK[4] = { 1, 3, 7, 15 };
444 const uint16 DRAWER3_OFFSET[4] = { 1, 2, 4, 8 };
445
SpriteDrawer3(byte * data,size_t filesize,int index)446 SpriteDrawer3::SpriteDrawer3(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
447 _offset = DRAWER3_OFFSET[index];
448 _mask = DRAWER3_MASK[index];
449
450 g_system->getPaletteManager()->grabPalette(_palette, 0, PALETTE_COUNT);
451 _hasPalette = false;
452 for (byte *pal = _palette; pal < _palette + PALETTE_SIZE && !_hasPalette; ++pal)
453 _hasPalette = *pal != 0;
454 }
455
drawPixel(byte * dest,byte pixel)456 void SpriteDrawer3::drawPixel(byte *dest, byte pixel) {
457 // WORKAROUND: This is slightly different then the original:
458 // 1) The original has bunches of black pixels appearing. This does index increments to avoid such pixels
459 // 2) It also prevents any pixels being drawn in the single initial frame until the palette is set
460 if (_hasPalette) {
461 byte level = (pixel & _mask) - _offset + (*dest & 0xf);
462
463 if (level >= 0x80) {
464 *dest &= 0xf0;
465 } else if (level <= 0xf) {
466 *dest = (*dest & 0xf0) | level;
467 } else {
468 *dest |= 0xf;
469 }
470
471 //
472 while (*dest < 0xff && !_palette[*dest * 3] && !_palette[*dest * 3 + 1] && !_palette[*dest * 3 + 2])
473 ++*dest;
474 }
475 }
476
477 /*------------------------------------------------------------------------*/
478
479 const byte DRAWER4_THRESHOLD[4] = { 4, 7, 10, 13 };
480
SpriteDrawer4(byte * data,size_t filesize,int index)481 SpriteDrawer4::SpriteDrawer4(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
482 _threshold = DRAWER4_THRESHOLD[index];
483 }
484
drawPixel(byte * dest,byte pixel)485 void SpriteDrawer4::drawPixel(byte *dest, byte pixel) {
486 if ((pixel & 0xf) >= _threshold)
487 *dest = pixel;
488 }
489
490 /*------------------------------------------------------------------------*/
491
492 const uint16 DRAWER5_THRESHOLD[4] = { 0x3333, 0x6666, 0x999A, 0xCCCD };
493
SpriteDrawer5(byte * data,size_t filesize,int index)494 SpriteDrawer5::SpriteDrawer5(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
495 _threshold = DRAWER5_THRESHOLD[index];
496 _random1 = g_vm->getRandomNumber(0xffff);
497 _random2 = g_vm->getRandomNumber(0xffff);
498 }
499
drawPixel(byte * dest,byte pixel)500 void SpriteDrawer5::drawPixel(byte *dest, byte pixel) {
501 bool flag = (_random1 & 0x8000) != 0;
502 _random1 = (int)((uint16)_random1 << 1) - _random2 - (flag ? 1 : 0);
503
504 rcr(_random2, flag);
505 rcr(_random2, flag);
506 _random2 ^= _random1;
507
508 if (_random2 > _threshold)
509 *dest = pixel;
510 }
511
rcr(uint16 & val,bool & cf)512 void SpriteDrawer5::rcr(uint16 &val, bool &cf) {
513 bool newCf = (val & 1);
514 val = (val >> 1) | (cf ? 0x8000 : 0);
515 cf = newCf;
516 }
517
518 /*------------------------------------------------------------------------*/
519
520 const byte DRAWER6_MASK[16] = { 1, 2, 4, 8, 1, 3, 7, 15, 8, 12, 14, 15, 1, 2, 1, 2 };
521
SpriteDrawer6(byte * data,size_t filesize,int index)522 SpriteDrawer6::SpriteDrawer6(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
523 _mask = DRAWER6_MASK[index];
524 }
525
drawPixel(byte * dest,byte pixel)526 void SpriteDrawer6::drawPixel(byte *dest, byte pixel) {
527 *dest = pixel ^ _mask;
528 }
529
530 } // End of namespace Xeen
531