1 /*
2  * Copyright (C) 2002-2020 by the Widelands Development Team
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 #include "graphic/rendertarget.h"
21 
22 #include "graphic/align.h"
23 #include "graphic/animation/animation.h"
24 #include "graphic/animation/animation_manager.h"
25 #include "graphic/graphic.h"
26 #include "graphic/surface.h"
27 
28 /**
29  * Build a render target for the given surface.
30  */
RenderTarget(Surface * surf)31 RenderTarget::RenderTarget(Surface* surf) : surface_(surf) {
32 	reset();
33 }
34 
35 /**
36  * Sets an arbitrary drawing window.
37  */
set_window(const Recti & rc,const Vector2i & ofs)38 void RenderTarget::set_window(const Recti& rc, const Vector2i& ofs) {
39 	rect_ = rc;
40 	offset_ = ofs;
41 
42 	// safeguards clipping against the bitmap itself
43 
44 	if (rect_.x < 0) {
45 		offset_.x += rect_.x;
46 		rect_.w = std::max<int32_t>(rect_.w + rect_.x, 0);
47 		rect_.x = 0;
48 	}
49 
50 	if (rect_.x + rect_.w > surface_->width()) {
51 		rect_.w = std::max<int32_t>(surface_->width() - rect_.x, 0);
52 	}
53 
54 	if (rect_.y < 0) {
55 		offset_.y += rect_.y;
56 		rect_.h = std::max<int32_t>(rect_.h + rect_.y, 0);
57 		rect_.y = 0;
58 	}
59 
60 	if (rect_.y + rect_.h > surface_->height()) {
61 		rect_.h = std::max<int32_t>(surface_->height() - rect_.y, 0);
62 	}
63 }
64 
65 /**
66  * Builds a subwindow. rc is relative to the current drawing window. The
67  * subwindow will be clipped appropriately.
68  *
69  * The previous window state is returned in previous and prevofs.
70  *
71  * Returns false if the subwindow is invisible. In that case, the window state
72  * is not changed at all. Otherwise, the function returns true.
73  */
enter_window(const Recti & rc,Recti * previous,Vector2i * prevofs)74 bool RenderTarget::enter_window(const Recti& rc, Recti* previous, Vector2i* prevofs) {
75 	Rectf newrect_f = rc.cast<float>();
76 	if (clip(newrect_f)) {
77 		if (previous) {
78 			*previous = rect_;
79 		}
80 		if (prevofs) {
81 			*prevofs = offset_;
82 		}
83 
84 		const Recti newrect = newrect_f.cast<int>();
85 		// Apply the changes
86 		offset_ = rc.origin() - (newrect.origin() - rect_.origin() - offset_);
87 		rect_ = newrect;
88 
89 		return true;
90 	} else {
91 		return false;
92 	}
93 }
94 
95 /**
96  * Returns the true size of the render target (ignoring the window settings).
97  */
width() const98 int32_t RenderTarget::width() const {
99 	return surface_->width();
100 }
101 
102 /**
103  * Returns the true size of the render target (ignoring the window settings).
104  */
height() const105 int32_t RenderTarget::height() const {
106 	return surface_->height();
107 }
108 
109 /**
110  * This functions draws a line in the target
111  */
draw_line_strip(const std::vector<Vector2f> & points,const RGBColor & color,float line_width)112 void RenderTarget::draw_line_strip(const std::vector<Vector2f>& points,
113                                    const RGBColor& color,
114                                    float line_width) {
115 	std::vector<Vector2f> adjusted_points;
116 	adjusted_points.reserve(points.size());
117 	for (const auto& p : points) {
118 		adjusted_points.emplace_back(p.x + offset_.x + rect_.x, p.y + offset_.y + rect_.y);
119 	}
120 	surface_->draw_line_strip(adjusted_points, color, line_width);
121 }
122 
123 /**
124  * Clip against window and pass those primitives along to the bitmap.
125  */
draw_rect(const Recti & rect,const RGBColor & clr)126 void RenderTarget::draw_rect(const Recti& rect, const RGBColor& clr) {
127 	Rectf r(rect.cast<float>());
128 	if (clip(r)) {
129 		::draw_rect(r, clr, surface_);
130 	}
131 }
132 
fill_rect(const Recti & rect,const RGBAColor & clr,BlendMode blend_mode)133 void RenderTarget::fill_rect(const Recti& rect, const RGBAColor& clr, BlendMode blend_mode) {
134 	Rectf r(rect.cast<float>());
135 	if (clip(r)) {
136 		surface_->fill_rect(r, clr, blend_mode);
137 	}
138 }
139 
brighten_rect(const Recti & rect,int32_t factor)140 void RenderTarget::brighten_rect(const Recti& rect, int32_t factor) {
141 	Rectf r(rect.cast<float>());
142 	if (clip(r)) {
143 		surface_->brighten_rect(r, factor);
144 	}
145 }
146 
147 /**
148  * Blits a Image on another Surface
149  *
150  * This blit function copies the pixels to the destination surface.
151  */
blit(const Vector2i & dst,const Image * image,BlendMode blend_mode,UI::Align align)152 void RenderTarget::blit(const Vector2i& dst,
153                         const Image* image,
154                         BlendMode blend_mode,
155                         UI::Align align) {
156 	assert(image != nullptr);
157 	Vector2i destination_point(dst);
158 	UI::correct_for_align(align, image->width(), &destination_point);
159 
160 	Rectf source_rect(0.f, 0.f, image->width(), image->height());
161 	Rectf destination_rect(destination_point.x, destination_point.y, source_rect.w, source_rect.h);
162 
163 	if (to_surface_geometry(&destination_rect, &source_rect)) {
164 		// I seem to remember seeing 1. a lot in blitting calls.
165 		constexpr float kFullyOpaque = 1.f;
166 		surface_->blit(destination_rect, *image, source_rect, kFullyOpaque, blend_mode);
167 	}
168 }
169 
blit_monochrome(const Vector2i & dst,const Image * image,const RGBAColor & blend_mode,UI::Align align)170 void RenderTarget::blit_monochrome(const Vector2i& dst,
171                                    const Image* image,
172                                    const RGBAColor& blend_mode,
173                                    UI::Align align) {
174 	Vector2i destination_point(dst);
175 	UI::correct_for_align(align, image->width(), &destination_point);
176 
177 	Rectf source_rect(0.f, 0.f, image->width(), image->height());
178 	Rectf destination_rect(destination_point.x, destination_point.y, source_rect.w, source_rect.h);
179 
180 	if (to_surface_geometry(&destination_rect, &source_rect)) {
181 		surface_->blit_monochrome(destination_rect, *image, source_rect, blend_mode);
182 	}
183 }
184 
185 /**
186  * Like \ref blit, but use only a sub-rectangle of the source image.
187  */
blitrect(const Vector2i & dst,const Image * image,const Recti & gsrcrc,BlendMode blend_mode)188 void RenderTarget::blitrect(const Vector2i& dst,
189                             const Image* image,
190                             const Recti& gsrcrc,
191                             BlendMode blend_mode) {
192 	assert(0 <= gsrcrc.x);
193 	assert(0 <= gsrcrc.y);
194 
195 	// We want to use the given srcrc, but we must make sure that we are not
196 	// blitting outside of the boundaries of 'image'.
197 	Rectf source_rect(gsrcrc.x, gsrcrc.y, std::min<int32_t>(image->width() - gsrcrc.x, gsrcrc.w),
198 	                  std::min<int32_t>(image->height() - gsrcrc.y, gsrcrc.h));
199 	Rectf destination_rect(dst.x, dst.y, source_rect.w, source_rect.h);
200 
201 	if (to_surface_geometry(&destination_rect, &source_rect)) {
202 		surface_->blit(destination_rect, *image, source_rect, 1., blend_mode);
203 	}
204 }
205 
blitrect_scale(Rectf destination_rect,const Image * image,Recti source_rect_i,const float opacity,const BlendMode blend_mode)206 void RenderTarget::blitrect_scale(Rectf destination_rect,
207                                   const Image* image,
208                                   Recti source_rect_i,
209                                   const float opacity,
210                                   const BlendMode blend_mode) {
211 	Rectf source_rect = source_rect_i.cast<float>();
212 	if (to_surface_geometry(&destination_rect, &source_rect)) {
213 		surface_->blit(destination_rect, *image, source_rect, opacity, blend_mode);
214 	}
215 }
216 
blitrect_scale_monochrome(Rectf destination_rect,const Image * image,Recti source_rect_i,const RGBAColor & blend)217 void RenderTarget::blitrect_scale_monochrome(Rectf destination_rect,
218                                              const Image* image,
219                                              Recti source_rect_i,
220                                              const RGBAColor& blend) {
221 	Rectf source_rect = source_rect_i.cast<float>();
222 	if (to_surface_geometry(&destination_rect, &source_rect)) {
223 		surface_->blit_monochrome(destination_rect, *image, source_rect, blend);
224 	}
225 }
226 
227 /**
228  * Fill the given rectangle with the given image.
229  *
230  * The pixel from ofs inside image is placed at the top-left corner of
231  * the filled rectangle.
232  */
tile(const Recti & rect,const Image * image,const Vector2i & gofs,BlendMode blend_mode)233 void RenderTarget::tile(const Recti& rect,
234                         const Image* image,
235                         const Vector2i& gofs,
236                         BlendMode blend_mode) {
237 	int32_t srcw = image->width();
238 	int32_t srch = image->height();
239 
240 	Rectf r = rect.cast<float>();
241 	Vector2i ofs(gofs);
242 	if (clip(r)) {
243 		if (offset_.x < 0) {
244 			ofs.x -= offset_.x;
245 		}
246 
247 		if (offset_.y < 0) {
248 			ofs.y -= offset_.y;
249 		}
250 
251 		// Make sure the offset is within bounds
252 		ofs.x = ofs.x % srcw;
253 
254 		if (ofs.x < 0) {
255 			ofs.x += srcw;
256 		}
257 
258 		ofs.y = ofs.y % srch;
259 
260 		if (ofs.y < 0) {
261 			ofs.y += srch;
262 		}
263 
264 		// Blit the image into the rectangle
265 		int ty = 0;
266 
267 		while (ty < r.h) {
268 			int tx = 0;
269 			int32_t tofsx = ofs.x;
270 			Rectf srcrc;
271 
272 			srcrc.y = ofs.y;
273 			srcrc.h = srch - ofs.y;
274 
275 			if (ty + srcrc.h > r.h) {
276 				srcrc.h = r.h - ty;
277 			}
278 
279 			while (tx < r.w) {
280 				srcrc.x = tofsx;
281 				srcrc.w = srcw - tofsx;
282 
283 				if (tx + srcrc.w > r.w) {
284 					srcrc.w = r.w - tx;
285 				}
286 
287 				const Rectf dst_rect(r.x + tx, r.y + ty, srcrc.w, srcrc.h);
288 				surface_->blit(dst_rect, *image, srcrc, 1., blend_mode);
289 
290 				tx += srcrc.w;
291 
292 				tofsx = 0;
293 			}
294 
295 			ty += srcrc.h;
296 			ofs.y = 0;
297 		}
298 	}
299 }
300 
blit_animation(const Vector2f & dst,const Widelands::Coords & coords,const float scale,uint32_t animation_id,uint32_t time,const RGBColor * player_color,const int percent_from_bottom)301 void RenderTarget::blit_animation(const Vector2f& dst,
302                                   const Widelands::Coords& coords,
303                                   const float scale,
304                                   uint32_t animation_id,
305                                   uint32_t time,
306                                   const RGBColor* player_color,
307                                   const int percent_from_bottom) {
308 	const Animation& animation = g_gr->animations().get_animation(animation_id);
309 	assert(percent_from_bottom <= 100);
310 	if (percent_from_bottom > 0) {
311 		// Scaling for zoom and animation image size, then fit screen edges.
312 		Rectf srcrc = animation.source_rectangle(percent_from_bottom, scale);
313 		Rectf dstrc = animation.destination_rectangle(dst, srcrc, scale);
314 		if (to_surface_geometry(&dstrc, &srcrc)) {
315 			animation.blit(time, coords, srcrc, dstrc, player_color, surface_, scale);
316 		}
317 	}
318 }
319 
320 /**
321  * Called every time before the render target is handed out by the Graphic
322  * implementation to start in a neutral state.
323  */
reset()324 void RenderTarget::reset() {
325 	rect_.x = rect_.y = 0;
326 	rect_.w = surface_->width();
327 	rect_.h = surface_->height();
328 
329 	offset_.x = offset_.y = 0;
330 }
331 
332 /**
333  * Offsets r by offset_ and clips r against rect_.
334  *
335  * If true is returned, r a valid rectangle that can be used.
336  * If false is returned, r may not be used and may be partially modified.
337  */
clip(Rectf & r) const338 bool RenderTarget::clip(Rectf& r) const {
339 	r.x += offset_.x;
340 	r.y += offset_.y;
341 
342 	if (r.x < 0) {
343 		if (r.w <= -r.x) {
344 			return false;
345 		}
346 
347 		r.w += r.x;
348 
349 		r.x = 0;
350 	}
351 
352 	if (r.x + r.w > rect_.w) {
353 		if (rect_.w <= r.x) {
354 			return false;
355 		}
356 		r.w = rect_.w - r.x;
357 	}
358 
359 	if (r.y < 0) {
360 		if (r.h <= -r.y) {
361 			return false;
362 		}
363 		r.h += r.y;
364 		r.y = 0;
365 	}
366 
367 	if (r.y + r.h > rect_.h) {
368 		if (rect_.h <= r.y) {
369 			return false;
370 		}
371 		r.h = rect_.h - r.y;
372 	}
373 
374 	r.x += rect_.x;
375 	r.y += rect_.y;
376 
377 	return r.w && r.h;
378 }
379 
380 /**
381  * Clip against window and source bitmap, returns false if blitting is
382  * unnecessary because image is not inside the target surface.
383  */
to_surface_geometry(Rectf * destination_rect,Rectf * source_rect) const384 bool RenderTarget::to_surface_geometry(Rectf* destination_rect, Rectf* source_rect) const {
385 	assert(0 <= source_rect->x);
386 	assert(0 <= source_rect->y);
387 	destination_rect->x += offset_.x;
388 	destination_rect->y += offset_.y;
389 
390 	// We have to clip the target rect against our own drawing area. If we make
391 	// changes to any side of our rectangle, we have to change the source rect
392 	// too. But since the source_rectangle might have a different size than the
393 	// destination_rect, we do this by making the proportional change.
394 
395 	// Clipping, from the left.
396 	if (destination_rect->x < 0.f) {
397 		if (destination_rect->w <= -destination_rect->x) {
398 			return false;
399 		}
400 		const float source_rect_pixel_change =
401 		   -destination_rect->x / destination_rect->w * source_rect->w;
402 		source_rect->x += source_rect_pixel_change;
403 		source_rect->w -= source_rect_pixel_change;
404 		destination_rect->w += destination_rect->x;
405 		destination_rect->x = 0.f;
406 	}
407 
408 	// Clipping, from the right.
409 	if (destination_rect->x + destination_rect->w > rect_.w) {
410 		if (rect_.w <= destination_rect->x) {
411 			return false;
412 		}
413 		const float new_destination_w = rect_.w - destination_rect->x;
414 		source_rect->w = new_destination_w / destination_rect->w * source_rect->w;
415 		destination_rect->w = new_destination_w;
416 	}
417 
418 	// Clipping, from the top.
419 	if (destination_rect->y < 0.f) {
420 		if (destination_rect->h <= -destination_rect->y) {
421 			return false;
422 		}
423 		const float source_rect_pixel_change =
424 		   -destination_rect->y / destination_rect->h * source_rect->h;
425 		source_rect->y += source_rect_pixel_change;
426 		source_rect->h -= source_rect_pixel_change;
427 		destination_rect->h += destination_rect->y;
428 		destination_rect->y = 0.f;
429 	}
430 
431 	// Clipping, from the bottom.
432 	if (destination_rect->y + destination_rect->h > rect_.h) {
433 		if (rect_.h <= destination_rect->y) {
434 			return false;
435 		}
436 		const float new_destination_h = rect_.h - destination_rect->y;
437 		source_rect->h = new_destination_h / destination_rect->h * source_rect->h;
438 		destination_rect->h = new_destination_h;
439 	}
440 	destination_rect->x += rect_.x;
441 	destination_rect->y += rect_.y;
442 	return true;
443 }
444