1// This file is part of SmallBASIC
2//
3// Image handling
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// Copyright(C) 2002-2019 Chris Warren-Smith.
9
10#include "common/sys.h"
11#include "common/messages.h"
12#include "common/pproc.h"
13#include "common/fs_socket_client.h"
14#include "lib/maapi.h"
15#include "lib/lodepng/lodepng.h"
16#include "ui/image.h"
17#include "ui/system.h"
18#include "ui/rgb.h"
19
20#define IMG_X "x"
21#define IMG_Y "y"
22#define IMG_OFFSET_TOP "offsetTop"
23#define IMG_OFFSET_LEFT "offsetLeft"
24#define IMG_WIDTH "width"
25#define IMG_HEIGHT "height"
26#define IMG_ZINDEX "zIndex"
27#define IMG_OPACITY "opacity"
28#define IMG_ID "ID"
29#define IMG_BID "BID"
30
31extern System *g_system;
32unsigned nextId = 0;
33strlib::List<ImageBuffer *> buffers;
34
35extern "C" int xpm_decode32(uint8_t **image, unsigned *width, unsigned *height, const char *const *xpm);
36
37void reset_image_cache() {
38 buffers.removeAll();
39}
40
41ImageBuffer::ImageBuffer() :
42 _filename(nullptr),
43 _image(nullptr),
44 _bid(0),
45 _width(0),
46 _height(0) {
47}
48
49ImageBuffer::ImageBuffer(ImageBuffer &o) :
50 _filename(o._filename),
51 _image(o._image),
52 _bid(o._bid),
53 _width(o._width),
54 _height(o._height) {
55}
56
57ImageBuffer::~ImageBuffer() {
58 free(_filename);
59 free(_image);
60 _filename = nullptr;
61 _image = nullptr;
62}
63
64ImageDisplay::ImageDisplay() :
65 Shape(0, 0, 0, 0),
66 _offsetLeft(0),
67 _offsetTop(0),
68 _zIndex(0),
69 _opacity(0),
70 _id(0),
71 _bid(0),
72 _buffer(nullptr) {
73}
74
75ImageDisplay::ImageDisplay(ImageDisplay &o) :
76 ImageDisplay() {
77 copyImage(o);
78}
79
80void ImageDisplay::copyImage(ImageDisplay &o) {
81 _x = o._x;
82 _y = o._y;
83 _offsetLeft = o._offsetLeft;
84 _offsetTop = o._offsetTop;
85 _width = o._width;
86 _height = o._height;
87 _zIndex = o._zIndex;
88 _opacity = o._opacity;
89 _id = o._id;
90 _bid = o._bid;
91 _buffer = o._buffer;
92}
93
94void ImageDisplay::draw(int x, int y, int w, int h, int cw) {
95 if (_buffer != nullptr) {
96 MAPoint2d dstPoint;
97 MARect srcRect;
98
99 dstPoint.x = x;
100 dstPoint.y = y;
101 srcRect.left = MIN(_buffer->_width, _offsetLeft);
102 srcRect.top = MIN(_buffer->_height, _offsetTop);
103 srcRect.width = MIN(w, MIN(_buffer->_width, _width));
104 srcRect.height = MIN(h, MIN(_buffer->_height, _height));
105 dev_map_point(&dstPoint.x, &dstPoint.y);
106
107 if (unsigned(srcRect.top + srcRect.height) > _buffer->_height) {
108 srcRect.height = _buffer->_height - srcRect.top;
109 }
110 if (unsigned(srcRect.left + srcRect.width) > _buffer->_width) {
111 srcRect.width = _buffer->_width - srcRect.left;
112 }
113
114 maDrawRGB(&dstPoint, _buffer->_image, &srcRect, _opacity, _buffer->_width);
115 }
116}
117
118void to_argb(unsigned char *image, unsigned w, unsigned h) {
119#if defined(_SDL)
120 // convert from LCT_RGBA to ARGB
121 for (unsigned y = 0; y < h; y++) {
122 unsigned yoffs = (y * w * 4);
123 for (unsigned x = 0; x < w; x++) {
124 unsigned offs = yoffs + (x * 4);
125 uint8_t r = image[offs + 2];
126 uint8_t b = image[offs + 0];
127 image[offs + 2] = b;
128 image[offs + 0] = r;
129 }
130 }
131#endif
132}
133
134unsigned decode_png(unsigned char **image, unsigned *w, unsigned *h, const unsigned char *buffer, size_t size) {
135 unsigned error = lodepng_decode32(image, w, h, buffer, size);
136 if (!error) {
137 to_argb(*image, *w, *h);
138 }
139 return error;
140}
141
142unsigned decode_png_file(unsigned char **image, unsigned *w, unsigned *h, const char *filename) {
143 unsigned error = lodepng_decode32_file(image, w, h, filename);
144 if (!error) {
145 to_argb(*image, *w, *h);
146 }
147 return error;
148}
149
150unsigned encode_png_file(const char *filename, const unsigned char *image, unsigned w, unsigned h) {
151 unsigned result;
152#if defined(_SDL)
153 unsigned size = w * h * 4;
154 auto imageCopy = (uint8_t *)malloc(size);
155 if (!imageCopy) {
156 // lodepng memory error code
157 result = 83;
158 } else {
159 // convert from ARGB to LCT_RGBA
160 for (unsigned y = 0; y < h; y++) {
161 unsigned yoffs = (y * w * 4);
162 for (unsigned x = 0; x < w; x++) {
163 int offs = yoffs + (x * 4);
164 uint8_t a, r, g, b;
165 GET_IMAGE_ARGB(image, offs, a, r, g, b);
166 imageCopy[offs + 3] = a;
167 imageCopy[offs + 2] = b;
168 imageCopy[offs + 1] = g;
169 imageCopy[offs + 0] = r;
170 }
171 }
172 result = lodepng_encode32_file(filename, imageCopy, w, h);
173 free(imageCopy);
174 }
175#else
176 result = lodepng_encode32_file(filename, image, w, h);
177#endif
178 return result;
179}
180
181dev_file_t *eval_filep() {
182 dev_file_t *result = nullptr;
183 code_skipnext();
184 if (code_getnext() == '#') {
185 int handle = par_getint();
186 if (!prog_error) {
187 result = dev_getfileptr(handle);
188 }
189 }
190 return result;
191}
192
193uint8_t *get_image_data(int x, int y, int w, int h) {
194 MARect rc;
195 rc.left = x;
196 rc.top = y;
197 rc.width = w;
198 rc.height = h;
199 int size = w * h * 4;
200 auto result = (uint8_t *)malloc(size);
201 if (result != nullptr) {
202 g_system->getOutput()->redraw();
203 maGetImageData(HANDLE_SCREEN, result, &rc, w * 4);
204 }
205 return result;
206}
207
208ImageBuffer *get_image(unsigned bid) {
209 ImageBuffer *result = nullptr;
210 List_each(ImageBuffer *, it, buffers) {
211 ImageBuffer *next = (*it);
212 if (next->_bid == (unsigned)bid) {
213 result = next;
214 break;
215 }
216 }
217 return result;
218}
219
220ImageBuffer *load_image(var_int_t x) {
221 var_int_t y, w, h;
222 int count = par_massget("iii", &y, &w, &h);
223 int width = g_system->getOutput()->getWidth();
224 int height = g_system->getOutput()->getHeight();
225 ImageBuffer *result = nullptr;
226
227 if (prog_error || count == 0 || count == 2) {
228 err_throw(ERR_PARAM);
229 } else {
230 if (count == 1) {
231 w = width;
232 h = height;
233 } else {
234 w = MIN(w, width);
235 h = MIN(h, height);
236 }
237 uint8_t* image = get_image_data(x, y, w, h);
238 if (image == nullptr) {
239 err_throw(ERR_IMAGE_LOAD, "Failed to load screen image");
240 } else {
241 result = new ImageBuffer();
242 result->_bid = ++nextId;
243 result->_width = w;
244 result->_height = h;
245 result->_filename = nullptr;
246 result->_image = image;
247 buffers.add(result);
248 }
249 }
250 return result;
251}
252
253// share image buffer from another image variable
254ImageBuffer *load_image(var_t *var) {
255 ImageBuffer *result = nullptr;
256 if (var->type == V_MAP) {
257 int bid = map_get_int(var, IMG_BID, -1);
258 if (bid != -1) {
259 result = get_image((unsigned)bid);
260 }
261 } else if (var->type == V_ARRAY && v_maxdim(var) == 2) {
262 int h = ABS(v_ubound(var, 0) - v_lbound(var, 0)) + 1;
263 int w = ABS(v_ubound(var, 1) - v_lbound(var, 1)) + 1;
264 int size = w * h * 4;
265 auto image = (uint8_t *)malloc(size);
266 for (int y = 0; y < h; y++) {
267 int yoffs = (y * w * 4);
268 for (int x = 0; x < w; x++) {
269 int pos = y * w + x;
270 uint8_t a, r, g, b;
271 v_get_argb(v_getint(v_elem(var, pos)), a, r, g, b);
272 SET_IMAGE_ARGB(image, yoffs + (x * 4), a, r, g, b);
273 }
274 }
275 result = new ImageBuffer();
276 result->_bid = ++nextId;
277 result->_width = w;
278 result->_height = h;
279 result->_filename = nullptr;
280 result->_image = image;
281 buffers.add(result);
282 }
283 return result;
284}
285
286ImageBuffer *load_image(const unsigned char *buffer, int32_t size) {
287 ImageBuffer *result = nullptr;
288 unsigned w, h;
289 unsigned char *image;
290
291 unsigned error = decode_png(&image, &w, &h, buffer, size);
292 if (!error) {
293 result = new ImageBuffer();
294 result->_bid = ++nextId;
295 result->_width = w;
296 result->_height = h;
297 result->_filename = nullptr;
298 result->_image = image;
299 buffers.add(result);
300 } else {
301 err_throw(ERR_IMAGE_LOAD, lodepng_error_text(error));
302 }
303 return result;
304}
305
306ImageBuffer *load_image(dev_file_t *filep) {
307 ImageBuffer *result = nullptr;
308 List_each(ImageBuffer *, it, buffers) {
309 ImageBuffer *next = (*it);
310 if (next->_filename != nullptr && strcmp(next->_filename, filep->name) == 0) {
311 result = next;
312 break;
313 }
314 }
315
316 if (result == nullptr) {
317 unsigned w, h;
318 unsigned char *image;
319 unsigned error = 0;
320 unsigned network_error = 0;
321 var_t *var_p;
322
323 switch (filep->type) {
324 case ft_http_client:
325 // open "http://localhost/image1.gif" as #1
326 if (filep->handle == -1) {
327 network_error = 1;
328 } else {
329 var_p = v_new();
330 http_read(filep, var_p);
331 error = decode_png(&image, &w, &h, (unsigned char *)var_p->v.p.ptr, var_p->v.p.length);
332 v_free(var_p);
333 v_detach(var_p);
334 }
335 break;
336 case ft_stream:
337 error = decode_png_file(&image, &w, &h, filep->name);
338 break;
339 default:
340 error = 1;
341 break;
342 }
343 if (network_error) {
344 err_throw(ERR_IMAGE_LOAD, ERR_NETWORK);
345 } else if (error) {
346 err_throw(ERR_IMAGE_LOAD, lodepng_error_text(error));
347 } else {
348 result = new ImageBuffer();
349 result->_bid = ++nextId;
350 result->_width = w;
351 result->_height = h;
352 result->_filename = strdup(filep->name);
353 result->_image = image;
354 buffers.add(result);
355 }
356 }
357 return result;
358}
359
360ImageBuffer *load_xpm_image(char **data) {
361 unsigned w, h;
362 unsigned char *image;
363 unsigned error = xpm_decode32(&image, &w, &h, data);
364 ImageBuffer *result = nullptr;
365 if (!error) {
366 result = new ImageBuffer();
367 result->_bid = ++nextId;
368 result->_width = w;
369 result->_height = h;
370 result->_filename = nullptr;
371 result->_image = image;
372 buffers.add(result);
373 } else {
374 err_throw(ERR_IMAGE_LOAD, ERR_XPM_IMAGE);
375 }
376 return result;
377}
378
379void get_image_display(var_s *self, ImageDisplay *image) {
380 image->_bid = map_get_int(self, IMG_BID, -1);
381
382 List_each(ImageBuffer *, it, buffers) {
383 ImageBuffer *next = (*it);
384 if (next->_bid == image->_bid) {
385 image->_buffer = next;
386 break;
387 }
388 }
389
390 var_int_t x, y, z, op;
391 int count = par_massget("iiii", &x, &y, &z, &op);
392
393 if (prog_error || image->_buffer == nullptr || count == 1 || count > 4) {
394 err_throw(ERR_PARAM);
395 } else {
396 // 0, 2, 3, 4 arguments accepted
397 if (count >= 2) {
398 image->_x = x;
399 image->_y = y;
400 map_set_int(self, IMG_X, x);
401 map_set_int(self, IMG_Y, y);
402 } else {
403 image->_x = map_get_int(self, IMG_X, -1);
404 image->_y = map_get_int(self, IMG_Y, -1);
405 }
406 if (count >= 3) {
407 image->_zIndex = z;
408 map_set_int(self, IMG_ZINDEX, z);
409 } else {
410 image->_zIndex = map_get_int(self, IMG_ZINDEX, -1);
411 }
412 if (count == 4) {
413 image->_opacity = op;
414 map_set_int(self, IMG_OPACITY, op);
415 } else {
416 image->_opacity = map_get_int(self, IMG_OPACITY, -1);
417 }
418
419 image->_offsetLeft = map_get_int(self, IMG_OFFSET_LEFT, -1);
420 image->_offsetTop = map_get_int(self, IMG_OFFSET_TOP, -1);
421 image->_width = map_get_int(self, IMG_WIDTH, -1);
422 image->_height = map_get_int(self, IMG_HEIGHT, -1);
423 image->_id = map_get_int(self, IMG_ID, -1);
424 }
425}
426
427//
428// png.show(x, y, zindex, opacity)
429//
430void cmd_image_show(var_s *self, var_s *) {
431 ImageDisplay image;
432 get_image_display(self, &image);
433 if (!prog_error) {
434 g_system->getOutput()->addImage(image);
435 }
436}
437
438//
439// png.draw(x, y, opacity)
440//
441void cmd_image_draw(var_s *self, var_s *) {
442 ImageDisplay image;
443 get_image_display(self, &image);
444 if (!prog_error) {
445 image._opacity = image._zIndex;
446 g_system->getOutput()->drawImage(image);
447 }
448}
449
450//
451// png.hide()
452//
453void cmd_image_hide(var_s *self, var_s *) {
454 int id = map_get_int(self, IMG_ID, -1);
455 g_system->getOutput()->removeImage(id);
456}
457
458//
459// Output the image to a PNG file
460//
461// png.save("horse1.png")
462// png.save(#1)
463//
464void cmd_image_save(var_s *self, var_s *) {
465 unsigned id = map_get_int(self, IMG_BID, -1);
466 ImageBuffer *image = get_image(id);
467 var_t *array = nullptr;
468 dev_file_t *file = nullptr;
469 if (code_peek() == kwTYPE_SEP) {
470 file = eval_filep();
471 } else {
472 array = par_getvar_ptr();
473 }
474
475 bool saved = false;
476 if (!prog_error && image != nullptr) {
477 unsigned w = image->_width;
478 unsigned h = image->_height;
479 if (file != nullptr && file->open_flags == DEV_FILE_OUTPUT) {
480 if (!encode_png_file(file->name, image->_image, w, h)) {
481 saved = true;
482 }
483 } else if (array != nullptr) {
484 v_tomatrix(array, h, w);
485 // x0 x1 x2 (w=3,h=2)
486 // y0 rgba rgba rgba ypos=0
487 // y1 rgba rgba rgba ypos=12
488 //
489 for (unsigned y = 0; y < h; y++) {
490 unsigned yoffs = (y * w * 4);
491 for (unsigned x = 0; x < w; x++) {
492 uint8_t a, r, g, b;
493 GET_IMAGE_ARGB(image->_image, yoffs + (x * 4), a, r, g, b);
494 pixel_t px = v_get_argb_px(a, r, g, b);
495 unsigned pos = y * w + x;
496 v_setint(v_elem(array, pos), px);
497 }
498 }
499 saved = true;
500 }
501 }
502
503 if (!saved) {
504 err_throw(ERR_IMAGE_SAVE);
505 }
506}
507
508
509//
510// Reduces the size of the image
511// arguments: left, top, right, bottom
512//
513// png.clip(10, 10, 10, 10)
514//
515void cmd_image_clip(var_s *self, var_s *) {
516 if (self->type == V_MAP) {
517 int bid = map_get_int(self, IMG_BID, -1);
518 if (bid != -1) {
519 ImageBuffer *image = get_image((unsigned)bid);
520 var_int_t left, top, right, bottom;
521 if (image != nullptr && par_massget("iiii", &left, &top, &right, &bottom)) {
522 map_set_int(self, IMG_OFFSET_LEFT, left);
523 map_set_int(self, IMG_OFFSET_TOP, top);
524 map_set_int(self, IMG_WIDTH, right);
525 map_set_int(self, IMG_HEIGHT, bottom);
526 }
527 }
528 }
529}
530
531void create_image(var_p_t var, ImageBuffer *image) {
532 map_init(var);
533 map_add_var(var, IMG_X, 0);
534 map_add_var(var, IMG_Y, 0);
535 map_add_var(var, IMG_OFFSET_TOP, 0);
536 map_add_var(var, IMG_OFFSET_LEFT, 0);
537 map_add_var(var, IMG_ZINDEX, 100);
538 map_add_var(var, IMG_OPACITY, 0);
539 map_add_var(var, IMG_ID, ++nextId);
540 map_add_var(var, IMG_WIDTH, image->_width);
541 map_add_var(var, IMG_HEIGHT, image->_height);
542 map_add_var(var, IMG_BID, image->_bid);
543 v_create_func(var, "draw", cmd_image_draw);
544 v_create_func(var, "hide", cmd_image_hide);
545 v_create_func(var, "save", cmd_image_save);
546 v_create_func(var, "show", cmd_image_show);
547 v_create_func(var, "clip", cmd_image_clip);
548}
549
550// loads an image for the form image input type
551ImageDisplay *create_display_image(var_p_t var, const char *name) {
552 ImageDisplay *result = nullptr;
553 if (name != nullptr && var != nullptr) {
554 dev_file_t file;
555 strlcpy(file.name, name, sizeof(file.name));
556 file.type = ft_stream;
557 ImageBuffer *buffer = load_image(&file);
558 if (buffer != nullptr) {
559 result = new ImageDisplay();
560 result->_buffer = buffer;
561 result->_bid = buffer->_bid;
562 result->_width = buffer->_width;
563 result->_height = buffer->_height;
564 result->_zIndex = 0;
565 result->_offsetLeft = map_get_int(var, IMG_OFFSET_LEFT, -1);
566 result->_offsetTop = map_get_int(var, IMG_OFFSET_TOP, -1);
567 result->_opacity = map_get_int(var, IMG_OPACITY, -1);
568
569 if (result->_offsetLeft == -1) {
570 result->_offsetLeft = 0;
571 }
572 if (result->_offsetTop == -1) {
573 result->_offsetTop = 0;
574 }
575 if (result->_opacity == -1) {
576 result->_opacity = 0;
577 }
578 } else {
579 err_throw(ERR_IMAGE_LOAD, name);
580 }
581 } else {
582 err_throw(ERR_IMAGE_LOAD, "name field empty");
583 }
584 return result;
585}
586
587void screen_dump() {
588 int width = g_system->getOutput()->getWidth();
589 int height = g_system->getOutput()->getHeight();
590 auto image = get_image_data(0, 0, width, height);
591 if (image != nullptr) {
592 const char *path = gsb_bas_dir;
593#if defined(_ANDROID)
594 path = getenv("EXTERNAL_DIR");
595#endif
596 for (int i = 0; i < 1000; i++) {
597 String file;
598 if (strstr(path, "://") == nullptr) {
599 file.append(path);
600 }
601 if (file.lastChar() != '/') {
602 file.append("/");
603 }
604 file.append("sbasic_dump_");
605 file.append(i);
606 file.append(".png");
607 if (access(file.c_str(), R_OK) != 0) {
608 g_system->systemPrint("Saving screen to %s\n", file.c_str());
609 unsigned error = encode_png_file(file.c_str(), image, width, height);
610 if (error) {
611 g_system->systemPrint("Error: %s\n", lodepng_error_text(error));
612 }
613 break;
614 }
615 }
616 free(image);
617 }
618}
619
620extern "C" void v_create_image(var_p_t var) {
621 var_t arg;
622 ImageBuffer *image = nullptr;
623 dev_file_t *filep = nullptr;
624
625 byte code = code_peek();
626 switch (code) {
627 case kwTYPE_SEP:
628 filep = eval_filep();
629 if (filep != nullptr) {
630 image = load_image(filep);
631 }
632 break;
633
634 case kwTYPE_LINE:
635 case kwTYPE_EOC:
636 break;
637
638 default:
639 v_init(&arg);
640 eval(&arg);
641 if (arg.type == V_STR && !prog_error) {
642 dev_file_t file;
643 strlcpy(file.name, arg.v.p.ptr, sizeof(file.name));
644 file.type = ft_stream;
645 image = load_image(&file);
646 } else if (arg.type == V_ARRAY && v_asize(&arg) > 0 && !prog_error) {
647 var_p_t elem0 = v_elem(&arg, 0);
648 if (elem0->type == V_STR) {
649 char **data = new char*[v_asize(&arg)];
650 for (unsigned i = 0; i < v_asize(&arg); i++) {
651 var_p_t elem = v_elem(&arg, i);
652 data[i] = elem->v.p.ptr;
653 }
654 image = load_xpm_image(data);
655 delete [] data;
656 } else if (v_maxdim(&arg) == 2) {
657 // load from 2d array
658 image = load_image(&arg);
659 } else if (elem0->type == V_INT) {
660 unsigned char *data = new unsigned char[v_asize(&arg)];
661 for (unsigned i = 0; i < v_asize(&arg); i++) {
662 var_p_t elem = v_elem(&arg, i);
663 data[i] = (unsigned char)elem->v.i;
664 }
665 image = load_image(data, v_asize(&arg));
666 delete [] data;
667 }
668 } else if (arg.type == V_INT && !prog_error) {
669 image = load_image(arg.v.i);
670 } else {
671 image = load_image(&arg);
672 }
673 v_free(&arg);
674 break;
675 };
676
677 if (image != nullptr) {
678 create_image(var, image);
679 } else {
680 err_throw(ERR_BAD_FILE_HANDLE);
681 }
682}
683