1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2019 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20 
21 #ifndef SURFACE_DRAWING_H
22 #define SURFACE_DRAWING_H
23 
24 #include <SDL.h>
25 
26 #include <cmath>
27 
28 #include "SDLPixelIterator.h"
29 #include "Polygon.h"
30 
31 namespace GemRB {
32 
33 static_assert(std::is_trivially_destructible<Point>::value, "Expected Point to be trivially destructable.");
34 
PointClipped(SDL_Surface * surf,const Point & p)35 inline bool PointClipped(SDL_Surface* surf, const Point& p)
36 {
37 	if (p.x < 0 || p.x >= surf->w) {
38 		return true;
39 	}
40 
41 	if (p.y < 0 || p.y >= surf->h) {
42 		return true;
43 	}
44 
45 	return false;
46 }
47 
48 #if SDL_VERSION_ATLEAST(1,3,0)
49 template<typename T>
RectFromRegion(T && rgn)50 inline const SDL_Rect& RectFromRegion(T&& rgn)
51 {
52 	return reinterpret_cast<const SDL_Rect&>(rgn);
53 }
54 #else
RectFromRegion(const Region & rgn)55 inline SDL_Rect RectFromRegion(const Region& rgn)
56 {
57 	SDL_Rect rect = {Sint16(rgn.x), Sint16(rgn.y), Uint16(rgn.w), Uint16(rgn.h)};
58 	return rect;
59 }
60 #endif
61 
62 template<SHADER SHADE = SHADER::NONE>
DrawPointSurface(SDL_Surface * dst,Point p,const Region & clip,const Color & color)63 void DrawPointSurface(SDL_Surface* dst, Point p, const Region& clip, const Color& color)
64 {
65 	assert(dst->format->BitsPerPixel == 32); // we could easily support others if we have to
66 
67 	p = Clamp(p, clip.Origin(), clip.Maximum());
68 	if (PointClipped(dst, p)) return;
69 
70 	Uint32* px = ((Uint32*)dst->pixels) + (p.y * dst->pitch/4) + p.x;
71 
72 	if (SHADE != SHADER::NONE) {
73 		Color dstc;
74 		SDL_GetRGB( *px, dst->format, &dstc.r, &dstc.g, &dstc.b );
75 		if (SHADE == SHADER::TINT) {
76 			ShaderTint(color, dstc);
77 		} else {
78 			ShaderBlend<false>(color, dstc);
79 		}
80 		*px = SDL_MapRGBA(dst->format, dstc.r, dstc.g, dstc.b, dstc.a);
81 	} else {
82 		*px = SDL_MapRGBA(dst->format, color.r, color.g, color.b, color.a);
83 	}
84 }
85 
86 template<SHADER SHADE = SHADER::NONE>
DrawPointsSurface(SDL_Surface * surface,const std::vector<Point> & points,const Region & clip,const Color & srcc)87 void DrawPointsSurface(SDL_Surface* surface, const std::vector<Point>& points, const Region& clip, const Color& srcc)
88 {
89 	SDL_PixelFormat* fmt = surface->format;
90 	SDL_LockSurface( surface );
91 
92 	std::vector<Point>::const_iterator it;
93 	it = points.begin();
94 	for (; it != points.end(); ++it) {
95 		Point p = *it;
96 		if (!clip.PointInside(p) || PointClipped(surface, p)) continue;
97 
98 		unsigned char* start = static_cast<unsigned char*>(surface->pixels);
99 		unsigned char* dst = start + ((p.y * surface->pitch) + (p.x * fmt->BytesPerPixel));
100 
101 		Color dstc;
102 		switch (fmt->BytesPerPixel) {
103 			case 1:
104 				if (SHADE != SHADER::NONE) {
105 					SDL_GetRGB( *dst, surface->format, &dstc.r, &dstc.g, &dstc.b );
106 					if (SHADE == SHADER::TINT) {
107 						ShaderTint(srcc, dstc);
108 					} else {
109 						ShaderBlend<false>(srcc, dstc);
110 					}
111 					*dst = SDL_MapRGB(surface->format, dstc.r, dstc.g, dstc.b);
112 				} else {
113 					*dst = SDL_MapRGB(surface->format, srcc.r, srcc.g, srcc.b);
114 				}
115 				break;
116 			case 2:
117 				if (SHADE != SHADER::NONE) {
118 					SDL_GetRGB( *reinterpret_cast<Uint16*>(dst), surface->format, &dstc.r, &dstc.g, &dstc.b );
119 					if (SHADE == SHADER::TINT) {
120 						ShaderTint(srcc, dstc);
121 					} else {
122 						ShaderBlend<false>(srcc, dstc);
123 					}
124 					*reinterpret_cast<Uint16*>(dst) = SDL_MapRGB(surface->format, dstc.r, dstc.g, dstc.b);
125 				} else {
126 					*reinterpret_cast<Uint16*>(dst) = SDL_MapRGB(surface->format, srcc.r, srcc.g, srcc.b);
127 				}
128 				break;
129 			case 3:
130 			{
131 				// FIXME: implement alpha blending for this... or nix it
132 				// is this even used?
133 				/*
134 				Uint32 val = SDL_MapRGB(surface->format, srcc.r, srcc.g, srcc.b);
135 	#if SDL_BYTEORDER == SDL_LIL_ENDIAN
136 				pixel[0] = val & 0xff;
137 				pixel[1] = (val >> 8) & 0xff;
138 				pixel[2] = (val >> 16) & 0xff;
139 	#else
140 				pixel[2] = val & 0xff;
141 				pixel[1] = (val >> 8) & 0xff;
142 				pixel[0] = (val >> 16) & 0xff;
143 	#endif
144 				*/
145 			}
146 				break;
147 			case 4:
148 				if (SHADE != SHADER::NONE) {
149 					SDL_GetRGB( *reinterpret_cast<Uint32*>(dst), surface->format, &dstc.r, &dstc.g, &dstc.b );
150 					if (SHADE == SHADER::TINT) {
151 						ShaderTint(srcc, dstc);
152 					} else {
153 						ShaderBlend<false>(srcc, dstc);
154 					}
155 					*reinterpret_cast<Uint32*>(dst) = SDL_MapRGB(surface->format, dstc.r, dstc.g, dstc.b);
156 				} else {
157 					*reinterpret_cast<Uint32*>(dst) = SDL_MapRGB(surface->format, srcc.r, srcc.g, srcc.b);
158 				}
159 				break;
160 			default:
161 				Log(ERROR, "sprite_t", "Working with unknown pixel format: %s", SDL_GetError());
162 				break;
163 		}
164 	}
165 
166 	SDL_UnlockSurface( surface );
167 }
168 
169 template<SHADER SHADE = SHADER::NONE>
DrawHLineSurface(SDL_Surface * dst,Point p,short x2,const Region & clip,const Color & color)170 void DrawHLineSurface(SDL_Surface* dst, Point p, short x2, const Region& clip, const Color& color)
171 {
172 	assert(clip.x >= 0 && clip.w <= dst->w);
173 	assert(clip.y >= 0 && clip.h <= dst->h);
174 	assert(dst->format->BitsPerPixel == 32); // we could easily support others if we have to, but this is optimized for our needs
175 
176 	if (p.y < clip.y || p.y >= clip.y + clip.h) {
177 		return;
178 	}
179 
180 	if (x2 < p.x) {
181 		std::swap(x2, p.x);
182 	}
183 
184 	if (p.x >= clip.x + clip.w) return;
185 	if (x2 < clip.x) return;
186 
187 	if (p.x < clip.x) p.x = clip.x;
188 	x2 = Clamp<int>(x2, p.x, clip.x + clip.w);
189 
190 	if (p.x == x2)
191 		return DrawPointSurface<SHADE>(dst, p, clip, color);
192 
193 	assert(p.x < x2);
194 	if (p.y >= dst->h || p.x >= dst->w) {
195 		// when we are drawing stencils it is possible to get a dest that is smaller than the clip
196 		return;
197 	}
198 
199 	if (SHADE != SHADER::NONE) {
200 		Region r = Region::RegionFromPoints(p, Point(x2, p.y));
201 		r.h = 1;
202 		SDLPixelIterator dstit(dst, RectFromRegion(r.Intersect(clip)));
203 		SDLPixelIterator dstend = SDLPixelIterator::end(dstit);
204 
205 		if (SHADE == SHADER::TINT) {
206 			const static TintDst<false> blender;
207 			ColorFill(color, dstit, dstend, blender);
208 		} else {
209 			const static OneMinusSrcA<false, false> blender;
210 			ColorFill(color, dstit, dstend, blender);
211 		}
212 	} else {
213 		Uint32* px = ((Uint32*)dst->pixels) + (p.y * dst->pitch/4) + p.x;
214 		Uint32 c = SDL_MapRGBA(dst->format, color.r, color.g, color.b, color.a);
215 
216 		int numPx = std::min(x2 - p.x, dst->w - p.x);
217 		std::fill(px, px + numPx, c);
218 	}
219 }
220 
221 template<SHADER SHADE = SHADER::NONE>
DrawVLineSurface(SDL_Surface * dst,Point p,short y2,const Region & clip,const Color & color)222 inline void DrawVLineSurface(SDL_Surface* dst, Point p, short y2, const Region& clip, const Color& color)
223 {
224 	assert(clip.x >= 0 && clip.w <= dst->w);
225 	assert(clip.y >= 0 && clip.h <= dst->h);
226 
227 	if (p.x < clip.x || p.x >= clip.x + clip.w) {
228 		return;
229 	}
230 
231 	if (y2 < p.y) {
232 		std::swap(y2, p.y);
233 	}
234 
235 	if (p.y >= clip.y + clip.h) return;
236 	if (y2 < clip.y) return;
237 
238 	if (p.y < clip.y) p.y = clip.y;
239 	y2 = Clamp<int>(y2, p.y, clip.y + clip.h);
240 
241 	if (p.y == y2)
242 		return DrawPointSurface<SHADE>(dst, p, clip, color);
243 
244 	Region r = Region::RegionFromPoints(p, Point(p.x, y2));
245 	r.w = 1;
246 	SDLPixelIterator dstit(dst, RectFromRegion(r.Intersect(clip)));
247 	SDLPixelIterator dstend = SDLPixelIterator::end(dstit);
248 
249 	if (SHADE == SHADER::BLEND) {
250 		const static OneMinusSrcA<false, false> blender;
251 		ColorFill(color, dstit, dstend, blender);
252 	} else if (SHADE == SHADER::TINT) {
253 		const static TintDst<false> blender;
254 		ColorFill(color, dstit, dstend, blender);
255 	} else {
256 		const static SrcRGBA<false> blender;
257 		ColorFill(color, dstit, dstend, blender);
258 	}
259 }
260 
261 template<SHADER SHADE = SHADER::NONE>
DrawLineSurface(SDL_Surface * surface,const Point & start,const Point & end,const Region & clip,const Color & color)262 void DrawLineSurface(SDL_Surface* surface, const Point& start, const Point& end, const Region& clip, const Color& color)
263 {
264 	if (start.y == end.y) return DrawHLineSurface<SHADE>(surface, start, end.x, clip, color);
265 	if (start.x == end.x) return DrawVLineSurface<SHADE>(surface, start, end.y, clip, color);
266 
267 	assert(clip.x >= 0 && clip.w <= surface->w);
268 	assert(clip.y >= 0 && clip.h <= surface->h);
269 
270 	Point p1 = start;
271 	Point p2 = end;
272 
273 	bool yLonger = false;
274 	int shortLen = p2.y - p1.y;
275 	int longLen = p2.x - p1.x;
276 	if (abs( shortLen ) > abs( longLen )) {
277 		std::swap(shortLen, longLen);
278 		yLonger = true;
279 	}
280 
281 	int decInc;
282 	if (longLen == 0) {
283 		decInc = 0;
284 	} else {
285 		decInc = ( shortLen * 65536 ) / longLen;
286 	}
287 
288 	// the numper of "points" is the length of the hypotenuse
289 	// however, since we are only aproximating a straight line (because pixels)
290 	// and sqrts are expensive and mallocs larger mallocs arent more expensive than smaller ones
291 	// we will just overestimate by reserving shortLen + longLen Points
292 	// we continually recycle this vector
293 	// this prevents constant allocation/deallocation
294 	// while drawing. Yes, its permanantly used memory, but we do
295 	// enough drawing that this is not a problem (its a tiny amount anyway)
296 	// Point is trivial and clear() should be constant
297 	static std::vector<Point> s_points;
298 	s_points.clear();
299 	s_points.reserve(std::abs(longLen) + std::abs(shortLen));
300 	Point newp;
301 
302 	do { // TODO: rewrite without loop
303 		if (yLonger) {
304 			if (longLen > 0) {
305 				longLen += p1.y;
306 				for (int j = 0x8000 + ( p1.x << 16 ); p1.y <= longLen; ++p1.y) {
307 					newp = Point( j >> 16, p1.y );
308 					if (clip.PointInside(newp))
309 						s_points.push_back(newp);
310 					j += decInc;
311 				}
312 				break;
313 			}
314 			longLen += p1.y;
315 			for (int j = 0x8000 + ( p1.x << 16 ); p1.y >= longLen; --p1.y) {
316 				newp = Point( j >> 16, p1.y );
317 				if (clip.PointInside(newp))
318 					s_points.push_back(newp);
319 				j -= decInc;
320 			}
321 			break;
322 		}
323 
324 		if (longLen > 0) {
325 			longLen += p1.x;
326 			for (int j = 0x8000 + ( p1.y << 16 ); p1.x <= longLen; ++p1.x) {
327 				newp = Point( p1.x, j >> 16 );
328 				if (clip.PointInside(newp))
329 					s_points.push_back(newp);
330 				j += decInc;
331 			}
332 			break;
333 		}
334 		longLen += p1.x;
335 		for (int j = 0x8000 + ( p1.y << 16 ); p1.x >= longLen; --p1.x) {
336 			newp = Point( p1.x, j >> 16 );
337 			if (clip.PointInside(newp))
338 				s_points.push_back(newp);
339 			j -= decInc;
340 		}
341 	} while (false);
342 
343 	DrawPointsSurface<SHADE>(surface, s_points, clip, color);
344 }
345 
346 template<SHADER SHADE = SHADER::NONE>
DrawLinesSurface(SDL_Surface * surface,const std::vector<Point> & points,const Region & clip,const Color & color)347 void DrawLinesSurface(SDL_Surface* surface, const std::vector<Point>& points, const Region& clip, const Color& color)
348 {
349 	size_t count = points.size();
350 	assert(count % 2 == 0);
351 	for (size_t i = 0; i < count; i+=2)
352 	{
353 		DrawLineSurface<SHADE>(surface, points[i], points[i+1], clip, color);
354 	}
355 }
356 
357 template<SHADER SHADE = SHADER::NONE>
DrawPolygonSurface(SDL_Surface * surface,const Gem_Polygon * poly,const Point & origin,const Region & clip,const Color & color,bool fill)358 void DrawPolygonSurface(SDL_Surface* surface, const Gem_Polygon* poly, const Point& origin, const Region& clip, const Color& color, bool fill)
359 {
360 	if (fill) {
361 		for (const auto& lineSegments : poly->rasterData)
362 		{
363 			for (const auto& segment : lineSegments) {
364 				DrawHLineSurface<SHADE>(surface, segment.first + origin, (segment.second + origin).x, clip, color);
365 			}
366 		}
367 	} else {
368 		// we continually recycle this vector
369 		// this prevents constant allocation/deallocation
370 		// while drawing. Yes, its permanantly used memory, but we do
371 		// enough drawing that this is not a problem (its a tiny amount anyway)
372 		// Point is trivial and clear() should be constant
373 		static std::vector<Point> s_points;
374 		s_points.clear();
375 		s_points.resize(poly->Count()*2); // resize, not reserve! (it wont shrink the capacity FYI)
376 
377 		const Point& p = poly->vertices[0] - poly->BBox.Origin() + origin;
378 		s_points[0].x = p.x;
379 		s_points[0].y = p.y;
380 
381 		size_t j = 1;
382 		for (size_t i = 1; i < poly->Count(); ++i, j+=2) {
383 			// this is not a typo. one point ends the previous line, the next begins the next line
384 			const Point& p = poly->vertices[i] - poly->BBox.Origin() + origin;
385 			s_points[j].x = p.x;
386 			s_points[j].y = p.y;
387 			s_points[j+1] = s_points[j];
388 		}
389 		// reconnect with start point
390 		s_points[j].x = p.x;
391 		s_points[j].y = p.y;
392 
393 		DrawLinesSurface<SHADE>(surface, s_points, clip, color);
394 	}
395 }
396 
397 }
398 
399 #endif /* SURFACE_DRAWING_H */
400