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