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