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