1 /*
2  * Copyright 2010, 2011, 2014, 2016 Peter Olsson
3  *
4  * This file is part of Brum Brum Rally.
5  *
6  * Brum Brum Rally is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Brum Brum Rally is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "Path.h"
21 #include "Surface.h"
22 #include "Font.h"
23 #include "Vector.h"
24 #include "SDL_image.h"
25 #include <climits>
26 #include <algorithm>
27 #include <cmath>
28 #include <sstream>
29 #include <vector>
30 
Surface()31 Surface::Surface()
32 :	surface(0),
33 	clipping(),
34 	colorKey(),
35 	importantPixel()
36 {
37 	clipping.x = 0;
38 	clipping.y = 0;
39 	clipping.w = 0;
40 	clipping.h = 0;
41 }
42 
Surface(int width,int height)43 Surface::Surface(int width, int height)
44 :	colorKey(),
45 	importantPixel()
46 {
47 	SDL_PixelFormat *format = SDL_GetVideoSurface()->format;
48 	surface = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, format->BitsPerPixel,
49 	             format->Rmask, format->Gmask, format->Bmask, format->Amask);
50 
51 	if (format->BytesPerPixel == 1)
52 	{
53 		SDL_SetPalette(surface, SDL_LOGPAL, format->palette->colors, 0, format->palette->ncolors);
54 	}
55 
56 	clipping.x = 0;
57 	clipping.y = 0;
58 	clipping.w = width;
59 	clipping.h = height;
60 	colorKey.unused = true;
61 }
62 
Surface(const std::string & filename)63 Surface::Surface(const std::string& filename)
64 :	colorKey(),
65 	importantPixel()
66 {
67 	SDL_Surface* tempSurface = IMG_Load((dataPath() + filename).c_str());
68 
69 	clipping.x = 0;
70 	clipping.y = 0;
71 
72 	if(!tempSurface)
73 	{
74 		clipping.w = 0;
75 		clipping.h = 0;
76 		surface = 0;
77 		return;
78 	}
79 	surface = SDL_DisplayFormat(tempSurface);
80 	SDL_FreeSurface(tempSurface);
81 
82 	clipping.w = surface->w;
83 	clipping.h = surface->h;
84 
85 	colorKey.unused = true;
86 }
87 
getSmallFont()88 const Font& getSmallFont()
89 {
90 	static Font smallFont("small");
91 	return smallFont;
92 }
93 
getBigFont()94 const Font& getBigFont()
95 {
96 	static Font smallFont("big");
97 	return smallFont;
98 }
99 
getFont(FontType font)100 const Font& getFont(FontType font)
101 {
102 	switch (font)
103 	{
104 	case SMALL_FONT:
105 		return getSmallFont();
106 	case BIG_FONT:
107 	default: // Silent warnings.
108 		return getBigFont();
109 	}
110 }
111 
112 
Surface(const std::string & str,FontType font,Color c)113 Surface::Surface(const std::string& str, FontType font, Color c)
114 :	surface(0)
115 {
116 	*this = getFont(font).createTextSurface(str, c);
117 }
118 
Surface(const std::string & str,FontType font,Color c1,Color c2)119 Surface::Surface(const std::string& str, FontType font, Color c1, Color c2)
120 :	surface(0)
121 {
122 	*this = getFont(font).createTextSurface(str, c1, c2);
123 }
124 
Surface(const Surface & s)125 Surface::Surface(const Surface& s)
126 :	surface(s.surface),
127 	clipping(s.clipping),
128 	colorKey(s.colorKey),
129 	importantPixel(s.importantPixel)
130 {
131 	if (surface)
132 	{
133 		surface->refcount++;
134 	}
135 }
136 
~Surface()137 Surface::~Surface()
138 {
139 	// Note that SDL_FreeSurface will only decrease the refcount
140 	// if refcount is greater than 1.
141 	if (surface)
142 	{
143 		SDL_FreeSurface(surface);
144 	}
145 }
146 
operator =(const Surface & s)147 Surface& Surface::operator=(const Surface& s)
148 {
149 	if (surface)
150 	{
151 		SDL_FreeSurface(surface);
152 	}
153 	surface = s.surface;
154 	if (surface)
155 	{
156 		surface->refcount++;
157 	}
158 	clipping = s.clipping;
159 	colorKey = s.colorKey;
160 	importantPixel = s.importantPixel;
161 
162 	return *this;
163 }
164 
subSurface(int x,int y,int w,int h) const165 Surface Surface::subSurface(int x, int y, int w, int h) const
166 {
167 	if (!surface)
168 	{
169 		return Surface();
170 	}
171 
172 	Surface newSurface = *this;
173 	newSurface.clipping.x += x;
174 	newSurface.clipping.y += y;
175 	newSurface.clipping.w = w;
176 	newSurface.clipping.h = h;
177 	newSurface.validateClipping();
178 
179 	return newSurface;
180 }
181 
setImportantPixel(Uint32 pixelValue)182 void Surface::setImportantPixel(Uint32 pixelValue)
183 {
184 	importantPixel = pixelValue;
185 }
186 
validateClipping()187 void Surface::validateClipping()
188 {
189 	if (clipping.x >= surface->w)
190 	{
191 		clipping.x = surface->w - 1;
192 	}
193 	if (clipping.y >= surface->h)
194 	{
195 		clipping.y = surface->h - 1;
196 	}
197 	if (clipping.x + clipping.w > surface->w)
198 	{
199 		clipping.w = surface->w - clipping.x;
200 	}
201 	if (clipping.x + clipping.h > surface->h)
202 	{
203 		clipping.h = surface->h - clipping.y;
204 	}
205 }
206 
draw(Surface & dest,int x,int y) const207 void Surface::draw(Surface& dest, int x, int y) const
208 {
209 	if (!surface || !dest.surface)
210 	{
211 		return;
212 	}
213 
214 	// if the destination SDL_Surface object is shared by more than one
215 	// object we need to make a copy before drawing
216 	if (dest.surface->refcount > 1)
217 	{
218 		dest.surface->refcount--;
219 		// SDL_DisplayFormat will do the copying
220 		dest.surface = SDL_DisplayFormat(dest.surface);
221 	}
222 
223 	if (colorKey.unused)
224 	{
225 		SDL_SetColorKey(surface, 0, 0);
226 	}
227 	else
228 	{
229 		SDL_SetColorKey(surface, SDL_SRCCOLORKEY, SDL_MapRGB(surface->format, colorKey.r, colorKey.g ,colorKey.b));
230 	}
231 
232 	SDL_Rect offset;
233 	offset.x = x;
234 	offset.y = y;
235 
236 	SDL_BlitSurface(surface, &clipping, dest.surface, &offset);
237 }
238 
getPixel(SDL_Surface * surface,Sint16 x,Sint16 y)239 Uint32 getPixel(SDL_Surface* surface, Sint16 x, Sint16 y)
240 {
241 	if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
242 	{
243 		int bpp = surface->format->BytesPerPixel;
244 		Uint8 *pixel = (Uint8*)surface->pixels + y * surface->pitch + x * bpp;
245 
246 		switch(bpp) {
247 		case 1:
248 			return *pixel;
249 		case 2:
250 			return *(Uint16 *)pixel;
251 		case 3:
252 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
253 			return pixel[0] << 16 | pixel[1] << 8 | pixel[2];
254 #else
255 			return pixel[0] | pixel[1] << 8 | pixel[2] << 16;
256 #endif
257 		case 4:
258 			return *(Uint32 *) pixel;
259 		}
260 	}
261 	return 0;
262 }
263 
setPixel(SDL_Surface * surface,Sint16 x,Sint16 y,Uint32 value)264 void setPixel(SDL_Surface* surface, Sint16 x, Sint16 y, Uint32 value)
265 {
266 	if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
267 	{
268 		int bpp = surface->format->BytesPerPixel;
269 		Uint8 *pixel = (Uint8*)surface->pixels + y * surface->pitch + x * bpp;
270 
271 		switch (bpp) {
272 		case 1:
273 			*pixel = value;
274 			break;
275 		case 2:
276 			(*(Uint16*) pixel) = value;
277 			break;
278 		case 3:
279 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
280 			pixel[0] = (value >> 16) & 0xFF;
281 			pixel[1] = (value >> 8)  & 0xFF;
282 			pixel[2] = (value)       & 0xFF;
283 #else
284 			pixel[0] = (value)       & 0xFF;
285 			pixel[1] = (value >> 8)  & 0xFF;
286 			pixel[2] = (value >> 16) & 0xFF;
287 #endif
288 			break;
289 		case 4:
290 			(*(Uint32*) pixel) = value;
291 			break;
292 		}
293 	}
294 }
295 
rotate_draw(SDL_Surface * src,SDL_Surface * dest,int posX,int posY,const SDL_Rect & rect,double angle,Uint32 preservePixelValue)296 static void rotate_draw(SDL_Surface* src, SDL_Surface* dest, int posX, int posY, const SDL_Rect& rect, double angle, Uint32 preservePixelValue)
297 {
298 	Vector p1(0, 0);
299 	p1.rotate(angle);
300 	Vector p2(rect.w, rect.h);
301 	p2.rotate(angle);
302 	Vector p3(0, rect.h);
303 	p3.rotate(angle);
304 	Vector p4(rect.w, 0);
305 	p4.rotate(angle);
306 	int minX = std::floor(std::min(std::min(std::min(p1.x, p2.x), p3.x), p4.x));
307 	int minY = std::floor(std::min(std::min(std::min(p1.y, p2.y), p3.y), p4.y));
308 	int maxX = std::ceil(std::max(std::max(std::max(p1.x, p2.x), p3.x), p4.x));
309 	int maxY = std::ceil(std::max(std::max(std::max(p1.y, p2.y), p3.y), p4.y));
310 
311 	int countX = maxX - minX + 1;
312 	int countY = maxY - minY + 1;
313 
314 	typedef char PixelBool;
315 	static std::vector<PixelBool> drawnPixels;
316 	drawnPixels.resize(countX * countY, false);
317 
318 	Vector mid(0.5 * rect.w, 0.5 * rect.h);
319 	Vector midRot(mid);
320 	midRot.rotate(angle);
321 	double dx = mid.x + posX - int(midRot.x); // we want to round towards zero, that's why we convert to int here.
322 	double dy = mid.y + posY - int(midRot.y);
323 
324 	// rotations is mainly used to rotate cars so by iterating the
325 	// pixels in reverse order we are less likely to miss the headlights
326 	for (int y = rect.h - 1; y >= 0; y--)
327 	{
328 		for (int x = rect.w - 1; x >= 0; x--)
329 		{
330 			Uint32 pxl = getPixel(src, rect.x + x, rect.y + y);
331 			if (pxl != src->format->colorkey)
332 			{
333 				Vector v(x, y);
334 				v.rotate(angle);
335 
336 				PixelBool& pixelIsDrawn = drawnPixels[std::floor(dx + v.x) - minX - std::floor(dx) + (std::floor(dy + v.y) - minY  - std::floor(dy)) * countX];
337 				if (!pixelIsDrawn || pxl == preservePixelValue)
338 				{
339 					setPixel(dest, dx + v.x, dy + v.y, pxl);
340 					pixelIsDrawn = true;
341 				}
342 			}
343 		}
344 	}
345 
346 	// fill holes in image
347 	for (int y = 1; y < countY - 1; y++)
348 	{
349 		for (int x = 1; x < countX - 1; x++)
350 		{
351 			if (!drawnPixels[x + y * countX] &&
352 			    drawnPixels[(x + 1) + y * countX] &&
353 			    drawnPixels[(x - 1) + y * countX] &&
354 			    drawnPixels[x + (y + 1) * countX] &&
355 			    drawnPixels[x + (y - 1) * countX] )
356 			{
357 				int mx = dest->clip_rect.x + dx + minX + x;
358 				int my = dest->clip_rect.y + dy + minY + y;
359 				Uint32 pxl1 = getPixel(dest, mx + 1, my);
360 				Uint32 pxl2 = getPixel(dest, mx - 1, my);
361 				Uint32 pxl3 = getPixel(dest, mx, my + 1);
362 				Uint32 pxl4 = getPixel(dest, mx, my - 1);
363 				Uint32 pxl;
364 				// now we want to find the most common neighbor color.
365 				// it's enough if two color is equal
366 				if (pxl1 == pxl2 || pxl1 == pxl3 || pxl1 == pxl4)
367 				{
368 					pxl = pxl1;
369 				}
370 				else if (pxl2 == pxl3 || pxl2 == pxl4)
371 				{
372 					pxl = pxl2;
373 				}
374 				else if (pxl3 == pxl4)
375 				{
376 					pxl = pxl3;
377 				}
378 				else
379 				{
380 					pxl = pxl4;
381 				}
382 
383 				setPixel(dest, dx + minX + x, dy + minY + y, pxl);
384 			}
385 		}
386 	}
387 
388 	drawnPixels.clear();
389 }
390 
draw(Surface & dest,int x,int y,double angle) const391 void Surface::draw(Surface& dest, int x, int y, double angle) const
392 {
393 	if (!surface || !dest.surface)
394 	{
395 		return;
396 	}
397 
398 	// if the destination SDL_Surface object is shared by more than one
399 	// object we need to make a copy before drawing
400 	if (dest.surface->refcount > 1)
401 	{
402 		dest.surface->refcount--;
403 		// SDL_DisplayFormat will do the copying
404 		dest.surface = SDL_DisplayFormat(dest.surface);
405 	}
406 
407 	if (colorKey.unused)
408 	{
409 		SDL_SetColorKey(surface, 0, 0);
410 	}
411 	else
412 	{
413 		SDL_SetColorKey(surface, SDL_SRCCOLORKEY, SDL_MapRGB(surface->format, colorKey.r, colorKey.g ,colorKey.b));
414 	}
415 
416 	if (angle == 0.0)
417 	{
418 		// It is important to handle no rotation in special way to
419 		// get acceptable performance.
420 		SDL_Rect offset;
421 		offset.x = x;
422 		offset.y = y;
423 		SDL_BlitSurface(surface, &clipping, dest.surface, &offset);
424 	}
425 	else
426 	{
427 		rotate_draw(surface, dest.surface, x, y, clipping, angle, importantPixel);
428 	}
429 }
430 
width() const431 int Surface::width() const
432 {
433 	return clipping.w;
434 }
height() const435 int Surface::height() const
436 {
437 	return clipping.h;
438 }
439 
setColorKey(Color c)440 void Surface::setColorKey(Color c)
441 {
442 	colorKey.r = red(c);
443 	colorKey.g = green(c);
444 	colorKey.b = blue(c);
445 	colorKey.unused = false;
446 }
447 
disableColorKey()448 void Surface::disableColorKey()
449 {
450 	colorKey.unused = true;
451 }
452 
fill(Color c,SDL_Rect * rect)453 void Surface::fill(Color c, SDL_Rect* rect)
454 {
455 	if (surface)
456 	{
457 		SDL_PixelFormat* format = surface->format;
458 		if (surface->refcount > 1)
459 		{
460 			surface->refcount--;
461 			surface = SDL_CreateRGBSurface(SDL_SWSURFACE, surface->w, surface->h, format->BitsPerPixel,
462 			                               format->Rmask, format->Gmask, format->Bmask, format->Amask);
463 		}
464 		SDL_FillRect(surface, rect, SDL_MapRGB(format, red(c), green(c), blue(c)));
465 	}
466 }
467 
fill(Color c,int x,int y,int w,int h)468 void Surface::fill(Color c, int x, int y, int w, int h)
469 {
470 	SDL_Rect rect;
471 	rect.x = x;
472 	rect.y = y;
473 	rect.w = w;
474 	rect.h = h;
475 	fill(c, &rect);
476 }
477 
setColor(int x,int y,Color c)478 void Surface::setColor(int x, int y, Color c)
479 {
480 	if (surface)
481 	{
482 		setPixel(x, y, SDL_MapRGB(surface->format, red(c), green(c), blue(c)));
483 	}
484 }
485 
getColor(int x,int y)486 Color Surface::getColor(int x, int y)
487 {
488 	// After we dropped support for bpp < 24 this function should work fine.
489 	Uint8 r = 0;
490 	Uint8 g = 0;
491 	Uint8 b = 0;
492 	if (surface)
493 	{
494 		SDL_GetRGB(getPixel(x, y), surface->format, &r, &g, &b);
495 	}
496 	return (r << 16) | (g << 8) | b;
497 }
498 
setPixel(int x,int y,Uint32 pixelValue)499 void Surface::setPixel(int x, int y, Uint32 pixelValue)
500 {
501 	if (surface)
502 	{
503 		::setPixel(surface, clipping.x + x, clipping.y + y, pixelValue);
504 	}
505 }
506 
getPixel(int x,int y) const507 Uint32 Surface::getPixel(int x, int y) const
508 {
509 	if (!surface)
510 	{
511 		return 0;
512 	}
513 	return ::getPixel(surface, clipping.x + x, clipping.y + y);
514 }
515