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