1 /*
2 * Copyright 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 "Scaler.h"
21 #include "Settings.h"
22 #include "DColor.h"
23 #include "SDL.h"
24 #include <algorithm>
25 #include <cassert>
26 #include <cmath>
27
Scaler()28 Scaler::Scaler()
29 : buffer(0)
30 {
31 }
32
~Scaler()33 Scaler::~Scaler()
34 {
35 SDL_FreeSurface(buffer);
36 }
37
update(int w,int h)38 void Scaler::update(int w, int h)
39 {
40 zoom = std::min(w / GAME_WIDTH, h / GAME_HEIGHT);
41 while (!isValidZoom(zoom))
42 {
43 --zoom;
44 assert(zoom > 0);
45 }
46 xZoom = w / GAME_WIDTH;
47 yZoom = h / GAME_HEIGHT;
48
49 stretch = Settings::graphics.stretch;
50
51 if (stretch)
52 {
53 screenArea.w = w;
54 screenArea.h = h;
55 if (Settings::graphics.keepAspectRatio)
56 {
57 int w_scale = w * GAME_HEIGHT;
58 int h_scale = h * GAME_WIDTH;
59 if (w_scale > h_scale)
60 {
61 screenArea.w = h_scale / GAME_HEIGHT;
62 xZoom = yZoom;
63 }
64 else
65 {
66 screenArea.h = w_scale / GAME_WIDTH;
67 yZoom = xZoom;
68 }
69 }
70
71 if (screenArea.w == zoom * GAME_WIDTH &&
72 screenArea.h == zoom * GAME_HEIGHT)
73 {
74 // Stretching is not needed so it can be disabled here.
75 // This makes the performance somewhat inconsistent for
76 // different sizes.
77 stretch = false;
78 }
79 }
80 else
81 {
82 screenArea.w = GAME_WIDTH * zoom;
83 screenArea.h = GAME_HEIGHT * zoom;
84 }
85 screenArea.x = (w - screenArea.w) / 2;
86 screenArea.y = (h - screenArea.h) / 2;
87
88 if (stretch && needStretchBuffer())
89 {
90 SDL_PixelFormat* fmt = SDL_GetVideoSurface()->format;
91 buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, zoom * GAME_WIDTH, zoom * GAME_HEIGHT, fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask);
92 }
93 else
94 {
95 SDL_FreeSurface(buffer);
96 buffer = 0;
97 }
98 }
99
isValidZoom(int zoom) const100 bool Scaler::isValidZoom(int zoom) const
101 {
102 return true;
103 }
104
isValidBpp(int bpp) const105 bool Scaler::isValidBpp(int bpp) const
106 {
107 return bpp == 24 || bpp == 32;
108 }
109
needStretchBuffer() const110 bool Scaler::needStretchBuffer() const
111 {
112 return true;
113 }
114
render(const Surface & surface,SDL_Rect * area)115 void Scaler::render(const Surface& surface, SDL_Rect* area)
116 {
117 SDL_Surface* dest = SDL_GetVideoSurface();
118 SDL_Surface* src = surface.surface;
119
120 assert(src != 0);
121 assert(dest != 0);
122 assert(src->format->BytesPerPixel == dest->format->BytesPerPixel);
123
124 SDL_Rect srcArea;
125 SDL_Rect dstArea;
126 if (!area)
127 {
128 srcArea.w = src->w;
129 srcArea.h = src->h;
130 srcArea.x = 0;
131 srcArea.y = 0;
132 area = &dstArea;
133 }
134 else
135 {
136 srcArea.w = area->w;
137 srcArea.h = area->h;
138 srcArea.x = area->x;
139 srcArea.y = area->y;
140 }
141
142 if (stretch)
143 {
144 renderStretch(srcArea, *area, src, dest);
145 }
146 else
147 {
148 renderZoom(srcArea, *area, src, dest);
149 }
150 }
151
152 template <typename S>
stretchBilinear(const SDL_Rect & dstArea,const SDL_Rect & screenArea,SDL_Surface * source,SDL_Surface * destination)153 void stretchBilinear(const SDL_Rect& dstArea, const SDL_Rect& screenArea, SDL_Surface* source, SDL_Surface* destination)
154 {
155 typedef typename S::PixelType PixelType;
156
157 int xStart = dstArea.x;
158 int yStart = dstArea.y;
159 int xEnd = dstArea.x + dstArea.w;
160 int yEnd = dstArea.y + dstArea.h;
161
162 PixelType* src = (PixelType*) source->pixels;
163
164 std::ptrdiff_t src_rowskip = source->pitch / sizeof(PixelType);
165 std::ptrdiff_t dst_rowskip = destination->pitch / sizeof(PixelType);
166
167 double srcStepX = static_cast<double>(source->w) / screenArea.w;
168 double srcStepY = static_cast<double>(source->h) / screenArea.h;
169
170 double srcY = (yStart - screenArea.y + 0.5) * srcStepY - 0.5;
171
172 for (int y = yStart; y < yEnd; ++y, srcY += srcStepY)
173 {
174 int y1 = std::max(0, static_cast<int>(srcY));
175 int y2 = std::min(source->h - 1, static_cast<int>(srcY + 1.0));
176
177 double yDistance1 = srcY - std::floor(srcY);
178 double yDistance2 = 1.0 - yDistance1;
179
180 double srcX = (xStart - screenArea.x + 0.5) * srcStepX - 0.5;
181
182 for (int x = xStart; x < xEnd; ++x, srcX += srcStepX)
183 {
184 int x1 = std::max(0, static_cast<int>(srcX));
185 int x2 = std::min(source->w - 1, static_cast<int>(srcX + 1.0));
186
187 PixelType* p1 = src + S::pixelSize * x1 + y1 * src_rowskip;
188 PixelType* p2 = src + S::pixelSize * x2 + y1 * src_rowskip;
189 PixelType* p3 = src + S::pixelSize * x1 + y2 * src_rowskip;
190 PixelType* p4 = src + S::pixelSize * x2 + y2 * src_rowskip;
191
192 DColor c1;
193 DColor c2;
194 DColor c3;
195 DColor c4;
196
197 S::readColors(p1, c1);
198 S::readColors(p2, c2);
199 S::readColors(p3, c3);
200 S::readColors(p4, c4);
201
202 double xDistance1 = srcX - std::floor(srcX);
203 double xDistance2 = 1.0 - xDistance1;
204
205 double ry1 = c1.r * yDistance2 + c3.r * yDistance1;
206 double ry2 = c2.r * yDistance2 + c4.r * yDistance1;
207 double gy1 = c1.g * yDistance2 + c3.g * yDistance1;
208 double gy2 = c2.g * yDistance2 + c4.g * yDistance1;
209 double by1 = c1.b * yDistance2 + c3.b * yDistance1;
210 double by2 = c2.b * yDistance2 + c4.b * yDistance1;
211
212 DColor c;
213 c.r = ry1 * xDistance2 + ry2 * xDistance1;
214 c.g = gy1 * xDistance2 + gy2 * xDistance1;
215 c.b = by1 * xDistance2 + by2 * xDistance1;
216
217 PixelType* dst = (PixelType*) destination->pixels + S::pixelSize * x + y * dst_rowskip;
218 S::writeColors(dst, c);
219 }
220 }
221 }
222
stretchArea(const SDL_Rect & srcArea,SDL_Rect & dstArea,int x,int y,SDL_Surface * source,SDL_Surface * destination)223 void Scaler::stretchArea(const SDL_Rect& srcArea, SDL_Rect& dstArea, int x, int y, SDL_Surface* source, SDL_Surface* destination)
224 {
225 assert(source);
226 assert(destination);
227
228 int bpp = destination->format->BytesPerPixel;
229
230 int dstPaddingX = (screenArea.w / source->w) + ((screenArea.w % source->w) != 0 ? 1 : 0);
231 int dstPaddingY = (screenArea.h / source->h) + ((screenArea.h % source->h) != 0 ? 1 : 0);
232
233 dstArea.x -= dstPaddingX;
234 dstArea.w += 2 * dstPaddingX;
235 if (dstArea.x < screenArea.x)
236 {
237 dstArea.w -= screenArea.x - dstArea.x;
238 dstArea.x = screenArea.x;
239 }
240 Uint16 mx = screenArea.x + screenArea.w - dstArea.x;
241 if (screenArea.x + dstArea.w > mx)
242 {
243 dstArea.w = mx;
244 }
245
246 dstArea.y -= dstPaddingY;
247 dstArea.h += 2 * dstPaddingY;
248 if (dstArea.y < screenArea.y)
249 {
250 dstArea.h -= screenArea.y - dstArea.y;
251 dstArea.y = screenArea.y;
252 }
253 Uint16 my = screenArea.y + screenArea.h - dstArea.y;
254 if (screenArea.y + dstArea.h > my)
255 {
256 dstArea.h = my;
257 }
258
259 switch (bpp)
260 {
261 case 3:
262 stretchBilinear<Scale24>(dstArea, screenArea, buffer, destination);
263 break;
264 case 4:
265 stretchBilinear<Scale32>(dstArea, screenArea, buffer, destination);
266 break;
267 }
268 }
269
expand(SDL_Rect & srcArea,SDL_Rect & dstArea,int pixels)270 void Scaler::expand(SDL_Rect& srcArea, SDL_Rect& dstArea, int pixels)
271 {
272 srcArea.x -= pixels;
273 srcArea.w += 2 * pixels;
274 if (srcArea.x < 0)
275 {
276 srcArea.w += srcArea.x;
277 srcArea.x = 0;
278 }
279 Uint16 mx = GAME_WIDTH - srcArea.x;
280 if (srcArea.w > mx)
281 {
282 srcArea.w = mx;
283 }
284
285 srcArea.y -= pixels;
286 srcArea.h += 2 * pixels;
287 if (srcArea.y < 0)
288 {
289 srcArea.h += srcArea.y;
290 srcArea.y = 0;
291 }
292 Uint16 my = GAME_HEIGHT - srcArea.y;
293 if (srcArea.h > my)
294 {
295 srcArea.h = my;
296 }
297
298 dstArea.x = src2dst_x(srcArea.x);
299 dstArea.y = src2dst_y(srcArea.y);
300 dstArea.w = src2dst_x(srcArea.x + srcArea.w) - dstArea.x;
301 dstArea.h = src2dst_y(srcArea.y + srcArea.h) - dstArea.y;
302 }
303