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 = &rect;
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