1 #include "graphics.h"
2 
3 #include "city/view.h"
4 #include "core/config.h"
5 #include "graphics/color.h"
6 #include "graphics/menu.h"
7 #include "game/system.h"
8 #include "graphics/screen.h"
9 
10 #include <stdlib.h>
11 #include <string.h>
12 
13 static struct {
14     color_t *pixels;
15     int width;
16     int height;
17 } canvas[MAX_CANVAS];
18 
19 static struct {
20     int x_start;
21     int x_end;
22     int y_start;
23     int y_end;
24 } clip_rectangle = { 0, 800, 0, 600 };
25 
26 static struct {
27     int x;
28     int y;
29 } translation;
30 
31 static struct {
32     struct {
33         int x_start;
34         int x_end;
35         int y_start;
36         int y_end;
37     } clip_rectangle;
38     canvas_type type;
39 } original_canvas;
40 
41 static clip_info clip;
42 static canvas_type active_canvas;
43 
graphics_init_canvas(int width,int height)44 void graphics_init_canvas(int width, int height)
45 {
46     canvas[CANVAS_UI].pixels = system_create_ui_framebuffer(width, height);
47     if (config_get(CONFIG_UI_ZOOM)) {
48         canvas[CANVAS_CITY].pixels = system_create_city_framebuffer(width, height);
49         city_view_set_max_scale(system_get_max_zoom(width, height));
50     } else {
51         system_release_city_framebuffer();
52         canvas[CANVAS_CITY].pixels = 0;
53     }
54     canvas[CANVAS_UI].width = width;
55     canvas[CANVAS_UI].height = height;
56     canvas[CANVAS_CITY].width = width * 2;
57     canvas[CANVAS_CITY].height = height * 2;
58 
59     graphics_clear_screens();
60     graphics_set_clip_rectangle(0, 0, width, height);
61 }
62 
graphics_canvas(canvas_type type)63 const void *graphics_canvas(canvas_type type)
64 {
65     if (!config_get(CONFIG_UI_ZOOM)) {
66         return canvas[CANVAS_UI].pixels;
67     }
68     return canvas[type].pixels;
69 }
70 
graphics_set_active_canvas(canvas_type type)71 void graphics_set_active_canvas(canvas_type type)
72 {
73     active_canvas = type;
74     graphics_reset_clip_rectangle();
75 }
76 
graphics_set_custom_canvas(color_t * pixels,int width,int height)77 void graphics_set_custom_canvas(color_t *pixels, int width, int height)
78 {
79     canvas[CANVAS_CUSTOM].pixels = pixels;
80     canvas[CANVAS_CUSTOM].width = width;
81     canvas[CANVAS_CUSTOM].height = height;
82     if (active_canvas != CANVAS_CUSTOM) {
83         original_canvas.type = active_canvas;
84         original_canvas.clip_rectangle.x_start = clip_rectangle.x_start;
85         original_canvas.clip_rectangle.x_end = clip_rectangle.x_end;
86         original_canvas.clip_rectangle.y_start = clip_rectangle.y_start;
87         original_canvas.clip_rectangle.y_end = clip_rectangle.y_end;
88     }
89     graphics_set_active_canvas(CANVAS_CUSTOM);
90 }
91 
graphics_restore_original_canvas(void)92 void graphics_restore_original_canvas(void)
93 {
94     if (active_canvas != CANVAS_CUSTOM) {
95         return;
96     }
97     canvas[CANVAS_CUSTOM].pixels = 0;
98     canvas[CANVAS_CUSTOM].width = 0;
99     canvas[CANVAS_CUSTOM].height = 0;
100     active_canvas = original_canvas.type;
101     clip_rectangle.x_start = original_canvas.clip_rectangle.x_start;
102     clip_rectangle.x_end = original_canvas.clip_rectangle.x_end;
103     clip_rectangle.y_start = original_canvas.clip_rectangle.y_start;
104     clip_rectangle.y_end = original_canvas.clip_rectangle.y_end;
105 }
106 
graphics_get_canvas_type(void)107 canvas_type graphics_get_canvas_type(void)
108 {
109     return active_canvas;
110 }
111 
translate_clip(int dx,int dy)112 static void translate_clip(int dx, int dy)
113 {
114     clip_rectangle.x_start -= dx;
115     clip_rectangle.x_end -= dx;
116     clip_rectangle.y_start -= dy;
117     clip_rectangle.y_end -= dy;
118 }
119 
set_translation(int x,int y)120 static void set_translation(int x, int y)
121 {
122     int dx = x - translation.x;
123     int dy = y - translation.y;
124     translation.x = x;
125     translation.y = y;
126     translate_clip(dx, dy);
127 }
128 
graphics_in_dialog(void)129 void graphics_in_dialog(void)
130 {
131     set_translation(screen_dialog_offset_x(), screen_dialog_offset_y());
132 }
133 
graphics_in_dialog_with_size(int width,int height)134 void graphics_in_dialog_with_size(int width, int height)
135 {
136     set_translation((screen_width() - width) / 2, (screen_height() - height) / 2);
137 }
138 
graphics_reset_dialog(void)139 void graphics_reset_dialog(void)
140 {
141     set_translation(0, 0);
142 }
143 
graphics_set_clip_rectangle(int x,int y,int width,int height)144 void graphics_set_clip_rectangle(int x, int y, int width, int height)
145 {
146     clip_rectangle.x_start = x;
147     clip_rectangle.x_end = x + width;
148     clip_rectangle.y_start = y;
149     clip_rectangle.y_end = y + height;
150     // fix clip rectangle going over the edges of the screen
151     if (translation.x + clip_rectangle.x_start < 0) {
152         clip_rectangle.x_start = -translation.x;
153     }
154     if (translation.y + clip_rectangle.y_start < 0) {
155         clip_rectangle.y_start = -translation.y;
156     }
157     if (translation.x + clip_rectangle.x_end > canvas[active_canvas].width) {
158         clip_rectangle.x_end = canvas[active_canvas].width - translation.x;
159     }
160     if (translation.y + clip_rectangle.y_end > canvas[active_canvas].height) {
161         clip_rectangle.y_end = canvas[active_canvas].height - translation.y;
162     }
163 }
164 
graphics_reset_clip_rectangle(void)165 void graphics_reset_clip_rectangle(void)
166 {
167     clip_rectangle.x_start = 0;
168     clip_rectangle.x_end = canvas[active_canvas].width;
169     clip_rectangle.y_start = 0;
170     clip_rectangle.y_end = canvas[active_canvas].height;
171     if (active_canvas == CANVAS_UI) {
172         translate_clip(translation.x, translation.y);
173     }
174 }
175 
set_clip_x(int x_offset,int width)176 static void set_clip_x(int x_offset, int width)
177 {
178     clip.clipped_pixels_left = 0;
179     clip.clipped_pixels_right = 0;
180     if (width <= 0
181         || x_offset + width <= clip_rectangle.x_start
182         || x_offset >= clip_rectangle.x_end) {
183         clip.clip_x = CLIP_INVISIBLE;
184         clip.visible_pixels_x = 0;
185         return;
186     }
187     if (x_offset < clip_rectangle.x_start) {
188         // clipped on the left
189         clip.clipped_pixels_left = clip_rectangle.x_start - x_offset;
190         if (x_offset + width <= clip_rectangle.x_end) {
191             clip.clip_x = CLIP_LEFT;
192         } else {
193             clip.clip_x = CLIP_BOTH;
194             clip.clipped_pixels_right = x_offset + width - clip_rectangle.x_end;
195         }
196     } else if (x_offset + width > clip_rectangle.x_end) {
197         clip.clip_x = CLIP_RIGHT;
198         clip.clipped_pixels_right = x_offset + width - clip_rectangle.x_end;
199     } else {
200         clip.clip_x = CLIP_NONE;
201     }
202     clip.visible_pixels_x = width - clip.clipped_pixels_left - clip.clipped_pixels_right;
203 }
204 
set_clip_y(int y_offset,int height)205 static void set_clip_y(int y_offset, int height)
206 {
207     clip.clipped_pixels_top = 0;
208     clip.clipped_pixels_bottom = 0;
209     if (height <= 0
210         || y_offset + height <= clip_rectangle.y_start
211         || y_offset >= clip_rectangle.y_end) {
212         clip.clip_y = CLIP_INVISIBLE;
213     } else if (y_offset < clip_rectangle.y_start) {
214         // clipped on the top
215         clip.clipped_pixels_top = clip_rectangle.y_start - y_offset;
216         if (y_offset + height <= clip_rectangle.y_end) {
217             clip.clip_y = CLIP_TOP;
218         } else {
219             clip.clip_y = CLIP_BOTH;
220             clip.clipped_pixels_bottom = y_offset + height - clip_rectangle.y_end;
221         }
222     } else if (y_offset + height > clip_rectangle.y_end) {
223         clip.clip_y = CLIP_BOTTOM;
224         clip.clipped_pixels_bottom = y_offset + height - clip_rectangle.y_end;
225     } else {
226         clip.clip_y = CLIP_NONE;
227     }
228     clip.visible_pixels_y = height - clip.clipped_pixels_top - clip.clipped_pixels_bottom;
229 }
230 
graphics_get_clip_info(int x,int y,int width,int height)231 const clip_info *graphics_get_clip_info(int x, int y, int width, int height)
232 {
233     set_clip_x(x, width);
234     set_clip_y(y, height);
235     if (clip.clip_x == CLIP_INVISIBLE || clip.clip_y == CLIP_INVISIBLE) {
236         clip.is_visible = 0;
237     } else {
238         clip.is_visible = 1;
239     }
240     return &clip;
241 }
242 
graphics_save_to_buffer(int x,int y,int width,int height,color_t * buffer)243 void graphics_save_to_buffer(int x, int y, int width, int height, color_t *buffer)
244 {
245     const clip_info *current_clip = graphics_get_clip_info(x, y, width, height);
246     if (!current_clip->is_visible) {
247         return;
248     }
249     int min_x = x + current_clip->clipped_pixels_left;
250     int min_dy = current_clip->clipped_pixels_top;
251     int max_dy = height - current_clip->clipped_pixels_bottom;
252     for (int dy = min_dy; dy < max_dy; dy++) {
253         memcpy(&buffer[dy * width], graphics_get_pixel(min_x, y + dy),
254             sizeof(color_t) * current_clip->visible_pixels_x);
255     }
256 }
257 
graphics_draw_from_buffer(int x,int y,int width,int height,const color_t * buffer)258 void graphics_draw_from_buffer(int x, int y, int width, int height, const color_t *buffer)
259 {
260     const clip_info *current_clip = graphics_get_clip_info(x, y, width, height);
261     if (!current_clip->is_visible) {
262         return;
263     }
264     int min_x = x + current_clip->clipped_pixels_left;
265     int min_dy = current_clip->clipped_pixels_top;
266     int max_dy = height - current_clip->clipped_pixels_bottom;
267     for (int dy = min_dy; dy < max_dy; dy++) {
268         memcpy(graphics_get_pixel(min_x, y + dy), &buffer[dy * width],
269             sizeof(color_t) * current_clip->visible_pixels_x);
270     }
271 }
272 
graphics_get_pixel(int x,int y)273 color_t *graphics_get_pixel(int x, int y)
274 {
275     if (active_canvas == CANVAS_UI) {
276         return &canvas[CANVAS_UI].pixels[(translation.y + y) * canvas[CANVAS_UI].width + translation.x + x];
277     } else {
278         return &canvas[active_canvas].pixels[y * canvas[active_canvas].width + x];
279     }
280 }
281 
graphics_clear_screen(canvas_type type)282 void graphics_clear_screen(canvas_type type)
283 {
284     memset(canvas[type].pixels, 0, sizeof(color_t) * canvas[type].width * canvas[type].height);
285 }
286 
graphics_clear_city_viewport(void)287 void graphics_clear_city_viewport(void)
288 {
289     int x, y, width, height;
290     city_view_get_unscaled_viewport(&x, &y, &width, &height);
291     while (y < height) {
292         memset(graphics_get_pixel(0, y + TOP_MENU_HEIGHT), 0, width * sizeof(color_t));
293         y++;
294     }
295 }
296 
graphics_clear_screens(void)297 void graphics_clear_screens(void)
298 {
299     graphics_clear_screen(CANVAS_UI);
300     if (config_get(CONFIG_UI_ZOOM)) {
301         graphics_clear_screen(CANVAS_CITY);
302     }
303 }
304 
graphics_draw_vertical_line(int x,int y1,int y2,color_t color)305 void graphics_draw_vertical_line(int x, int y1, int y2, color_t color)
306 {
307     if (x < clip_rectangle.x_start || x >= clip_rectangle.x_end) {
308         return;
309     }
310     int y_min = y1 < y2 ? y1 : y2;
311     int y_max = y1 < y2 ? y2 : y1;
312     y_min = y_min < clip_rectangle.y_start ? clip_rectangle.y_start : y_min;
313     y_max = y_max >= clip_rectangle.y_end ? clip_rectangle.y_end - 1 : y_max;
314     color_t *pixel = graphics_get_pixel(x, y_min);
315     color_t *end_pixel = pixel + ((y_max - y_min) * canvas[active_canvas].width);
316     while (pixel <= end_pixel) {
317         *pixel = color;
318         pixel += canvas[active_canvas].width;
319     }
320 }
321 
graphics_draw_horizontal_line(int x1,int x2,int y,color_t color)322 void graphics_draw_horizontal_line(int x1, int x2, int y, color_t color)
323 {
324     if (y < clip_rectangle.y_start || y >= clip_rectangle.y_end) {
325         return;
326     }
327     int x_min = x1 < x2 ? x1 : x2;
328     int x_max = x1 < x2 ? x2 : x1;
329     x_min = x_min < clip_rectangle.x_start ? clip_rectangle.x_start : x_min;
330     x_max = x_max >= clip_rectangle.x_end ? clip_rectangle.x_end - 1 : x_max;
331     color_t *pixel = graphics_get_pixel(x_min, y);
332     color_t *end_pixel = pixel + (x_max - x_min);
333     while (pixel <= end_pixel) {
334         *pixel = color;
335         ++pixel;
336     }
337 }
338 
graphics_draw_rect(int x,int y,int width,int height,color_t color)339 void graphics_draw_rect(int x, int y, int width, int height, color_t color)
340 {
341     graphics_draw_horizontal_line(x, x + width - 1, y, color);
342     graphics_draw_horizontal_line(x, x + width - 1, y + height - 1, color);
343     graphics_draw_vertical_line(x, y, y + height - 1, color);
344     graphics_draw_vertical_line(x + width - 1, y, y + height - 1, color);
345 }
346 
graphics_draw_inset_rect(int x,int y,int width,int height)347 void graphics_draw_inset_rect(int x, int y, int width, int height)
348 {
349     graphics_draw_horizontal_line(x, x + width - 1, y, COLOR_INSET_DARK);
350     graphics_draw_vertical_line(x + width - 1, y, y + height - 1, COLOR_INSET_LIGHT);
351     graphics_draw_horizontal_line(x, x + width - 1, y + height - 1, COLOR_INSET_LIGHT);
352     graphics_draw_vertical_line(x, y, y + height - 1, COLOR_INSET_DARK);
353 }
354 
graphics_fill_rect(int x,int y,int width,int height,color_t color)355 void graphics_fill_rect(int x, int y, int width, int height, color_t color)
356 {
357     for (int yy = y; yy < height + y; yy++) {
358         graphics_draw_horizontal_line(x, x + width - 1, yy, color);
359     }
360 }
361 
graphics_shade_rect(int x,int y,int width,int height,int darkness)362 void graphics_shade_rect(int x, int y, int width, int height, int darkness)
363 {
364     const clip_info *cur_clip = graphics_get_clip_info(x, y, width, height);
365     if (!cur_clip->is_visible) {
366         return;
367     }
368     for (int yy = y + cur_clip->clipped_pixels_top; yy < y + height - cur_clip->clipped_pixels_bottom; yy++) {
369         for (int xx = x + cur_clip->clipped_pixels_left; xx < x + width - cur_clip->clipped_pixels_right; xx++) {
370             color_t *pixel = graphics_get_pixel(xx, yy);
371             int r = (*pixel & 0xff0000) >> 16;
372             int g = (*pixel & 0xff00) >> 8;
373             int b = (*pixel & 0xff);
374             int grey = (r + g + b) / 3 >> darkness;
375             color_t new_pixel = (color_t) (ALPHA_OPAQUE | grey << 16 | grey << 8 | grey);
376             *pixel = new_pixel;
377         }
378     }
379 }
380