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