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 "engines/util.h"
24 #include "mads/compression.h"
25 #include "mads/screen.h"
26 #include "mads/mads.h"
27 #include "mads/msurface.h"
28 #include "mads/resources.h"
29 #include "mads/sprites.h"
30
31 namespace MADS {
32
33 MADSEngine *BaseSurface::_vm = nullptr;
34
scaleValue(int value,int scale,int err)35 int BaseSurface::scaleValue(int value, int scale, int err) {
36 int scaled = 0;
37 while (value--) {
38 err -= scale;
39 while (err < 0) {
40 scaled++;
41 err += 100;
42 }
43 }
44 return scaled;
45 }
46
drawSprite(const Common::Point & pt,SpriteInfo & info,const Common::Rect & clipRect)47 void BaseSurface::drawSprite(const Common::Point &pt, SpriteInfo &info, const Common::Rect &clipRect) {
48 enum {
49 kStatusSkip,
50 kStatusScale,
51 kStatusDraw
52 };
53
54 // NOTE: The current clipping code assumes that the top left corner of the clip
55 // rectangle is always 0, 0
56 assert(clipRect.top == 0 && clipRect.left == 0);
57
58 int errX = info.hotX * info.scaleX % 100;
59 int errY = info.hotY * info.scaleY % 100;
60 int scaledWidth = scaleValue(info.width, info.scaleX, errX);
61 int scaledHeight = scaleValue(info.height, info.scaleY, errY);
62
63 int x = pt.x, y = pt.y;
64 int clipX = 0, clipY = 0;
65 // Clip the sprite's width and height according to the clip rectangle's dimensions
66 // This clips the sprite to the bottom and right
67 if (x >= 0) {
68 scaledWidth = MIN<int>(x + scaledWidth, clipRect.right) - x;
69 } else {
70 clipX = x;
71 scaledWidth = x + scaledWidth;
72 }
73 if (y >= 0) {
74 scaledHeight = MIN<int>(y + scaledHeight, clipRect.bottom) - y;
75 } else {
76 clipY = y;
77 scaledHeight = y + scaledHeight;
78 }
79
80 // Check if sprite is inside the screen. If it's not, there's no need to draw it
81 if (scaledWidth + x <= 0 || scaledHeight + y <= 0) // check left and top (in case x,y are negative)
82 return;
83 if (scaledWidth <= 0 || scaledHeight <= 0) // check right and bottom
84 return;
85 int heightAmt = scaledHeight;
86
87 const byte *src = (const byte *)info.sprite->getPixels();
88 byte *dst = (byte *)getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY);
89
90 int status = kStatusSkip;
91 byte *scaledLineBuf = new byte[scaledWidth];
92
93 while (heightAmt > 0) {
94
95 if (status == kStatusSkip) {
96 // Skip line
97 errY -= info.scaleY;
98 if (errY < 0)
99 status = kStatusScale;
100 else
101 src += info.width;
102 } else {
103
104 if (status == kStatusScale) {
105 // Scale current line
106 byte *lineDst = scaledLineBuf;
107 int curErrX = errX;
108 int width = scaledWidth;
109 const byte *tempSrc = src;
110 int startX = clipX;
111 while (width > 0) {
112 byte pixel = *tempSrc++;
113 curErrX -= info.scaleX;
114 while (curErrX < 0) {
115 if (startX == 0) {
116 *lineDst++ = pixel;
117 width--;
118 } else {
119 startX++;
120 }
121 curErrX += 100;
122 }
123 }
124 src += info.width;
125 status = kStatusDraw;
126 }
127
128 if (status == kStatusDraw && clipY == 0) {
129 // Draw previously scaled line
130 byte *tempDst = dst;
131 for (int lineX = 0; lineX < scaledWidth; lineX++) {
132 byte pixel = scaledLineBuf[lineX];
133
134 if (info.encoding & 0x80) {
135
136 if (pixel == 0x80) {
137 pixel = 0;
138 } else {
139 byte destPixel = *tempDst;
140 byte r, g, b;
141 r = CLIP((info.palette[destPixel * 3] * pixel) >> 10, 0, 31);
142 g = CLIP((info.palette[destPixel * 3 + 1] * pixel) >> 10, 0, 31);
143 b = CLIP((info.palette[destPixel * 3 + 2] * pixel) >> 10, 0, 31);
144 pixel = info.inverseColorTable[(b << 10) | (g << 5) | r];
145 }
146 }
147
148 if (pixel)
149 *tempDst = pixel;
150
151 tempDst++;
152 }
153 dst += pitch;
154 heightAmt--;
155
156 errY += 100;
157 if (errY >= 0)
158 status = kStatusSkip;
159 } else if (status == kStatusDraw && clipY < 0) {
160 clipY++;
161
162 errY += 100;
163 if (errY >= 0)
164 status = kStatusSkip;
165 }
166
167 }
168
169 }
170
171 delete[] scaledLineBuf;
172 }
173
scrollX(int xAmount)174 void BaseSurface::scrollX(int xAmount) {
175 if (xAmount == 0)
176 return;
177
178 byte buffer[80];
179 int direction = (xAmount > 0) ? -1 : 1;
180 int xSize = ABS(xAmount);
181 assert(xSize <= 80);
182
183 byte *srcP = (byte *)getBasePtr(0, 0);
184
185 for (int y = 0; y < this->h; ++y, srcP += pitch) {
186 if (direction < 0) {
187 // Copy area to be overwritten
188 Common::copy(srcP, srcP + xSize, &buffer[0]);
189 // Shift the remainder of the line over the given area
190 Common::copy(srcP + xSize, srcP + this->w, srcP);
191 // Move buffered area to the end of the line
192 Common::copy(&buffer[0], &buffer[xSize], srcP + this->w - xSize);
193 } else {
194 // Copy area to be overwritten
195 Common::copy_backward(srcP + this->w - xSize, srcP + this->w, &buffer[80]);
196 // Shift the remainder of the line over the given area
197 Common::copy_backward(srcP, srcP + this->w - xSize, srcP + this->w);
198 // Move buffered area to the start of the line
199 Common::copy_backward(&buffer[80 - xSize], &buffer[80], srcP + xSize);
200 }
201 }
202
203 markAllDirty();
204 }
205
scrollY(int yAmount)206 void BaseSurface::scrollY(int yAmount) {
207 if (yAmount == 0)
208 return;
209
210 int direction = (yAmount > 0) ? 1 : -1;
211 int ySize = ABS(yAmount);
212 assert(ySize < (this->h / 2));
213 assert(this->w == pitch);
214
215 int blockSize = ySize * this->w;
216 byte *tempData = new byte[blockSize];
217 byte *pixelsP = (byte *)getBasePtr(0, 0);
218
219 if (direction > 0) {
220 // Buffer the lines to be overwritten
221 byte *srcP = (byte *)getBasePtr(0, this->h - ySize);
222 Common::copy(srcP, srcP + (pitch * ySize), tempData);
223 // Vertically shift all the lines
224 Common::copy_backward(pixelsP, pixelsP + (pitch * (this->h - ySize)),
225 pixelsP + (pitch * this->h));
226 // Transfer the buffered lines top the top of the screen
227 Common::copy(tempData, tempData + blockSize, pixelsP);
228 } else {
229 // Buffer the lines to be overwritten
230 Common::copy(pixelsP, pixelsP + (pitch * ySize), tempData);
231 // Vertically shift all the lines
232 Common::copy(pixelsP + (pitch * ySize), pixelsP + (pitch * this->h), pixelsP);
233 // Transfer the buffered lines to the bottom of the screen
234 Common::copy(tempData, tempData + blockSize, pixelsP + (pitch * (this->h - ySize)));
235 }
236
237 markAllDirty();
238 delete[] tempData;
239 }
240
translate(Common::Array<RGB6> & palette)241 void BaseSurface::translate(Common::Array<RGB6> &palette) {
242 for (int y = 0; y < this->h; ++y) {
243 byte *pDest = (byte *)getBasePtr(0, y);
244
245 for (int x = 0; x < this->w; ++x, ++pDest) {
246 if (*pDest < 255) // scene 752 has some palette indices of 255
247 *pDest = palette[*pDest]._palIndex;
248 }
249 }
250
251 markAllDirty();
252 }
253
translate(byte map[PALETTE_COUNT])254 void BaseSurface::translate(byte map[PALETTE_COUNT]) {
255 for (int y = 0; y < this->h; ++y) {
256 byte *pDest = (byte *)getBasePtr(0, y);
257
258 for (int x = 0; x < this->w; ++x, ++pDest) {
259 *pDest = map[*pDest];
260 }
261 }
262
263 markAllDirty();
264 }
265
flipHorizontal() const266 BaseSurface *BaseSurface::flipHorizontal() const {
267 MSurface *dest = new MSurface(this->w, this->h);
268
269 for (int y = 0; y < this->h; ++y) {
270 const byte *srcP = getBasePtr(this->w - 1, y);
271 byte *destP = dest->getBasePtr(0, y);
272
273 for (int x = 0; x < this->w; ++x)
274 *destP++ = *srcP--;
275 }
276
277 return dest;
278 }
279
copyRectTranslate(BaseSurface & srcSurface,const byte * paletteMap,const Common::Point & destPos,const Common::Rect & srcRect)280 void BaseSurface::copyRectTranslate(BaseSurface &srcSurface, const byte *paletteMap,
281 const Common::Point &destPos, const Common::Rect &srcRect) {
282 // Loop through the lines
283 for (int yCtr = 0; yCtr < srcRect.height(); ++yCtr) {
284 const byte *srcP = (const byte *)srcSurface.getBasePtr(srcRect.left, srcRect.top + yCtr);
285 byte *destP = (byte *)getBasePtr(destPos.x, destPos.y + yCtr);
286
287 // Copy the line over
288 for (int xCtr = 0; xCtr < srcRect.width(); ++xCtr, ++srcP, ++destP) {
289 *destP = paletteMap[*srcP];
290 }
291 }
292
293 addDirtyRect(Common::Rect(destPos.x, destPos.y, destPos.x + srcRect.width(),
294 destPos.y + srcRect.height()));
295 }
296
copyFrom(BaseSurface & src,const Common::Point & destPos,int depth,DepthSurface * depthSurface,int scale,bool flipped,int transparentColor)297 void BaseSurface::copyFrom(BaseSurface &src, const Common::Point &destPos, int depth,
298 DepthSurface *depthSurface, int scale, bool flipped, int transparentColor) {
299 int destX = destPos.x, destY = destPos.y;
300 int frameWidth = src.w;
301 int frameHeight = src.h;
302 int direction = flipped ? -1 : 1;
303
304 int highestDim = MAX(frameWidth, frameHeight);
305 bool lineDist[MADS_SCREEN_WIDTH];
306 int distXCount = 0, distYCount = 0;
307
308 if (scale != -1) {
309 int distCtr = 0;
310 int distIndex = 0;
311 do {
312 distCtr += scale;
313 if (distCtr < 100) {
314 lineDist[distIndex] = false;
315 }
316 else {
317 lineDist[distIndex] = true;
318 distCtr -= 100;
319
320 if (distIndex < frameWidth)
321 ++distXCount;
322
323 if (distIndex < frameHeight)
324 ++distYCount;
325 }
326 } while (++distIndex < highestDim);
327
328 destX -= distXCount / 2;
329 destY -= distYCount - 1;
330 }
331
332 // Special case for quicker drawing of non-scaled images
333 if (scale == 100 || scale == -1) {
334 // Copy the specified area
335 Common::Rect copyRect(0, 0, src.w, src.h);
336
337 if (destX < 0) {
338 copyRect.left += -destX;
339 destX = 0;
340 } else if (destX + copyRect.width() > w) {
341 copyRect.right -= destX + copyRect.width() - w;
342 }
343 if (destY < 0) {
344 copyRect.top += -destY;
345 destY = 0;
346 } else if (destY + copyRect.height() > h) {
347 copyRect.bottom -= destY + copyRect.height() - h;
348 }
349
350 if (!copyRect.isValidRect())
351 return;
352
353 if (flipped)
354 copyRect.moveTo(0, copyRect.top);
355
356 byte *data = src.getPixels();
357 byte *srcPtr = data + (src.w * copyRect.top + copyRect.left);
358 byte *destPtr = (byte *)getPixels() + (destY * pitch) + destX;
359 if (flipped)
360 srcPtr += copyRect.width() - 1;
361
362 // 100% scaling variation
363 for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) {
364 // Copy each byte one at a time checking against the depth
365 for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) {
366 byte *srcP = srcPtr + xCtr * direction;
367 int pixelDepth = depthSurface == nullptr ? 15 :
368 depthSurface->getDepth(Common::Point(destX + xCtr, destY + rowCtr));
369
370 if ((depth <= pixelDepth) && (*srcP != transparentColor))
371 destPtr[xCtr] = *srcP;
372 }
373
374 srcPtr += src.w;
375 destPtr += this->w;
376 }
377
378 return;
379 }
380
381 // Start of draw logic for scaled sprites
382 const byte *srcPixelsP = src.getPixels();
383
384 int destRight = this->w - 1;
385 int destBottom = this->h - 1;
386
387 // Check x bounding area
388 int spriteLeft = 0;
389 int spriteWidth = distXCount;
390 int widthAmount = destX + distXCount - 1;
391
392 if (destX < 0) {
393 spriteWidth += destX;
394 spriteLeft -= destX;
395 }
396 widthAmount -= destRight;
397 if (widthAmount > 0)
398 spriteWidth -= widthAmount;
399
400 if (spriteWidth <= 0)
401 return;
402
403 int spriteRight = spriteLeft + spriteWidth;
404 if (flipped) {
405 destX += distXCount - 1;
406 spriteLeft = -(distXCount - spriteRight);
407 spriteRight = (-spriteLeft + spriteWidth);
408 }
409
410 // Check y bounding area
411 int spriteTop = 0;
412 int spriteHeight = distYCount;
413 int heightAmount = destY + distYCount - 1;
414
415 if (destY < 0) {
416 spriteHeight += destY;
417 spriteTop -= destY;
418 }
419 heightAmount -= destBottom;
420 if (heightAmount > 0)
421 spriteHeight -= heightAmount;
422 int spriteBottom = spriteTop + spriteHeight;
423
424 if (spriteHeight <= 0)
425 return;
426
427 byte *destPixelsP = this->getBasePtr(destX + spriteLeft, destY + spriteTop);
428
429 spriteLeft = spriteLeft * direction;
430
431 // Loop through the lines of the sprite
432 for (int yp = 0, sprY = -1; yp < frameHeight; ++yp, srcPixelsP += src.pitch) {
433 if (!lineDist[yp])
434 // Not a display line, so skip it
435 continue;
436 // Check whether the sprite line is in the display range
437 ++sprY;
438 if ((sprY >= spriteBottom) || (sprY < spriteTop))
439 continue;
440
441 // Found a line to display. Loop through the pixels
442 const byte *srcP = srcPixelsP;
443 byte *destP = destPixelsP;
444
445 for (int xp = 0, sprX = -1; xp < frameWidth; ++xp, ++srcP) {
446 if (!lineDist[xp])
447 // Not a display pixel
448 continue;
449
450 ++sprX;
451 if (sprX < spriteLeft || sprX >= spriteRight)
452 // Skip pixel if it's not in horizontal display portion
453 continue;
454
455 // Get depth of current output pixel in depth surface
456 Common::Point pt((destP - (byte *)getPixels()) % this->pitch,
457 (destP - (byte *)getPixels()) / this->pitch);
458 int pixelDepth = (depthSurface == nullptr) ? 15 : depthSurface->getDepth(pt);
459
460 if ((*srcP != transparentColor) && (depth <= pixelDepth))
461 *destP = *srcP;
462
463 destP += direction;
464 }
465
466 // Move to the next destination line
467 destPixelsP += this->pitch;
468 }
469
470 addDirtyRect(Common::Rect(destX, destY, destX + frameWidth, destY + frameHeight));
471 }
472
473 /*------------------------------------------------------------------------*/
474
getDepth(const Common::Point & pt)475 int DepthSurface::getDepth(const Common::Point &pt) {
476 if (_depthStyle == 2) {
477 int bits = (3 - (pt.x % 4)) * 2;
478 byte v = *(const byte *)getBasePtr(pt.x >> 2, pt.y);
479 return v >> bits;
480 } else {
481 if (pt.x < 0 || pt.y < 0 || pt.x >= this->w || pt.y >= this->h)
482 return 0;
483
484 return *(const byte *)getBasePtr(pt.x, pt.y) & 0xF;
485 }
486 }
487
getDepthHighBit(const Common::Point & pt)488 int DepthSurface::getDepthHighBit(const Common::Point &pt) {
489 if (_depthStyle == 2) {
490 int bits = (3 - (pt.x % 4)) * 2;
491 byte v = *(const byte *)getBasePtr(pt.x >> 2, pt.y);
492 return (v >> bits) & 2;
493 } else {
494 if (pt.x < 0 || pt.y < 0 || pt.x >= this->w || pt.y >= this->h)
495 return 0;
496
497 return *(const byte *)getBasePtr(pt.x, pt.y) & 0x80;
498 }
499 }
500
501 } // End of namespace MADS
502