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