A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * user intereface of image viewer
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/*
23 * TODO:
24 * - check magick value in file header to determine image type.
25 */
26#include "plugin.h"
27#include <lib/playback_control.h>
28#include <lib/helper.h>
29#include <lib/configfile.h>
30#include "imageviewer.h"
31#include "imageviewer_button.h"
32#include "image_decoder.h"
33
34
35#ifdef USEGSLIB
36GREY_INFO_STRUCT
37#endif
38
39/* Headings */
40#define DIR_PREV 1
41#define DIR_NEXT -1
42#define DIR_NONE 0
43
44/******************************* Globals ***********************************/
45
46/* Persistent configuration */
47#define IMGVIEW_CONFIGFILE "imageviewer.cfg"
48#define IMGVIEW_SETTINGS_MINVERSION 1
49#define IMGVIEW_SETTINGS_VERSION 2
50
51/* Slideshow times */
52#define SS_MIN_TIMEOUT 1
53#define SS_MAX_TIMEOUT 20
54#define SS_DEFAULT_TIMEOUT 5
55
56#ifdef HAVE_LCD_COLOR
57/* needed for value of settings */
58#include "jpeg/yuv2rgb.h"
59#endif
60
61static struct imgview_settings settings =
62{
63#ifdef HAVE_LCD_COLOR
64 COLOURMODE_COLOUR,
65 DITHER_NONE,
66#endif
67 SS_DEFAULT_TIMEOUT
68};
69static struct imgview_settings old_settings;
70
71static struct configdata config[] =
72{
73#ifdef HAVE_LCD_COLOR
74 { TYPE_ENUM, 0, COLOUR_NUM_MODES, { .int_p = &settings.jpeg_colour_mode },
75 "Colour Mode", (char *[]){ "Colour", "Grayscale" } },
76 { TYPE_ENUM, 0, DITHER_NUM_MODES, { .int_p = &settings.jpeg_dither_mode },
77 "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" } },
78#endif
79 { TYPE_INT, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT,
80 { .int_p = &settings.ss_timeout }, "Slideshow Time", NULL },
81};
82
83static void cb_progress(int current, int total);
84
85static struct imgdec_api iv_api = {
86 .settings = &settings,
87 .slideshow_enabled = false,
88 .running_slideshow = false,
89#ifdef DISK_SPINDOWN
90 .immediate_ata_off = false,
91#endif
92#ifdef USE_PLUG_BUF
93 .plug_buf = true,
94#endif
95
96 .cb_progress = cb_progress,
97
98#ifdef USEGSLIB
99 .gray_bitmap_part = myxlcd_ub_(gray_bitmap_part),
100#endif
101};
102
103/**************** begin Application ********************/
104
105
106/************************* Globals ***************************/
107
108#ifdef HAVE_LCD_COLOR
109static fb_data rgb_linebuf[LCD_WIDTH]; /* Line buffer for scrolling when
110 DITHER_DIFFUSION is set */
111#endif
112
113/* buffer to load image decoder */
114static unsigned char* decoder_buf;
115static size_t decoder_buf_size;
116/* the remaining free part of the buffer for loaded+resized images */
117static unsigned char* buf;
118static size_t buf_size;
119
120static int ds, ds_min, ds_max; /* downscaling and limits */
121static struct image_info image_info;
122
123/* the current full file name */
124static char np_file[MAX_PATH];
125static int curfile = -1, direction = DIR_NEXT, entries = 0;
126
127/* list of the supported image files */
128static char **file_pt;
129
130/* progress update tick */
131static long next_progress_tick;
132
133static const struct image_decoder *imgdec = NULL;
134static enum image_type image_type = IMAGE_UNKNOWN;
135
136/************************* Implementation ***************************/
137
138/* Read directory contents for scrolling. */
139static void get_pic_list(bool single_file)
140{
141 file_pt = (char **) buf;
142
143 if (single_file)
144 {
145 file_pt[0] = np_file;
146 buf_size -= sizeof(file_pt);
147 entries = 1;
148 curfile = 0;
149 return;
150 }
151
152 struct tree_context *tree = rb->tree_get_context();
153 struct entry *dircache = rb->tree_get_entries(tree);
154 int i;
155 char *pname;
156
157 /* Remove path and leave only the name.*/
158 pname = rb->strrchr(np_file,'/');
159 pname++;
160
161 for (i = 0; i < tree->filesindir && buf_size > sizeof(char**); i++)
162 {
163 /* Add all files. Non-image files will be filtered out while loading. */
164 if (!(dircache[i].attr & ATTR_DIRECTORY))
165 {
166 file_pt[entries] = dircache[i].name;
167 /* Set Selected File. */
168 if (!rb->strcmp(file_pt[entries], pname))
169 curfile = entries;
170 entries++;
171
172 buf += (sizeof(char**));
173 buf_size -= (sizeof(char**));
174 }
175 }
176}
177
178static int change_filename(int direct)
179{
180 bool file_erased = (file_pt[curfile] == NULL);
181 direction = direct;
182
183 curfile += (direct == DIR_PREV? entries - 1: 1);
184 if (curfile >= entries)
185 curfile -= entries;
186
187 if (file_erased)
188 {
189 /* remove 'erased' file names from list. */
190 int count, i;
191 for (count = i = 0; i < entries; i++)
192 {
193 if (curfile == i)
194 curfile = count;
195 if (file_pt[i] != NULL)
196 file_pt[count++] = file_pt[i];
197 }
198 entries = count;
199 }
200
201 if (entries == 0)
202 {
203 rb->splash(HZ, "No supported files");
204 return PLUGIN_ERROR;
205 }
206
207 size_t np_file_length = rb->strlen(np_file);
208 size_t np_file_name_length = rb->strlen(rb->strrchr(np_file, '/')+1);
209 size_t avail_length = sizeof(np_file) - (np_file_length - np_file_name_length);
210
211 rb->snprintf(rb->strrchr(np_file, '/')+1, avail_length, "%s", file_pt[curfile]);
212
213 return PLUGIN_OTHER;
214}
215
216/* switch off overlay, for handling SYS_ events */
217static void cleanup(void *parameter)
218{
219 (void)parameter;
220#ifdef USEGSLIB
221 grey_show(false);
222#endif
223}
224
225#ifdef HAVE_LCD_COLOR
226static bool set_option_grayscale(void)
227{
228 bool gray = settings.jpeg_colour_mode == COLOURMODE_GRAY;
229 rb->set_bool("Grayscale (Jpeg)", &gray);
230 settings.jpeg_colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR;
231 return false;
232}
233
234static bool set_option_dithering(void)
235{
236 static const struct opt_items dithering[DITHER_NUM_MODES] = {
237 [DITHER_NONE] = { STR(LANG_OFF) },
238 [DITHER_ORDERED] = { STR(LANG_ORDERED) },
239 [DITHER_DIFFUSION] = { STR(LANG_DIFFUSION) },
240 };
241
242 rb->set_option(rb->str(LANG_DITHERING), &settings.jpeg_dither_mode, RB_INT,
243 dithering, DITHER_NUM_MODES, NULL);
244 return false;
245}
246
247MENUITEM_FUNCTION(grayscale_item, 0, ID2P(LANG_GRAYSCALE),
248 set_option_grayscale, NULL, Icon_NOICON);
249MENUITEM_FUNCTION(dithering_item, 0, ID2P(LANG_DITHERING),
250 set_option_dithering, NULL, Icon_NOICON);
251MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON,
252 &grayscale_item, &dithering_item);
253
254static void display_options(void)
255{
256 rb->do_menu(&display_menu, NULL, NULL, false);
257}
258#endif /* HAVE_LCD_COLOR */
259
260static int show_menu(void) /* return 1 to quit */
261{
262 int result;
263
264 enum menu_id
265 {
266 MIID_RETURN = 0,
267 MIID_TOGGLE_SS_MODE,
268 MIID_CHANGE_SS_MODE,
269#ifdef USE_PLUG_BUF
270 MIID_SHOW_PLAYBACK_MENU,
271#endif
272#ifdef HAVE_LCD_COLOR
273 MIID_DISPLAY_OPTIONS,
274#endif
275 MIID_QUIT,
276 };
277
278 MENUITEM_STRINGLIST(menu, "Image Viewer Menu", NULL,
279 ID2P(LANG_RETURN),
280 ID2P(LANG_SLIDESHOW_MODE),
281 ID2P(LANG_SLIDESHOW_TIME),
282#ifdef USE_PLUG_BUF
283 ID2P(LANG_PLAYBACK_CONTROL),
284#endif
285#ifdef HAVE_LCD_COLOR
286 ID2P(LANG_MENU_DISPLAY_OPTIONS),
287#endif
288 ID2P(LANG_MENU_QUIT));
289
290 static const struct opt_items slideshow[2] = {
291 { STR(LANG_OFF) },
292 { STR(LANG_ON) },
293 };
294
295 result=rb->do_menu(&menu, NULL, NULL, false);
296
297 switch (result)
298 {
299 case MIID_RETURN:
300 break;
301 case MIID_TOGGLE_SS_MODE:
302 rb->set_option(rb->str(LANG_SLIDESHOW_MODE), &iv_api.slideshow_enabled, RB_BOOL,
303 slideshow , 2, NULL);
304 break;
305 case MIID_CHANGE_SS_MODE:
306 rb->set_int(rb->str(LANG_SLIDESHOW_TIME), "s", UNIT_SEC,
307 &settings.ss_timeout, NULL, 1,
308 SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL);
309 break;
310
311#ifdef USE_PLUG_BUF
312 case MIID_SHOW_PLAYBACK_MENU:
313 if (iv_api.plug_buf)
314 {
315 playback_control(NULL);
316 }
317 else
318 {
319 rb->splash(HZ, ID2P(LANG_CANNOT_RESTART_PLAYBACK));
320 }
321 break;
322#endif
323#ifdef HAVE_LCD_COLOR
324 case MIID_DISPLAY_OPTIONS:
325 display_options();
326 break;
327#endif
328 case MIID_QUIT:
329 return 1;
330 break;
331 }
332
333#ifdef DISK_SPINDOWN
334 /* change ata spindown time based on slideshow time setting */
335 iv_api.immediate_ata_off = false;
336 rb->storage_spindown(rb->global_settings->disk_spindown);
337
338 if (iv_api.slideshow_enabled)
339 {
340 if(settings.ss_timeout < 10)
341 {
342 /* slideshow times < 10s keep disk spinning */
343 rb->storage_spindown(0);
344 }
345 else if (!rb->pcm_is_playing())
346 {
347 /* slideshow times > 10s and not playing: ata_off after load */
348 iv_api.immediate_ata_off = true;
349 }
350 }
351#endif
352#if LCD_DEPTH > 1
353 rb->lcd_set_backdrop(NULL);
354 rb->lcd_set_foreground(LCD_WHITE);
355 rb->lcd_set_background(LCD_BLACK);
356#endif
357 rb->lcd_clear_display();
358 return 0;
359}
360
361#ifdef USE_PLUG_BUF
362static int ask_and_get_audio_buffer(const char *filename)
363{
364 int button;
365#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_QUIT_PRE)
366 int lastbutton = BUTTON_NONE;
367#endif
368 rb->lcd_setfont(FONT_SYSFIXED);
369 rb->lcd_clear_display();
370 rb->lcd_puts(0, 0, rb->strrchr(filename,'/')+1);
371 rb->lcd_puts(0, 1, "Not enough plugin memory!");
372 rb->lcd_puts(0, 2, "Zoom In: Stop playback.");
373 if(entries > 1)
374 rb->lcd_puts(0, 3, "Left/Right: Skip File.");
375 rb->lcd_puts(0, 4, "Show Menu: Quit.");
376 rb->lcd_update();
377 rb->lcd_setfont(FONT_UI);
378
379 rb->button_clear_queue();
380
381 while (1)
382 {
383 if (iv_api.slideshow_enabled)
384 button = rb->button_get_w_tmo(settings.ss_timeout * HZ);
385 else
386 button = rb->button_get(true);
387
388 switch(button)
389 {
390 case IMGVIEW_ZOOM_IN:
391#ifdef IMGVIEW_ZOOM_PRE
392 if (lastbutton != IMGVIEW_ZOOM_PRE)
393 break;
394#endif
395 iv_api.plug_buf = false;
396 buf = rb->plugin_get_audio_buffer(&buf_size);
397 /*try again this file, now using the audio buffer */
398 return PLUGIN_OTHER;
399#ifdef IMGVIEW_RC_MENU
400 case IMGVIEW_RC_MENU:
401#endif
402#ifdef IMGVIEW_QUIT
403 case IMGVIEW_QUIT:
404#ifdef IMGVIEW_QUIT_PRE
405 if (lastbutton != IMGVIEW_QUIT_PRE)
406 break;
407#endif
408#endif
409 /* Intentional fallthrough */
410 case IMGVIEW_MENU:
411 return PLUGIN_OK;
412
413 case IMGVIEW_LEFT:
414 if(entries>1)
415 {
416 rb->lcd_clear_display();
417 return change_filename(DIR_PREV);
418 }
419 break;
420
421 case IMGVIEW_RIGHT:
422 if(entries>1)
423 {
424 rb->lcd_clear_display();
425 return change_filename(DIR_NEXT);
426 }
427 break;
428 case BUTTON_NONE:
429 if(entries>1)
430 {
431 rb->lcd_clear_display();
432 return change_filename(direction);
433 }
434 break;
435
436 default:
437 if(rb->default_event_handler_ex(button, cleanup, NULL)
438 == SYS_USB_CONNECTED)
439 return PLUGIN_USB_CONNECTED;
440 }
441#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_QUIT_PRE)
442 if (button != BUTTON_NONE)
443 lastbutton = button;
444#endif
445 }
446}
447#endif /* USE_PLUG_BUF */
448
449/* callback updating a progress meter while image decoding */
450static void cb_progress(int current, int total)
451{
452 /* do not yield or update the progress bar if we did so too recently */
453 long now = *rb->current_tick;
454 if(!TIME_AFTER(now, next_progress_tick))
455 return;
456
457 /* limit to 20fps */
458 next_progress_tick = now + HZ/20;
459
460#ifndef USEGSLIB
461 /* in slideshow mode, keep gui interference to a minimum */
462 const int size = (!iv_api.running_slideshow ? 8 : 4);
463#else
464 const int size = 8;
465 if(!iv_api.running_slideshow)
466#endif
467 {
468 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],
469 0, LCD_HEIGHT-size, LCD_WIDTH, size,
470 total, 0, current, HORIZONTAL);
471 rb->lcd_update_rect(0, LCD_HEIGHT-size, LCD_WIDTH, size);
472 }
473
474 rb->yield(); /* be nice to the other threads */
475}
476
477#define VSCROLL (LCD_HEIGHT/8)
478#define HSCROLL (LCD_WIDTH/10)
479
480/* Pan the viewing window right - move image to the left and fill in
481 the right-hand side */
482static void pan_view_right(struct image_info *info)
483{
484 int move;
485
486 move = MIN(HSCROLL, info->width - info->x - LCD_WIDTH);
487 if (move > 0)
488 {
489 mylcd_ub_scroll_left(move); /* scroll left */
490 info->x += move;
491 imgdec->draw_image_rect(info, LCD_WIDTH - move, 0,
492 move, info->height-info->y);
493 mylcd_ub_update();
494 }
495}
496
497/* Pan the viewing window left - move image to the right and fill in
498 the left-hand side */
499static void pan_view_left(struct image_info *info)
500{
501 int move;
502
503 move = MIN(HSCROLL, info->x);
504 if (move > 0)
505 {
506 mylcd_ub_scroll_right(move); /* scroll right */
507 info->x -= move;
508 imgdec->draw_image_rect(info, 0, 0, move, info->height-info->y);
509 mylcd_ub_update();
510 }
511}
512
513/* Pan the viewing window up - move image down and fill in
514 the top */
515static void pan_view_up(struct image_info *info)
516{
517 int move;
518
519 move = MIN(VSCROLL, info->y);
520 if (move > 0)
521 {
522 mylcd_ub_scroll_down(move); /* scroll down */
523 info->y -= move;
524#ifdef HAVE_LCD_COLOR
525 if (image_type == IMAGE_JPEG
526 && settings.jpeg_dither_mode == DITHER_DIFFUSION)
527 {
528 /* Draw over the band at the top of the last update
529 caused by lack of error history on line zero. */
530 move = MIN(move + 1, info->y + info->height);
531 }
532#endif
533 imgdec->draw_image_rect(info, 0, 0, info->width-info->x, move);
534 mylcd_ub_update();
535 }
536}
537
538/* Pan the viewing window down - move image up and fill in
539 the bottom */
540static void pan_view_down(struct image_info *info)
541{
542 static fb_data *lcd_fb = NULL;
543 if (!lcd_fb)
544 {
545 struct viewport *vp_main = *(rb->screens[SCREEN_MAIN]->current_viewport);
546 lcd_fb = vp_main->buffer->fb_ptr;
547 }
548
549 int move;
550
551 move = MIN(VSCROLL, info->height - info->y - LCD_HEIGHT);
552 if (move > 0)
553 {
554 mylcd_ub_scroll_up(move); /* scroll up */
555 info->y += move;
556#ifdef HAVE_LCD_COLOR
557 if (image_type == IMAGE_JPEG
558 && settings.jpeg_dither_mode == DITHER_DIFFUSION)
559 {
560 /* Save the line that was on the last line of the display
561 and draw one extra line above then recover the line with
562 image data that had an error history when it was drawn.
563 */
564 move++, info->y--;
565 rb->memcpy(rgb_linebuf,
566 lcd_fb + (LCD_HEIGHT - move)*LCD_WIDTH,
567 LCD_WIDTH*sizeof (fb_data));
568 }
569#endif
570
571 imgdec->draw_image_rect(info, 0, LCD_HEIGHT - move,
572 info->width-info->x, move);
573
574#ifdef HAVE_LCD_COLOR
575 if (image_type == IMAGE_JPEG
576 && settings.jpeg_dither_mode == DITHER_DIFFUSION)
577 {
578 /* Cover the first row drawn with previous image data. */
579 rb->memcpy(lcd_fb + (LCD_HEIGHT - move)*LCD_WIDTH,
580 rgb_linebuf, LCD_WIDTH*sizeof (fb_data));
581 info->y++;
582 }
583#endif
584 mylcd_ub_update();
585 }
586}
587
588/* interactively scroll around the image */
589static int scroll_bmp(struct image_info *info, bool initial_frame)
590{
591 static long ss_timeout = 0;
592
593 int button;
594#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) \
595 || defined(IMGVIEW_SLIDE_SHOW_PRE) || defined(IMGVIEW_QUIT_PRE)
596 static int lastbutton;
597 if (initial_frame)
598 lastbutton = BUTTON_NONE;
599
600#else
601 (void) initial_frame;
602#endif
603
604 if (!ss_timeout && iv_api.slideshow_enabled)
605 ss_timeout = *rb->current_tick + settings.ss_timeout * HZ;
606
607 while (true)
608 {
609 if (iv_api.slideshow_enabled)
610 {
611 if (info->frames_count > 1 && info->delay &&
612 settings.ss_timeout * HZ > info->delay)
613 {
614 /* animated content and delay between subsequent frames
615 * is shorter then slideshow delay
616 */
617 button = rb->button_get_w_tmo(info->delay);
618 }
619 else
620 button = rb->button_get_w_tmo(settings.ss_timeout * HZ);
621 }
622 else
623 {
624 if (info->frames_count > 1 && info->delay)
625 button = rb->button_get_w_tmo(info->delay);
626 else
627 button = rb->button_get(true);
628 }
629
630 iv_api.running_slideshow = false;
631
632 switch(button)
633 {
634 case IMGVIEW_LEFT:
635 if (entries > 1 && info->width <= LCD_WIDTH
636 && info->height <= LCD_HEIGHT)
637 {
638 int result = change_filename(DIR_PREV);
639 if (entries > 1)
640 return result;
641 }
642 /* fallthrough */
643 case IMGVIEW_LEFT | BUTTON_REPEAT:
644 pan_view_left(info);
645 break;
646
647 case IMGVIEW_RIGHT:
648 if (entries > 1 && info->width <= LCD_WIDTH
649 && info->height <= LCD_HEIGHT)
650 {
651 int result = change_filename(DIR_NEXT);
652 if (entries > 1)
653 return result;
654 }
655 /* fallthrough */
656 case IMGVIEW_RIGHT | BUTTON_REPEAT:
657 pan_view_right(info);
658 break;
659
660 case IMGVIEW_UP:
661 case IMGVIEW_UP | BUTTON_REPEAT:
662#ifdef IMGVIEW_SCROLL_UP
663 case IMGVIEW_SCROLL_UP:
664 case IMGVIEW_SCROLL_UP | BUTTON_REPEAT:
665#endif
666 pan_view_up(info);
667 break;
668
669 case IMGVIEW_DOWN:
670 case IMGVIEW_DOWN | BUTTON_REPEAT:
671#ifdef IMGVIEW_SCROLL_DOWN
672 case IMGVIEW_SCROLL_DOWN:
673 case IMGVIEW_SCROLL_DOWN | BUTTON_REPEAT:
674#endif
675 pan_view_down(info);
676 break;
677
678 case BUTTON_NONE:
679 if (iv_api.slideshow_enabled && entries > 1)
680 {
681 if (info->frames_count > 1)
682 {
683 /* animations */
684 if (TIME_AFTER(*rb->current_tick, ss_timeout))
685 {
686 iv_api.running_slideshow = true;
687 ss_timeout = 0;
688 return change_filename(DIR_NEXT);
689 }
690 else
691 return NEXT_FRAME;
692 }
693 else
694 {
695 /* still picture */
696 iv_api.running_slideshow = true;
697 return change_filename(DIR_NEXT);
698 }
699 }
700 else
701 return NEXT_FRAME;
702
703 break;
704
705#ifdef IMGVIEW_SLIDE_SHOW
706 case IMGVIEW_SLIDE_SHOW:
707#ifdef IMGVIEW_SLIDE_SHOW_PRE
708 if (lastbutton != IMGVIEW_SLIDE_SHOW_PRE)
709 break;
710#endif
711#ifdef IMGVIEW_SLIDE_SHOW2
712 case IMGVIEW_SLIDE_SHOW2:
713#endif
714 iv_api.slideshow_enabled = !iv_api.slideshow_enabled;
715 break;
716#endif
717
718#ifdef IMGVIEW_NEXT_REPEAT
719 case IMGVIEW_NEXT_REPEAT:
720#endif
721 case IMGVIEW_NEXT:
722 if (entries > 1)
723 return change_filename(DIR_NEXT);
724 break;
725
726#ifdef IMGVIEW_PREVIOUS_REPEAT
727 case IMGVIEW_PREVIOUS_REPEAT:
728#endif
729 case IMGVIEW_PREVIOUS:
730 if (entries > 1)
731 return change_filename(DIR_PREV);
732 break;
733
734 case IMGVIEW_ZOOM_IN:
735#ifdef IMGVIEW_ZOOM_PRE
736 if (lastbutton != IMGVIEW_ZOOM_PRE)
737 break;
738 lastbutton = button;
739#endif
740 return ZOOM_IN;
741 break;
742
743 case IMGVIEW_ZOOM_OUT:
744#ifdef IMGVIEW_ZOOM_PRE
745 if (lastbutton != IMGVIEW_ZOOM_PRE)
746 break;
747 lastbutton = button;
748#endif
749 return ZOOM_OUT;
750 break;
751
752#ifdef IMGVIEW_RC_MENU
753 case IMGVIEW_RC_MENU:
754#endif
755 case IMGVIEW_MENU:
756#ifdef IMGVIEW_MENU_PRE
757 if (lastbutton != IMGVIEW_MENU_PRE)
758 break;
759#endif
760#ifdef USEGSLIB
761 grey_show(false); /* switch off greyscale overlay */
762#endif
763 if (show_menu() == 1)
764 return PLUGIN_OK;
765
766#ifdef USEGSLIB
767 grey_show(true); /* switch on greyscale overlay */
768#else
769 imgdec->draw_image_rect(info, 0, 0,
770 info->width-info->x, info->height-info->y);
771 mylcd_ub_update();
772#endif
773 break;
774
775#ifdef IMGVIEW_QUIT
776 case IMGVIEW_QUIT:
777#ifdef IMGVIEW_QUIT_PRE
778 if (lastbutton != IMGVIEW_QUIT_PRE)
779 break;
780#endif
781 return PLUGIN_OK;
782 break;
783#endif
784
785 default:
786 if (rb->default_event_handler_ex(button, cleanup, NULL)
787 == SYS_USB_CONNECTED)
788 return PLUGIN_USB_CONNECTED;
789 break;
790
791 } /* switch */
792#if defined(IMGVIEW_ZOOM_PRE) || defined(IMGVIEW_MENU_PRE) ||\
793 defined(IMGVIEW_SLIDE_SHOW_PRE) || defined(IMGVIEW_QUIT_PRE)
794 if (button != BUTTON_NONE)
795 lastbutton = button;
796#endif
797 } /* while (true) */
798}
799
800/********************* main function *************************/
801
802/* how far can we zoom in without running out of memory */
803static int min_downscale(int bufsize)
804{
805 int downscale = 8;
806
807 if (imgdec->img_mem(8) > bufsize)
808 return 0; /* error, too large, even 1:8 doesn't fit */
809
810 while (downscale > 1 && imgdec->img_mem(downscale/2) <= bufsize)
811 downscale /= 2;
812
813 return downscale;
814}
815
816/* how far can we zoom out, to fit image into the LCD */
817static int max_downscale(struct image_info *info)
818{
819 int downscale = 1;
820
821 while (downscale < 8 && (info->x_size/downscale > LCD_WIDTH
822 || info->y_size/downscale > LCD_HEIGHT))
823 {
824 downscale *= 2;
825 }
826
827 return downscale;
828}
829
830/* set the view to the given center point, limit if necessary */
831static void set_view(struct image_info *info, int cx, int cy)
832{
833 int x, y;
834
835 /* plain center to available width/height */
836 x = cx - MIN(LCD_WIDTH, info->width) / 2;
837 y = cy - MIN(LCD_HEIGHT, info->height) / 2;
838
839 /* limit against upper image size */
840 x = MIN(info->width - LCD_WIDTH, x);
841 y = MIN(info->height - LCD_HEIGHT, y);
842
843 /* limit against negative side */
844 x = MAX(0, x);
845 y = MAX(0, y);
846
847 info->x = x; /* set the values */
848 info->y = y;
849}
850
851/* calculate the view center based on the bitmap position */
852static void get_view(struct image_info *info, int *p_cx, int *p_cy)
853{
854 *p_cx = info->x + MIN(LCD_WIDTH, info->width) / 2;
855 *p_cy = info->y + MIN(LCD_HEIGHT, info->height) / 2;
856}
857
858/* load, decode, display the image */
859static int load_and_show(char *filename, struct image_info *info,
860 int offset, int filesize, int status)
861{
862 int cx, cy;
863 ssize_t remaining;
864
865 if (status == IMAGE_UNKNOWN) {
866 /* file isn't supported image file, skip this. */
867 file_pt[curfile] = NULL;
868 return change_filename(direction);
869 }
870
871reload_decoder:
872 rb->lcd_clear_display();
873
874 if (image_type != status) /* type of image is changed, load decoder. */
875 {
876 struct loader_info loader_info = {
877 status, &iv_api, decoder_buf, decoder_buf_size,
878 };
879 image_type = status;
880 imgdec = load_decoder(&loader_info);
881 if (imgdec == NULL)
882 {
883 /* something is wrong */
884 return PLUGIN_ERROR;
885 }
886#ifdef USE_PLUG_BUF
887 if(iv_api.plug_buf)
888 {
889 buf = loader_info.buffer;
890 buf_size = loader_info.size;
891 }
892#endif
893 }
894 rb->memset(info, 0, sizeof(*info));
895 remaining = buf_size;
896
897 if (rb->button_get(false) == IMGVIEW_MENU)
898 status = PLUGIN_ABORT;
899 else
900 status = imgdec->load_image(filename, info, buf, &remaining, offset, filesize);
901
902 if (status == PLUGIN_JPEG_PROGRESSIVE)
903 {
904 status = IMAGE_JPEG_PROGRESSIVE;
905 goto reload_decoder;
906 }
907
908 if (status == PLUGIN_OUTOFMEM)
909 {
910#ifdef USE_PLUG_BUF
911 if(iv_api.plug_buf)
912 {
913 return ask_and_get_audio_buffer(filename);
914 }
915 else
916#endif
917 {
918 rb->splash(HZ, "Out of Memory");
919 file_pt[curfile] = NULL;
920 return change_filename(direction);
921 }
922 }
923 else if (status == PLUGIN_ERROR)
924 {
925 file_pt[curfile] = NULL;
926 return change_filename(direction);
927 }
928 else if (status == PLUGIN_ABORT) {
929 rb->splash(HZ, "Aborted");
930 return PLUGIN_OK;
931 }
932
933 ds_max = max_downscale(info); /* check display constraint */
934 ds_min = min_downscale(remaining); /* check memory constraint */
935 if (ds_min == 0)
936 {
937 if (imgdec->unscaled_avail)
938 {
939 /* Can not resize the image but original one is available, so use it. */
940 ds_min = ds_max = 1;
941 }
942 else
943#ifdef USE_PLUG_BUF
944 if (iv_api.plug_buf)
945 {
946 return ask_and_get_audio_buffer(filename);
947 }
948 else
949#endif
950 {
951 rb->splash(HZ, "Too large");
952 file_pt[curfile] = NULL;
953 return change_filename(direction);
954 }
955 }
956 else if (ds_max < ds_min)
957 ds_max = ds_min;
958
959 ds = ds_max; /* initialize setting */
960 cx = info->x_size/ds/2; /* center the view */
961 cy = info->y_size/ds/2;
962
963 /* used to loop through subimages in animated gifs */
964 int frame = 0;
965 bool initial_frame = true;
966 do /* loop the image prepare and decoding when zoomed */
967 {
968 status = imgdec->get_image(info, frame, ds); /* decode or fetch from cache */
969 if (status == PLUGIN_ERROR)
970 {
971 file_pt[curfile] = NULL;
972 return change_filename(direction);
973 }
974
975 set_view(info, cx, cy);
976
977 if(!iv_api.running_slideshow && (info->frames_count == 1))
978 {
979 rb->lcd_putsf(0, 3, "showing %dx%d", info->width, info->height);
980 rb->lcd_update();
981 }
982
983 if (frame == 0)
984 mylcd_ub_clear_display();
985 imgdec->draw_image_rect(info, 0, 0,
986 info->width-info->x, info->height-info->y);
987 mylcd_ub_update();
988
989#ifdef USEGSLIB
990 grey_show(true); /* switch on greyscale overlay */
991#endif
992
993 /* drawing is now finished, play around with scrolling
994 * until you press OFF or connect USB
995 */
996 while (1)
997 {
998 status = scroll_bmp(info, initial_frame);
999 initial_frame = false;
1000
1001 if (status == ZOOM_IN)
1002 {
1003 if (ds > ds_min || (imgdec->unscaled_avail && ds > 1))
1004 {
1005 /* if 1/1 is always available, jump ds from ds_min to 1. */
1006 int zoom = (ds == ds_min)? ds_min: 2;
1007 ds /= zoom; /* reduce downscaling to zoom in */
1008 get_view(info, &cx, &cy);
1009 cx *= zoom; /* prepare the position in the new image */
1010 cy *= zoom;
1011 }
1012 else
1013 continue;
1014 }
1015
1016 if (status == ZOOM_OUT)
1017 {
1018 if (ds < ds_max)
1019 {
1020 /* if ds is 1 and ds_min is > 1, jump ds to ds_min. */
1021 int zoom = (ds < ds_min)? ds_min: 2;
1022 ds *= zoom; /* increase downscaling to zoom out */
1023 get_view(info, &cx, &cy);
1024 cx /= zoom; /* prepare the position in the new image */
1025 cy /= zoom;
1026 mylcd_ub_clear_display();
1027 }
1028 else
1029 continue;
1030 }
1031
1032 /* next frame in animated content */
1033 if (status == NEXT_FRAME)
1034 frame = (frame + 1)%info->frames_count;
1035
1036 break;
1037 }
1038
1039#ifdef USEGSLIB
1040 if (info->frames_count <= 1)
1041 grey_show(false); /* switch off overlay */
1042#endif
1043 rb->lcd_clear_display();
1044 }
1045 while (status > PLUGIN_OTHER);
1046#ifdef USEGSLIB
1047 grey_show(false); /* switch off overlay */
1048 rb->lcd_update();
1049#endif
1050 return status;
1051}
1052
1053static bool find_album_art(int *offset, int *filesize, int *status)
1054{
1055#ifndef HAVE_ALBUMART
1056 (void)offset;(void)filesize;(void)status;
1057 return false;
1058#else
1059 struct mp3entry *current_track = rb->audio_current_track();
1060
1061 if (current_track == NULL)
1062 {
1063 return false;
1064 }
1065
1066 switch (current_track->albumart.type)
1067 {
1068 case AA_TYPE_BMP:
1069 (*status) = IMAGE_BMP;
1070 break;
1071 case AA_TYPE_PNG:
1072 (*status) = IMAGE_PNG;
1073 break;
1074 case AA_TYPE_JPG:
1075 (*status) = IMAGE_JPEG;
1076 break;
1077 default:
1078 (*status) = IMAGE_UNKNOWN;
1079 }
1080
1081 if (IMAGE_UNKNOWN == *status
1082 || AA_PREFER_IMAGE_FILE == rb->global_settings->album_art)
1083 {
1084 if (rb->search_albumart_files(current_track, "", np_file, MAX_PATH))
1085 {
1086 (*status) = get_image_type(np_file, false);
1087 return true;
1088 }
1089
1090 if (*status == IMAGE_UNKNOWN)
1091 return false;
1092 }
1093 rb->strcpy(np_file, current_track->path);
1094 (*offset) = current_track->albumart.pos;
1095 (*filesize) = current_track->albumart.size;
1096 return true;
1097#endif
1098}
1099
1100/******************** Plugin entry point *********************/
1101
1102enum plugin_status plugin_start(const void* parameter)
1103{
1104 int condition;
1105#ifdef USEGSLIB
1106 long greysize; /* helper */
1107#endif
1108
1109 int offset = 0, filesize = 0, status;
1110
1111 bool is_album_art = false;
1112 if (!parameter)
1113 {
1114 if (!find_album_art(&offset, &filesize, &status))
1115 {
1116 rb->splash(HZ * 2, "No file");
1117 return PLUGIN_ERROR;
1118 }
1119
1120 is_album_art = true;
1121 }
1122 else
1123 {
1124 rb->strcpy(np_file, parameter);
1125 if ((status = get_image_type(np_file, false)) == IMAGE_UNKNOWN)
1126 {
1127 rb->splash(HZ * 2, "Unsupported file");
1128 return PLUGIN_ERROR;
1129 }
1130 }
1131
1132#ifdef USE_PLUG_BUF
1133 buf = rb->plugin_get_buffer(&buf_size);
1134#else
1135 decoder_buf = rb->plugin_get_buffer(&decoder_buf_size);
1136 buf = rb->plugin_get_audio_buffer(&buf_size);
1137#endif
1138 get_pic_list(is_album_art);
1139
1140#ifdef USEGSLIB
1141 if (!grey_init(buf, buf_size, GREY_ON_COP,
1142 LCD_WIDTH, LCD_HEIGHT, &greysize))
1143 {
1144 rb->splash(HZ, "grey buf error");
1145 return PLUGIN_ERROR;
1146 }
1147 buf += greysize;
1148 buf_size -= greysize;
1149#endif
1150
1151#ifdef USE_PLUG_BUF
1152 decoder_buf = buf;
1153 decoder_buf_size = buf_size;
1154 if(!rb->audio_status())
1155 {
1156 iv_api.plug_buf = false;
1157 buf = rb->plugin_get_audio_buffer(&buf_size);
1158 }
1159#endif
1160
1161 /* should be ok to just load settings since the plugin itself has
1162 just been loaded from disk and the drive should be spinning */
1163 configfile_load(IMGVIEW_CONFIGFILE, config,
1164 ARRAYLEN(config), IMGVIEW_SETTINGS_MINVERSION);
1165 rb->memcpy(&old_settings, &settings, sizeof (settings));
1166
1167 /* Turn off backlight timeout */
1168 backlight_ignore_timeout();
1169
1170#if LCD_DEPTH > 1
1171 rb->lcd_set_backdrop(NULL);
1172 rb->lcd_set_foreground(LCD_WHITE);
1173 rb->lcd_set_background(LCD_BLACK);
1174#endif
1175
1176 do
1177 {
1178 condition = load_and_show(np_file, &image_info, offset, filesize, status);
1179 if (condition >= PLUGIN_OTHER)
1180 {
1181 if(!is_album_art)
1182 {
1183 /* suppress warning while running slideshow */
1184 status = get_image_type(np_file, iv_api.running_slideshow);
1185 }
1186 continue;
1187 }
1188 break;
1189 } while (true);
1190 release_decoder();
1191
1192 if (rb->memcmp(&settings, &old_settings, sizeof (settings)))
1193 {
1194 /* Just in case drive has to spin, keep it from looking locked */
1195 rb->splash(0, "Saving Settings");
1196 configfile_save(IMGVIEW_CONFIGFILE, config,
1197 ARRAYLEN(config), IMGVIEW_SETTINGS_VERSION);
1198 }
1199
1200#ifdef DISK_SPINDOWN
1201 /* set back ata spindown time in case we changed it */
1202 rb->storage_spindown(rb->global_settings->disk_spindown);
1203#endif
1204
1205 /* Turn on backlight timeout (revert to settings) */
1206 backlight_use_settings();
1207
1208#ifdef USEGSLIB
1209 grey_release(); /* deinitialize */
1210#endif
1211
1212 return condition;
1213}