1// This file is part of SmallBASIC
2//
3// Copyright(C) 2001-2019 Chris Warren-Smith.
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8
9#include "config.h"
10#include <math.h>
11#include "ui/utils.h"
12#include "platform/fltk/display.h"
13
14GraphicsWidget *graphics;
15
16#define MAX_CANVAS_SIZE 20
17
18//
19// Canvas implementation
20//
21Canvas::Canvas() :
22 _w(0),
23 _h(0),
24 _scale(0),
25 _offscreen(0),
26 _clip(NULL),
27 _drawColor(fl_rgb_color(31, 28, 31)) {
28}
29
30Canvas::~Canvas() {
31 if (_offscreen) {
32 fl_delete_offscreen(_offscreen);
33 _offscreen = 0;
34 }
35 delete _clip;
36 _clip = NULL;
37}
38
39bool Canvas::create(int w, int h) {
40 logEntered();
41 _w = w;
42 _h = h;
43 _offscreen = fl_create_offscreen(_w, _h);
44 _scale = Fl_Graphics_Driver::default_driver().scale();
45 return _offscreen != 0;
46}
47
48void Canvas::drawArc(int xc, int yc, double r, double start, double end, double aspect) {
49 fl_begin_offscreen(_offscreen);
50 fl_push_clip(x(), y(), w(), h());
51 fl_color(_drawColor);
52 if (r < 1) {
53 r = 1;
54 }
55 while (end < start) {
56 end += M_PI * 2.0;
57 }
58 double th = (end - start) / r;
59 double xs = xc + r * cos(start);
60 double ys = yc + r * aspect * sin(start);
61 double xe = xc + r * cos(end);
62 double ye = yc + r * aspect * sin(end);
63 int x = xs;
64 int y = ys;
65 for (int i = 1; i < r; i++) {
66 double ph = start + i * th;
67 xs = xc + r * cos(ph);
68 ys = yc + r * aspect * sin(ph);
69 fl_line(x, y, xs, ys);
70 x = xs;
71 y = ys;
72 }
73 fl_line(x, y, xe, ye);
74 fl_pop_clip();
75 fl_end_offscreen();
76}
77
78void Canvas::drawEllipse(int xc, int yc, int rx, int ry, bool fill) {
79 fl_begin_offscreen(_offscreen);
80 fl_push_clip(x(), y(), w(), h());
81 fl_color(_drawColor);
82 int x = xc - rx;
83 int y = yc - ry;
84 int w = rx * 2;
85 int h = ry * 2;
86 if (fill) {
87 fl_begin_polygon();
88 fl_pie(x, y, w, h, 0.0, 360.0);
89 fl_end_polygon();
90 } else {
91 fl_begin_line();
92 fl_arc(x, y, w, h, 0.0, 360.0);
93 fl_end_line();
94 }
95 fl_pop_clip();
96 fl_end_offscreen();
97}
98
99void Canvas::drawLine(int startX, int startY, int endX, int endY) {
100 fl_begin_offscreen(_offscreen);
101 fl_push_clip(x(), y(), w(), h());
102 fl_color(_drawColor);
103 fl_line(startX, startY, endX, endY);
104 fl_pop_clip();
105 fl_end_offscreen();
106}
107
108void Canvas::drawPixel(int posX, int posY) {
109 fl_begin_offscreen(_offscreen);
110 fl_color(_drawColor);
111 fl_point(posX, posY);
112 fl_end_offscreen();
113}
114
115struct DrawData {
116 uint8_t *_image;
117 uint8_t *_screen;
118 int _opacity;
119 int _stride;
120 int _left;
121 int _top;
122};
123
124// x, y, w are position and width of scan line in image.
125// copy w pixels from scanline y, starting at pixel x to this buffer.
126void drawImage(void *data, int x, int y, int w, uchar *out) {
127 DrawData *drawData = (DrawData *)data;
128 uint8_t *image = drawData->_image;
129 uint8_t *screen = drawData->_screen;
130 int opacity = drawData->_opacity;
131 int scanLine = w * 4;
132 int i_offs = (drawData->_left + ((y + drawData->_top) * drawData->_stride)) * 4;
133 int s_offs = y * w * 4;
134 float op = opacity / 100.0f;
135
136 for (int sx = 0; sx < scanLine; sx += 4, i_offs += 4, s_offs += 4) {
137 uint8_t a = image[i_offs + 3];
138 uint8_t b = image[i_offs + 2];
139 uint8_t g = image[i_offs + 1];
140 uint8_t r = image[i_offs + 0];
141 uint8_t sB = screen[s_offs + 2];
142 uint8_t sG = screen[s_offs + 1];
143 uint8_t sR = screen[s_offs + 0];
144 if (opacity > 0 && opacity < 100 && a > 64) {
145 sR = (op * r) + ((1 - op) * sR);
146 sG = (op * g) + ((1 - op) * sG);
147 sB = (op * b) + ((1 - op) * sB);
148 } else {
149 sR = sR + ((r - sR) * a / 255);
150 sG = sG + ((g - sG) * a / 255);
151 sB = sB + ((b - sB) * a / 255);
152 }
153 out[sx + 3] = a;
154 out[sx + 2] = sB;
155 out[sx + 1] = sG;
156 out[sx + 0] = sR;
157 }
158}
159
160void Canvas::drawRGB(const MAPoint2d *dstPoint, const void *src, const MARect *srcRect, int opacity, int stride) {
161 int x = dstPoint->x;
162 int y = dstPoint->y;
163 int w = srcRect->width;
164 int h = srcRect->height;
165 DrawData data;
166 data._image = (uint8_t *)src;
167 data._opacity = opacity;
168 data._screen = (uint8_t *)calloc(w * h * 4, 1);
169 data._stride = stride;
170 data._left = srcRect->left;
171 data._top = srcRect->top;
172
173 fl_begin_offscreen(_offscreen);
174 fl_read_image(data._screen, x, y, w, h, 1);
175 fl_draw_image(drawImage, (void *)&data, x, y, w, h, 4);
176 fl_end_offscreen();
177 free(data._screen);
178}
179
180void Canvas::drawRegion(Canvas *src, const MARect *srcRect, int destX, int destY) {
181 int width = MIN(_w, srcRect->width);
182 int height = MIN(_h, srcRect->height);
183 fl_begin_offscreen(_offscreen);
184 fl_copy_offscreen(destX, destY, width, height, src->_offscreen, srcRect->left, srcRect->top);
185 fl_end_offscreen();
186}
187
188void Canvas::drawText(Font *font, int left, int top, const char *str, int len) {
189 fl_begin_offscreen(_offscreen);
190 if (font) {
191 font->setCurrent();
192 }
193 fl_push_clip(x(), y(), w(), h());
194 fl_color(_drawColor);
195 fl_draw(str, len, x() + left, y() + top);
196 fl_pop_clip();
197 fl_end_offscreen();
198}
199
200void Canvas::fillRect(int left, int top, int width, int height, Fl_Color color) {
201 fl_begin_offscreen(_offscreen);
202 fl_color(color);
203 fl_push_clip(x(), y(), w(), h());
204 fl_rectf(left, top, width, height);
205 fl_pop_clip();
206 fl_end_offscreen();
207}
208
209void Canvas::getImageData(uint8_t *image, const MARect *srcRect, int stride) {
210 fl_begin_offscreen(_offscreen);
211 unsigned x = srcRect->left;
212 unsigned y = srcRect->top;
213 unsigned w = srcRect->width;
214 unsigned h = srcRect->height;
215 fl_read_image(image, x, y, w, h, 1);
216 fl_end_offscreen();
217
218 if (srcRect->width == 1 && srcRect->height == 1) {
219 // compatibility with PSET/POINT
220 uchar b = image[2];
221 uchar g = image[1];
222 uchar r = image[0];
223 image[3] = 255;
224 image[2] = r;
225 image[1] = g;
226 image[0] = b;
227 } else {
228 // set alpha to 255
229 for (unsigned y = 0; y < h; y++) {
230 unsigned yoffs = (4 * y * w);
231 for (unsigned x = 0; x < w; x++) {
232 int offs = yoffs + (x * 4);
233 image[offs + 3] = 255;
234 }
235 }
236 }
237}
238
239void Canvas::setClip(int x, int y, int w, int h) {
240 delete _clip;
241 if (x != 0 || y != 0 || _w != w || _h != h) {
242 _clip = new Fl_Rect(x, y, x + w, y + h);
243 } else {
244 _clip = NULL;
245 }
246}
247
248//
249// Graphics implementation
250//
251GraphicsWidget::GraphicsWidget(int xx, int yy, int ww, int hh) :
252 Fl_Widget(xx, yy, ww, hh),
253 _screen(NULL),
254 _drawTarget(NULL),
255 _font(NULL),
256 _textOffset(0) {
257 logEntered();
258 _screen = new Canvas();
259 _screen->create(ww, hh);
260 graphics = this;
261}
262
263GraphicsWidget::~GraphicsWidget() {
264 delete _screen;
265}
266
267void GraphicsWidget::deleteFont(Font *font) {
268 if (font == _font) {
269 _font = NULL;
270 }
271 delete font;
272}
273
274void GraphicsWidget::draw() {
275 if (_screen && _screen->_offscreen) {
276 if (_screen->_scale != Fl_Graphics_Driver::default_driver().scale()) {
277 fl_rescale_offscreen(_screen->_offscreen);
278 _screen->_scale = Fl_Graphics_Driver::default_driver().scale();
279 }
280 fl_copy_offscreen(x(), y(), w(), h(), _screen->_offscreen, 0, 0);
281 }
282}
283
284void GraphicsWidget::drawText(int left, int top, const char *str, int length) {
285 if (_drawTarget) {
286 _drawTarget->drawText(_font, left, top + _textOffset, str, length);
287 }
288}
289
290MAExtent GraphicsWidget::getTextSize(const char *str) {
291 if (_font) {
292 _font->setCurrent();
293 }
294 int height = fl_height();
295 int width = fl_width(str);
296 _textOffset = height - fl_descent();
297 return (MAExtent)((width << 16) + height);
298}
299
300void GraphicsWidget::resize(int x, int y, int w, int h) {
301 Fl_Widget::resize(x, y, w, h);
302 layout();
303}
304
305void GraphicsWidget::layout() {
306 if (_screen->_w != w() || _screen->_h != h()) {
307 logEntered();
308 bool drawScreen = (_drawTarget == _screen);
309 delete _screen;
310 _screen = new Canvas();
311 _screen->create(w(), h());
312 _drawTarget = drawScreen ? _screen : NULL;
313 }
314}
315
316MAHandle GraphicsWidget::setDrawTarget(MAHandle maHandle) {
317 MAHandle result = (MAHandle) _drawTarget;
318 if (maHandle == (MAHandle) HANDLE_SCREEN ||
319 maHandle == (MAHandle) HANDLE_SCREEN_BUFFER) {
320 _drawTarget = _screen;
321
322 } else {
323 _drawTarget = (Canvas *)maHandle;
324 }
325 delete _drawTarget->_clip;
326 _drawTarget->_clip = NULL;
327 return result;
328}
329
330//
331// maapi implementation
332//
333MAHandle maCreatePlaceholder(void) {
334 return (MAHandle) new Canvas();
335}
336
337int maFontDelete(MAHandle maHandle) {
338 if (maHandle != -1) {
339 graphics->deleteFont((Font *)maHandle);
340 }
341 return RES_FONT_OK;
342}
343
344int maSetColor(int c) {
345 Canvas *canvas = graphics->getDrawTarget();
346 if (canvas) {
347 // Fl_Color => 0xrrggbbii
348 canvas->setColor((c << 8) & 0xffffff00);
349 }
350 return c;
351}
352
353void maSetClipRect(int left, int top, int width, int height) {
354 Canvas *canvas = graphics->getDrawTarget();
355 if (canvas) {
356 canvas->setClip(left, top, width, height);
357 }
358}
359
360void maPlot(int posX, int posY) {
361 Canvas *canvas = graphics->getDrawTarget();
362 if (canvas) {
363 canvas->drawPixel(posX, posY);
364 }
365}
366
367void maLine(int startX, int startY, int endX, int endY) {
368 Canvas *canvas = graphics->getDrawTarget();
369 if (canvas) {
370 canvas->drawLine(startX, startY, endX, endY);
371 }
372}
373
374void maFillRect(int left, int top, int width, int height) {
375 Canvas *canvas = graphics->getDrawTarget();
376 if (canvas) {
377 canvas->fillRect(left, top, width, height, canvas->_drawColor);
378 }
379}
380
381void maArc(int xc, int yc, double r, double start, double end, double aspect) {
382 Canvas *canvas = graphics->getDrawTarget();
383 if (canvas) {
384 canvas->drawArc(xc, yc, r, start, end, aspect);
385 }
386}
387
388void maEllipse(int xc, int yc, int rx, int ry, int fill) {
389 Canvas *canvas = graphics->getDrawTarget();
390 if (canvas) {
391 canvas->drawEllipse(xc, yc, rx, ry, fill);
392 }
393}
394
395void maDrawText(int left, int top, const char *str, int length) {
396 if (str && str[0]) {
397 graphics->drawText(left, top, str, length);
398 }
399}
400
401void maDrawRGB(const MAPoint2d *dstPoint, const void *src, const MARect *srcRect, int opacity, int stride) {
402 Canvas *canvas = graphics->getDrawTarget();
403 if (canvas) {
404 canvas->drawRGB(dstPoint, src, srcRect, opacity, stride);
405 }
406}
407
408MAExtent maGetTextSize(const char *str) {
409 MAExtent result;
410 if (str && str[0]) {
411 result = graphics->getTextSize(str);
412 } else {
413 result = 0;
414 }
415 return result;
416}
417
418MAExtent maGetScrSize(void) {
419 short width = graphics->getWidth();
420 short height = graphics->getHeight();
421 return (MAExtent)((width << 16) + height);
422}
423
424MAHandle maFontLoadDefault(int type, int style, int size) {
425 Fl_Font font = FL_COURIER;
426 if (style & FONT_STYLE_BOLD) {
427 font += FL_BOLD;
428 }
429 if (style & FONT_STYLE_ITALIC) {
430 font += FL_ITALIC;
431 }
432 return (MAHandle)new Font(font, size);
433}
434
435MAHandle maFontSetCurrent(MAHandle maHandle) {
436 graphics->setFont((Font *)maHandle);
437 return maHandle;
438}
439
440void maDrawImageRegion(MAHandle maHandle, const MARect *srcRect, const MAPoint2d *dstPoint, int transformMode) {
441 Canvas *canvas = graphics->getDrawTarget();
442 Canvas *src = (Canvas *)maHandle;
443 if (canvas && canvas != src) {
444 canvas->drawRegion(src, srcRect, dstPoint->x, dstPoint->y);
445 }
446}
447
448void maDestroyPlaceholder(MAHandle maHandle) {
449 Canvas *canvas = (Canvas *)maHandle;
450 delete canvas;
451}
452
453void maGetImageData(MAHandle maHandle, void *dst, const MARect *srcRect, int stride) {
454 Canvas *canvas = (Canvas *)maHandle;
455 if (canvas == HANDLE_SCREEN) {
456 canvas = graphics->getScreen();
457 }
458 canvas->getImageData((uint8_t *)dst, srcRect, stride);
459}
460
461MAHandle maSetDrawTarget(MAHandle maHandle) {
462 return graphics->setDrawTarget(maHandle);
463}
464
465int maCreateDrawableImage(MAHandle maHandle, int width, int height) {
466 int result = RES_OK;
467 int maxSize = MAX(graphics->getWidth(), graphics->getHeight());
468 if (height > maxSize * MAX_CANVAS_SIZE) {
469 result -= 1;
470 } else {
471 Canvas *drawable = (Canvas *)maHandle;
472 result = drawable->create(width, height) ? RES_OK : -1;
473 }
474 return result;
475}
476
477void maUpdateScreen(void) {
478 ((::GraphicsWidget *)graphics)->redraw();
479}
480
481void maShowVirtualKeyboard(void) {
482 // not implemented
483}
484
485void maHideVirtualKeyboard(void) {
486 // not implemented
487}
488