1 /*
2 * KCemu -- The emulator for the KC85 homecomputer series and much more.
3 * Copyright (C) 1997-2010 Torsten Paul
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <math.h>
21 #include <stdio.h>
22
23 #include <cairo/cairo.h>
24 #include <cairo/cairo-pdf.h>
25 #include <cairo/cairo-svg.h>
26
27 #include "kc/system.h"
28
29 #include "kc/plotter.h"
30
Plotter(void)31 Plotter::Plotter(void)
32 {
33 init();
34 }
35
~Plotter(void)36 Plotter::~Plotter(void)
37 {
38 }
39
40 void
init(void)41 Plotter::init(void)
42 {
43 _x = 0;
44 _y = 0;
45 _pen_down_factor = 1.2;
46 _show_pen = false;
47 _show_plot_area = false;
48
49 _pdf_cr = 0;
50 _pdf_surface = 0;
51
52 _buffer_cr = 0;
53 _buffer_surface = 0;
54
55 _onscreen_cr = 0;
56 _onscreen_surface = 0;
57
58 _origin_x = _origin_x_new = (WIDTH_MM - PLOTTING_AREA_WIDTH_MM) / 2.0;
59 _origin_y = _origin_y_new = (HEIGHT_MM - PLOTTING_AREA_HEIGHT_MM) / 2.0;
60
61 clear_dirty_rect();
62
63 pen_up();
64 set_line_width(0.2);
65 set_bg_color(1, 1, 1);
66 set_pen_color(0, 0, 0);
67
68 init_image_surface(&_buffer_surface, &_buffer_cr, WIDTH_MM * BUFFER_SURFACE_SCALE, HEIGHT_MM * BUFFER_SURFACE_SCALE);
69 init_image_surface(&_onscreen_surface, &_onscreen_cr, WIDTH_MM * ONSCREEN_SURFACE_SCALE, HEIGHT_MM * ONSCREEN_SURFACE_SCALE);
70 }
71
72 void
clear_dirty_rect(void)73 Plotter::clear_dirty_rect(void)
74 {
75 _dirty = false;
76 _dirty_x1 = PLOTTING_AREA_WIDTH_MM;
77 _dirty_y1 = PLOTTING_AREA_HEIGHT_MM;
78 _dirty_x2 = 0;
79 _dirty_y2 = 0;
80 }
81
82 void
invalidate(void)83 Plotter::invalidate(void)
84 {
85 _dirty = true;
86 _invalidated = true;
87 }
88
89 void
update_dirty_rect(double x,double y)90 Plotter::update_dirty_rect(double x, double y)
91 {
92 _dirty = true;
93 if (x < _dirty_x1)
94 _dirty_x1 = x;
95 if (y < _dirty_y1)
96 _dirty_y1 = y;
97 if (x > _dirty_x2)
98 _dirty_x2 = x;
99 if (y > _dirty_y2)
100 _dirty_y2 = y;
101 }
102
103 bool
is_dirty(void)104 Plotter::is_dirty(void)
105 {
106 return _dirty;
107 }
108
109 cairo_rectangle_t *
get_dirty_rectangle(void)110 Plotter::get_dirty_rectangle(void)
111 {
112 static cairo_rectangle_t rect;
113 static cairo_rectangle_t rect_full = { 0, 0, WIDTH_MM * ONSCREEN_SURFACE_SCALE, HEIGHT_MM * ONSCREEN_SURFACE_SCALE};
114
115 if (_invalidated)
116 {
117 _invalidated = false;
118 return &rect_full;
119 }
120
121 if (!_dirty)
122 return NULL;
123
124 cairo_rectangle_t *r;
125 if (is_show_helpers())
126 {
127 r = &rect_full;
128 }
129 else
130 {
131 if (_dirty_x1 > _dirty_x2)
132 return NULL;
133
134 r = ▭
135 rect.x = _dirty_x1 + _origin_x - 2 * _line_width;
136 rect.y = _dirty_y1 + _origin_y - 2 * _line_width;
137 rect.width = _dirty_x2 - _dirty_x1 + 4 * _line_width;
138 rect.height = _dirty_y2 - _dirty_y1 + 4 * _line_width;
139
140 rect.x *= ONSCREEN_SURFACE_SCALE;
141 rect.y *= ONSCREEN_SURFACE_SCALE;
142 rect.width *= ONSCREEN_SURFACE_SCALE;
143 rect.height *= ONSCREEN_SURFACE_SCALE;
144
145 rect.x -= 2;
146 rect.y -= 2;
147 rect.width += 4;
148 rect.height += 4;
149
150 if (rect.x < 0)
151 rect.x = 0;
152 if (rect.y < 0)
153 rect.y = 0;
154 }
155
156 clear_dirty_rect();
157
158 return r;
159 }
160
161 cairo_t *
replace_cairo_context(cairo_t * old_cr,cairo_surface_t * surface,double scale_factor)162 Plotter::replace_cairo_context(cairo_t *old_cr, cairo_surface_t *surface, double scale_factor)
163 {
164 cairo_t *cr = cairo_create(surface);
165
166 cairo_scale(cr, scale_factor, scale_factor);
167 cairo_translate(cr, _origin_x, _origin_y);
168
169 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
170 cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
171
172 cairo_set_line_width(cr, _line_width);
173 cairo_set_source_rgb(cr, _red, _green, _blue);
174
175 cairo_destroy(old_cr);
176
177 return cr;
178 }
179
180 void
init_image_surface(cairo_surface_t ** surface,cairo_t ** cr,double width,double height)181 Plotter::init_image_surface(cairo_surface_t **surface, cairo_t **cr, double width, double height)
182 {
183 *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
184 *cr = replace_cairo_context(*cr, *surface, width / WIDTH_MM);
185 cairo_surface_destroy(*surface);
186
187 clear_surface(*surface, width / WIDTH_MM);
188 }
189
190 void
clear_surface(cairo_surface_t * surface,double scale_factor)191 Plotter::clear_surface(cairo_surface_t *surface, double scale_factor)
192 {
193 cairo_t *cr = cairo_create(surface);
194 cairo_scale(cr, scale_factor, scale_factor);
195 cairo_set_source_rgb(cr, _bg_red, _bg_green, _bg_blue);
196 cairo_rectangle(cr, 0, 0, WIDTH_MM, HEIGHT_MM);
197 cairo_fill(cr);
198 cairo_destroy(cr);
199 }
200
201 void
open_pdf(const char * filename)202 Plotter::open_pdf(const char *filename)
203 {
204 close_pdf();
205
206 cairo_surface_t *surface = cairo_pdf_surface_create(filename, WIDTH_MM * MM_TO_INCH, HEIGHT_MM * MM_TO_INCH);
207 cairo_status_t status = cairo_surface_status(surface);
208 if (status == CAIRO_STATUS_SUCCESS)
209 {
210 _pdf_cr = replace_cairo_context(_pdf_cr, surface, MM_TO_INCH);
211 _pdf_surface = surface;
212 }
213 cairo_surface_destroy(surface);
214 }
215
216 void
close_pdf(void)217 Plotter::close_pdf(void)
218 {
219 if (_pdf_cr)
220 cairo_destroy(_pdf_cr);
221
222 _pdf_cr = 0;
223 _pdf_surface = 0;
224 }
225
226 void
show_page(void)227 Plotter::show_page(void)
228 {
229 _origin_x = _origin_x_new;
230 _origin_y = _origin_y_new;
231
232 if (_pdf_surface)
233 {
234 cairo_surface_show_page(_pdf_surface);
235 clear_surface(_pdf_surface, MM_TO_INCH);
236 _pdf_cr = replace_cairo_context(_pdf_cr, _pdf_surface, MM_TO_INCH);
237 }
238
239 clear_surface(_buffer_surface, BUFFER_SURFACE_SCALE);
240 _buffer_cr = replace_cairo_context(_buffer_cr, _buffer_surface, BUFFER_SURFACE_SCALE);
241
242 clear_surface(_onscreen_surface, ONSCREEN_SURFACE_SCALE);
243 _onscreen_cr = replace_cairo_context(_onscreen_cr, _onscreen_surface, ONSCREEN_SURFACE_SCALE);
244
245 invalidate();
246 }
247
248 cairo_status_t
save_as_png(const char * filename)249 Plotter::save_as_png(const char *filename)
250 {
251 cairo_status_t status = cairo_surface_write_to_png(_buffer_surface, filename);
252 return status;
253 }
254
255 cairo_surface_t *
get_onscreen_surface()256 Plotter::get_onscreen_surface()
257 {
258 static cairo_surface_t *surface = NULL;
259
260 if (!is_show_helpers())
261 return _onscreen_surface;
262
263 if (surface)
264 cairo_surface_destroy(surface);
265
266 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH_MM * ONSCREEN_SURFACE_SCALE, HEIGHT_MM * ONSCREEN_SURFACE_SCALE);
267 cairo_t *cr = cairo_create(surface);
268
269 cairo_set_source_surface(cr, _onscreen_surface, 0, 0);
270 cairo_paint(cr);
271
272 cairo_scale(cr, ONSCREEN_SURFACE_SCALE, ONSCREEN_SURFACE_SCALE);
273 cairo_translate(cr, _origin_x, _origin_y);
274
275 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
276 cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
277
278 cairo_set_line_width(cr, 0.1);
279
280 if (is_show_pen())
281 {
282 cairo_set_source_rgba(cr, 1, 0, 0, 0.4);
283 cairo_move_to(cr, get_x(), get_y());
284 cairo_arc(cr, get_x(), get_y(), 1, 0, 2 * M_PI);
285 cairo_fill(cr);
286
287 cairo_set_source_rgba(cr, 0, 0, 1, 0.8);
288 cairo_move_to(cr, get_x() - 5, get_y());
289 cairo_rel_line_to(cr, 10, 0);
290 cairo_stroke(cr);
291
292 cairo_move_to(cr, get_x(), get_y() - 5);
293 cairo_rel_line_to(cr, 0, 10);
294 cairo_stroke(cr);
295 }
296
297 if (is_show_plot_area())
298 {
299 cairo_set_source_rgba(cr, 0, 1, 0, 0.8);
300 cairo_rectangle(cr, 0, 0, PLOTTING_AREA_WIDTH_MM, PLOTTING_AREA_HEIGHT_MM);
301 cairo_stroke(cr);
302 }
303
304 cairo_destroy(cr);
305
306 return surface;
307 }
308
309 void
pen_up(void)310 Plotter::pen_up(void)
311 {
312 _pen_down = false;
313 }
314
315 void
pen_down(void)316 Plotter::pen_down(void)
317 {
318 _pen_down = true;
319
320 set_point(_buffer_cr, get_x(), get_y());
321 set_point(_onscreen_cr, get_x(), get_y());
322
323 if (_pdf_cr)
324 set_point(_pdf_cr, get_x(), get_y());
325 }
326
327 void
step(int delta_x,int delta_y)328 Plotter::step(int delta_x, int delta_y)
329 {
330 double x = _x + delta_x * STEP_WIDTH;
331 double y = _y + delta_y * STEP_WIDTH;
332
333 if (x < 0)
334 x = 0;
335 if (x > PLOTTING_AREA_WIDTH_MM)
336 x = PLOTTING_AREA_WIDTH_MM;
337
338 if (y < 0)
339 y = 0;
340 if (y > PLOTTING_AREA_HEIGHT_MM)
341 y = PLOTTING_AREA_HEIGHT_MM;
342
343 draw_to(_buffer_cr, x, y);
344 draw_to(_onscreen_cr, x, y);
345
346 if (_pdf_cr)
347 draw_to(_pdf_cr, x, y);
348
349 _x = x;
350 _y = y;
351 }
352
353 double
get_x(void)354 Plotter::get_x(void)
355 {
356 return _x;
357 }
358
359 double
get_y(void)360 Plotter::get_y(void)
361 {
362 return _y;
363 }
364
365 bool
is_pen_down(void)366 Plotter::is_pen_down(void)
367 {
368 return _pen_down;
369 }
370
371 double
get_line_width(void)372 Plotter::get_line_width(void)
373 {
374 return _line_width;
375 }
376
377 void
set_line_width(double line_width)378 Plotter::set_line_width(double line_width)
379 {
380 _line_width = line_width;
381
382 if (_buffer_cr)
383 cairo_set_line_width(_buffer_cr, _line_width);
384
385 if (_onscreen_cr)
386 cairo_set_line_width(_onscreen_cr, _line_width);
387
388 if (_pdf_cr)
389 cairo_set_line_width(_pdf_cr, _line_width);
390 }
391
392 double
get_origin_x(void)393 Plotter::get_origin_x(void)
394 {
395 return _origin_x_new;
396 }
397
398 void
set_origin_x(double origin_x)399 Plotter::set_origin_x(double origin_x)
400 {
401 _origin_x_new = origin_x;
402 }
403
404 double
get_origin_y(void)405 Plotter::get_origin_y(void)
406 {
407 return _origin_y_new;
408 }
409
410 void
set_origin_y(double origin_y)411 Plotter::set_origin_y(double origin_y)
412 {
413 _origin_y_new = origin_y;
414 }
415
416 double
get_bg_red(void)417 Plotter::get_bg_red(void)
418 {
419 return _bg_red;
420 }
421
422 double
get_bg_green(void)423 Plotter::get_bg_green(void)
424 {
425 return _bg_green;
426 }
427
428 double
get_bg_blue(void)429 Plotter::get_bg_blue(void)
430 {
431 return _bg_blue;
432 }
433
434 void
set_bg_color(double red,double green,double blue)435 Plotter::set_bg_color(double red, double green, double blue)
436 {
437 _bg_red = red;
438 _bg_green = green;
439 _bg_blue = blue;
440 }
441
442 double
get_pen_red(void)443 Plotter::get_pen_red(void)
444 {
445 return _red;
446 }
447
448 double
get_pen_green(void)449 Plotter::get_pen_green(void)
450 {
451 return _green;
452 }
453
454 double
get_pen_blue(void)455 Plotter::get_pen_blue(void)
456 {
457 return _blue;
458 }
459
460 void
set_pen_color(double red,double green,double blue)461 Plotter::set_pen_color(double red, double green, double blue)
462 {
463 _red = red;
464 _green = green;
465 _blue = blue;
466
467 if (_buffer_cr)
468 cairo_set_source_rgb(_buffer_cr, _red, _green, _blue);
469
470 if (_onscreen_cr)
471 cairo_set_source_rgb(_onscreen_cr, _red, _green, _blue);
472
473 if (_pdf_cr)
474 cairo_set_source_rgb(_pdf_cr, _red, _green, _blue);
475 }
476
477 bool
is_show_pen(void)478 Plotter::is_show_pen(void)
479 {
480 return _show_pen;
481 }
482
483 void
set_show_pen(bool show_pen)484 Plotter::set_show_pen(bool show_pen)
485 {
486 invalidate();
487 _show_pen = show_pen;
488 }
489
490 bool
is_show_plot_area(void)491 Plotter::is_show_plot_area(void)
492 {
493 return _show_plot_area;
494 }
495
496 void
set_show_plot_area(bool show_plot_area)497 Plotter::set_show_plot_area(bool show_plot_area)
498 {
499 invalidate();
500 _show_plot_area = show_plot_area;
501 }
502
503 bool
is_show_helpers(void)504 Plotter::is_show_helpers(void)
505 {
506 return is_show_pen() || is_show_plot_area();
507 }
508
509 void
set_point(cairo_t * cr,double x,double y)510 Plotter::set_point(cairo_t *cr, double x, double y)
511 {
512 cairo_move_to(cr, x, y);
513 cairo_save(cr);
514 cairo_set_line_width(cr, cairo_get_line_width(cr) * _pen_down_factor);
515 cairo_rel_line_to(cr, 0, 0);
516 cairo_stroke(cr);
517 cairo_restore(cr);
518 update_dirty_rect(x, y);
519 }
520
521 void
draw_to(cairo_t * cr,double x,double y)522 Plotter::draw_to(cairo_t *cr, double x, double y)
523 {
524 // force dirty status also when just moving the cursor
525 // this is required for the cursor indicator to also
526 // generate update requests
527 _dirty = true;
528
529 if (is_pen_down())
530 {
531 cairo_line_to(cr, x, y);
532 cairo_stroke(cr);
533 update_dirty_rect(x, y);
534 }
535 cairo_move_to(cr, x, y);
536 }
537