1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * user intereface of image viewers (jpeg, png, etc.)
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
23 #include <lib/playback_control.h>
24 #include <lib/helper.h>
25 #include <lib/configfile.h>
26 #include "imageviewer.h"
39 /******************************* Globals ***********************************/
41 bool slideshow_enabled
= false; /* run slideshow */
42 bool running_slideshow
= false; /* loading image because of slideshow */
44 bool immediate_ata_off
= false; /* power down disk after loading */
47 /* are we using the plugin buffer or the audio buffer? */
51 /* Persistent configuration */
52 #define IMGVIEW_CONFIGFILE "imageviewer.cfg"
53 #define IMGVIEW_SETTINGS_MINVERSION 1
54 #define IMGVIEW_SETTINGS_VERSION 2
57 #define SS_MIN_TIMEOUT 1
58 #define SS_MAX_TIMEOUT 20
59 #define SS_DEFAULT_TIMEOUT 5
62 /* needed for value of settings */
63 #include "jpeg/yuv2rgb.h"
67 struct imgview_settings settings
=
75 static struct imgview_settings old_settings
;
77 static struct configdata config
[] =
80 { TYPE_ENUM
, 0, COLOUR_NUM_MODES
, { .int_p
= &settings
.jpeg_colour_mode
},
81 "Colour Mode", (char *[]){ "Colour", "Grayscale" } },
82 { TYPE_ENUM
, 0, DITHER_NUM_MODES
, { .int_p
= &settings
.jpeg_dither_mode
},
83 "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" } },
85 { TYPE_INT
, SS_MIN_TIMEOUT
, SS_MAX_TIMEOUT
,
86 { .int_p
= &settings
.ss_timeout
}, "Slideshow Time", NULL
},
89 /**************** begin Application ********************/
92 /************************* Globals ***************************/
94 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
95 static fb_data rgb_linebuf
[LCD_WIDTH
]; /* Line buffer for scrolling when
96 DITHER_DIFFUSION is set */
99 /* my memory pool (from the mp3 buffer) */
100 static char print
[32]; /* use a common snprintf() buffer */
101 /* the remaining free part of the buffer for loaded+resized images */
102 static unsigned char* buf
;
103 static size_t buf_size
;
105 static int ds
, ds_min
, ds_max
; /* downscaling and limits */
106 static struct image_info image_info
;
108 /* the current full file name */
109 static char np_file
[MAX_PATH
];
110 static int curfile
= 0, direction
= DIR_NEXT
, entries
= 0;
112 /* list of the supported image files */
113 static char **file_pt
;
115 /************************* Implementation ***************************/
117 /* Read directory contents for scrolling. */
118 static void get_pic_list(void)
120 struct tree_context
*tree
= rb
->tree_get_context();
121 struct entry
*dircache
= tree
->dircache
;
125 file_pt
= (char **) buf
;
127 /* Remove path and leave only the name.*/
128 pname
= rb
->strrchr(np_file
,'/');
131 for (i
= 0; i
< tree
->filesindir
&& buf_size
> sizeof(char**); i
++)
133 if (!(dircache
[i
].attr
& ATTR_DIRECTORY
)
134 && img_ext(rb
->strrchr(dircache
[i
].name
,'.')))
136 file_pt
[entries
] = dircache
[i
].name
;
137 /* Set Selected File. */
138 if (!rb
->strcmp(file_pt
[entries
], pname
))
142 buf
+= (sizeof(char**));
143 buf_size
-= (sizeof(char**));
148 static int change_filename(int direct
)
150 bool file_erased
= (file_pt
[curfile
] == NULL
);
153 curfile
+= (direct
== DIR_PREV
? entries
- 1: 1);
154 if (curfile
>= entries
)
159 /* remove 'erased' file names from list. */
161 for (count
= i
= 0; i
< entries
; i
++)
165 if (file_pt
[i
] != NULL
)
166 file_pt
[count
++] = file_pt
[i
];
173 rb
->splash(HZ
, "No supported files");
177 rb
->strcpy(rb
->strrchr(np_file
, '/')+1, file_pt
[curfile
]);
182 /* switch off overlay, for handling SYS_ events */
183 static void cleanup(void *parameter
)
191 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
192 static bool set_option_grayscale(void)
194 bool gray
= settings
.jpeg_colour_mode
== COLOURMODE_GRAY
;
195 rb
->set_bool("Grayscale", &gray
);
196 settings
.jpeg_colour_mode
= gray
? COLOURMODE_GRAY
: COLOURMODE_COLOUR
;
200 static bool set_option_dithering(void)
202 static const struct opt_items dithering
[DITHER_NUM_MODES
] = {
203 [DITHER_NONE
] = { "Off", -1 },
204 [DITHER_ORDERED
] = { "Ordered", -1 },
205 [DITHER_DIFFUSION
] = { "Diffusion", -1 },
208 rb
->set_option("Dithering", &settings
.jpeg_dither_mode
, INT
,
209 dithering
, DITHER_NUM_MODES
, NULL
);
213 MENUITEM_FUNCTION(grayscale_item
, 0, "Greyscale",
214 set_option_grayscale
, NULL
, NULL
, Icon_NOICON
);
215 MENUITEM_FUNCTION(dithering_item
, 0, "Dithering",
216 set_option_dithering
, NULL
, NULL
, Icon_NOICON
);
217 MAKE_MENU(display_menu
, "Display Options", NULL
, Icon_NOICON
,
218 &grayscale_item
, &dithering_item
);
220 static void display_options(void)
222 rb
->do_menu(&display_menu
, NULL
, NULL
, false);
224 #endif /* defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER) */
226 static int show_menu(void) /* return 1 to quit */
236 MIID_SHOW_PLAYBACK_MENU
,
238 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
239 MIID_DISPLAY_OPTIONS
,
244 MENUITEM_STRINGLIST(menu
, MENU_TITLE
, NULL
,
245 "Return", "Toggle Slideshow Mode",
246 "Change Slideshow Time",
248 "Show Playback Menu",
250 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
255 static const struct opt_items slideshow
[2] = {
260 result
=rb
->do_menu(&menu
, NULL
, NULL
, false);
266 case MIID_TOGGLE_SS_MODE
:
267 rb
->set_option("Toggle Slideshow", &slideshow_enabled
, BOOL
,
268 slideshow
, 2, NULL
);
270 case MIID_CHANGE_SS_MODE
:
271 rb
->set_int("Slideshow Time", "s", UNIT_SEC
,
272 &settings
.ss_timeout
, NULL
, 1,
273 SS_MIN_TIMEOUT
, SS_MAX_TIMEOUT
, NULL
);
277 case MIID_SHOW_PLAYBACK_MENU
:
280 playback_control(NULL
);
284 rb
->splash(HZ
, "Cannot restart playback");
288 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
289 case MIID_DISPLAY_OPTIONS
:
299 /* change ata spindown time based on slideshow time setting */
300 immediate_ata_off
= false;
301 rb
->storage_spindown(rb
->global_settings
->disk_spindown
);
303 if (slideshow_enabled
)
305 if(settings
.ss_timeout
< 10)
307 /* slideshow times < 10s keep disk spinning */
308 rb
->storage_spindown(0);
310 else if (!rb
->mp3_is_playing())
312 /* slideshow times > 10s and not playing: ata_off after load */
313 immediate_ata_off
= true;
318 rb
->lcd_set_backdrop(NULL
);
319 rb
->lcd_set_foreground(LCD_WHITE
);
320 rb
->lcd_set_background(LCD_BLACK
);
322 rb
->lcd_clear_display();
327 static int ask_and_get_audio_buffer(const char *filename
)
329 rb
->lcd_setfont(FONT_SYSFIXED
);
330 rb
->lcd_clear_display();
331 rb
->lcd_puts(0, 0, rb
->strrchr(filename
,'/')+1);
332 rb
->lcd_puts(0, 1, "Not enough plugin memory!");
333 rb
->lcd_puts(0, 2, "Zoom In: Stop playback.");
335 rb
->lcd_puts(0, 3, "Left/Right: Skip File.");
336 rb
->lcd_puts(0, 4, "Show Menu: Quit.");
338 rb
->lcd_setfont(FONT_UI
);
340 rb
->button_clear_queue();
344 int button
= rb
->button_get(true);
347 case IMGVIEW_ZOOM_IN
:
349 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
350 /*try again this file, now using the audio buffer */
352 #ifdef IMGVIEW_RC_MENU
353 case IMGVIEW_RC_MENU
:
364 rb
->lcd_clear_display();
365 return change_filename(DIR_PREV
);
372 rb
->lcd_clear_display();
373 return change_filename(DIR_NEXT
);
377 if(rb
->default_event_handler_ex(button
, cleanup
, NULL
)
378 == SYS_USB_CONNECTED
)
379 return PLUGIN_USB_CONNECTED
;
383 #endif /* USE_PLUG_BUF */
385 /* callback updating a progress meter while image decoding */
386 void cb_progress(int current
, int total
)
388 rb
->yield(); /* be nice to the other threads */
390 /* in slideshow mode, keep gui interference to a minimum */
391 const int size
= (!running_slideshow
? 8 : 4);
394 if(!running_slideshow
)
397 rb
->gui_scrollbar_draw(rb
->screens
[SCREEN_MAIN
],
398 0, LCD_HEIGHT
-size
, LCD_WIDTH
, size
,
399 total
, 0, current
, HORIZONTAL
);
400 rb
->lcd_update_rect(0, LCD_HEIGHT
-size
, LCD_WIDTH
, size
);
404 #define VSCROLL (LCD_HEIGHT/8)
405 #define HSCROLL (LCD_WIDTH/10)
407 #define ZOOM_IN 100 /* return codes for below function */
410 /* Pan the viewing window right - move image to the left and fill in
411 the right-hand side */
412 static void pan_view_right(struct image_info
*info
)
416 move
= MIN(HSCROLL
, info
->width
- info
->x
- LCD_WIDTH
);
419 MYXLCD(scroll_left
)(move
); /* scroll left */
421 draw_image_rect(info
, LCD_WIDTH
- move
, 0, move
, info
->height
-info
->y
);
426 /* Pan the viewing window left - move image to the right and fill in
427 the left-hand side */
428 static void pan_view_left(struct image_info
*info
)
432 move
= MIN(HSCROLL
, info
->x
);
435 MYXLCD(scroll_right
)(move
); /* scroll right */
437 draw_image_rect(info
, 0, 0, move
, info
->height
-info
->y
);
442 /* Pan the viewing window up - move image down and fill in
444 static void pan_view_up(struct image_info
*info
)
448 move
= MIN(VSCROLL
, info
->y
);
451 MYXLCD(scroll_down
)(move
); /* scroll down */
453 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
454 if (settings
.jpeg_dither_mode
== DITHER_DIFFUSION
)
456 /* Draw over the band at the top of the last update
457 caused by lack of error history on line zero. */
458 move
= MIN(move
+ 1, info
->y
+ info
->height
);
461 draw_image_rect(info
, 0, 0, info
->width
-info
->x
, move
);
466 /* Pan the viewing window down - move image up and fill in
468 static void pan_view_down(struct image_info
*info
)
472 move
= MIN(VSCROLL
, info
->height
- info
->y
- LCD_HEIGHT
);
475 MYXLCD(scroll_up
)(move
); /* scroll up */
477 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
478 if (settings
.jpeg_dither_mode
== DITHER_DIFFUSION
)
480 /* Save the line that was on the last line of the display
481 and draw one extra line above then recover the line with
482 image data that had an error history when it was drawn.
485 rb
->memcpy(rgb_linebuf
,
486 rb
->lcd_framebuffer
+ (LCD_HEIGHT
- move
)*LCD_WIDTH
,
487 LCD_WIDTH
*sizeof (fb_data
));
491 draw_image_rect(info
, 0, LCD_HEIGHT
- move
, info
->width
-info
->x
, move
);
493 #if defined(HAVE_LCD_COLOR) && defined(JPEG_VIEWER)
494 if (settings
.jpeg_dither_mode
== DITHER_DIFFUSION
)
496 /* Cover the first row drawn with previous image data. */
497 rb
->memcpy(rb
->lcd_framebuffer
+ (LCD_HEIGHT
- move
)*LCD_WIDTH
,
498 rgb_linebuf
, LCD_WIDTH
*sizeof (fb_data
));
506 /* interactively scroll around the image */
507 static int scroll_bmp(struct image_info
*info
)
510 int lastbutton
= BUTTON_NONE
;
514 if (slideshow_enabled
)
515 button
= rb
->button_get_w_tmo(settings
.ss_timeout
* HZ
);
517 button
= rb
->button_get(true);
519 running_slideshow
= false;
524 if (entries
> 1 && info
->width
<= LCD_WIDTH
525 && info
->height
<= LCD_HEIGHT
)
526 return change_filename(DIR_PREV
);
527 case IMGVIEW_LEFT
| BUTTON_REPEAT
:
532 if (entries
> 1 && info
->width
<= LCD_WIDTH
533 && info
->height
<= LCD_HEIGHT
)
534 return change_filename(DIR_NEXT
);
535 case IMGVIEW_RIGHT
| BUTTON_REPEAT
:
536 pan_view_right(info
);
540 case IMGVIEW_UP
| BUTTON_REPEAT
:
545 case IMGVIEW_DOWN
| BUTTON_REPEAT
:
550 if (slideshow_enabled
&& entries
> 1)
552 running_slideshow
= true;
553 return change_filename(DIR_NEXT
);
557 #ifdef IMGVIEW_SLIDE_SHOW
558 case IMGVIEW_SLIDE_SHOW
:
559 slideshow_enabled
= !slideshow_enabled
;
563 #ifdef IMGVIEW_NEXT_REPEAT
564 case IMGVIEW_NEXT_REPEAT
:
568 return change_filename(DIR_NEXT
);
571 #ifdef IMGVIEW_PREVIOUS_REPEAT
572 case IMGVIEW_PREVIOUS_REPEAT
:
574 case IMGVIEW_PREVIOUS
:
576 return change_filename(DIR_PREV
);
579 case IMGVIEW_ZOOM_IN
:
580 #ifdef IMGVIEW_ZOOM_PRE
581 if (lastbutton
!= IMGVIEW_ZOOM_PRE
)
587 case IMGVIEW_ZOOM_OUT
:
588 #ifdef IMGVIEW_ZOOM_PRE
589 if (lastbutton
!= IMGVIEW_ZOOM_PRE
)
595 #ifdef IMGVIEW_RC_MENU
596 case IMGVIEW_RC_MENU
:
599 #ifdef IMGVIEW_MENU_PRE
600 if (lastbutton
!= IMGVIEW_MENU_PRE
)
604 grey_show(false); /* switch off greyscale overlay */
606 if (show_menu() == 1)
610 grey_show(true); /* switch on greyscale overlay */
612 draw_image_rect(info
, 0, 0,
613 info
->width
-info
->x
, info
->height
-info
->y
);
625 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
626 == SYS_USB_CONNECTED
)
627 return PLUGIN_USB_CONNECTED
;
632 if (button
!= BUTTON_NONE
)
637 /********************* main function *************************/
639 /* how far can we zoom in without running out of memory */
640 static int min_downscale(int bufsize
)
644 if (img_mem(8) > bufsize
)
645 return 0; /* error, too large, even 1:8 doesn't fit */
647 while (downscale
> 1 && img_mem(downscale
/2) <= bufsize
)
653 /* how far can we zoom out, to fit image into the LCD */
654 static int max_downscale(struct image_info
*info
)
658 while (downscale
< 8 && (info
->x_size
/downscale
> LCD_WIDTH
659 || info
->y_size
/downscale
> LCD_HEIGHT
))
667 /* set the view to the given center point, limit if necessary */
668 static void set_view(struct image_info
*info
, int cx
, int cy
)
672 /* plain center to available width/height */
673 x
= cx
- MIN(LCD_WIDTH
, info
->width
) / 2;
674 y
= cy
- MIN(LCD_HEIGHT
, info
->height
) / 2;
676 /* limit against upper image size */
677 x
= MIN(info
->width
- LCD_WIDTH
, x
);
678 y
= MIN(info
->height
- LCD_HEIGHT
, y
);
680 /* limit against negative side */
684 info
->x
= x
; /* set the values */
688 /* calculate the view center based on the bitmap position */
689 static void get_view(struct image_info
*info
, int *p_cx
, int *p_cy
)
691 *p_cx
= info
->x
+ MIN(LCD_WIDTH
, info
->width
) / 2;
692 *p_cy
= info
->y
+ MIN(LCD_HEIGHT
, info
->height
) / 2;
695 /* load, decode, display the image */
696 static int load_and_show(char* filename
, struct image_info
*info
)
702 rb
->lcd_clear_display();
704 rb
->memset(info
, 0, sizeof(*info
));
705 remaining
= buf_size
;
707 if (rb
->button_get(false) == IMGVIEW_MENU
)
708 status
= PLUGIN_ABORT
;
710 status
= load_image(filename
, info
, buf
, &remaining
);
712 if (status
== PLUGIN_OUTOFMEM
)
717 return ask_and_get_audio_buffer(filename
);
722 rb
->splash(HZ
, "Out of Memory");
723 file_pt
[curfile
] = NULL
;
724 return change_filename(direction
);
727 else if (status
== PLUGIN_ERROR
)
729 file_pt
[curfile
] = NULL
;
730 return change_filename(direction
);
732 else if (status
== PLUGIN_ABORT
) {
733 rb
->splash(HZ
, "aborted");
737 ds_max
= max_downscale(info
); /* check display constraint */
738 ds_min
= min_downscale(remaining
); /* check memory constraint */
741 #if UNSCALED_IS_AVAILABLE
742 /* Can not resize the image but original one is available, so use it. */
745 /* not enough memory to decode image. */
749 return ask_and_get_audio_buffer(filename
);
754 rb
->splash(HZ
, "too large");
755 file_pt
[curfile
] = NULL
;
756 return change_filename(direction
);
760 else if (ds_max
< ds_min
)
763 ds
= ds_max
; /* initialize setting */
764 cx
= info
->x_size
/ds
/2; /* center the view */
765 cy
= info
->y_size
/ds
/2;
767 do /* loop the image prepare and decoding when zoomed */
769 status
= get_image(info
, ds
); /* decode or fetch from cache */
770 if (status
== PLUGIN_ERROR
)
772 file_pt
[curfile
] = NULL
;
773 return change_filename(direction
);
776 set_view(info
, cx
, cy
);
778 if(!running_slideshow
)
780 rb
->snprintf(print
, sizeof(print
), "showing %dx%d",
781 info
->width
, info
->height
);
782 rb
->lcd_puts(0, 3, print
);
786 MYLCD(clear_display
)();
787 draw_image_rect(info
, 0, 0,
788 info
->width
-info
->x
, info
->height
-info
->y
);
792 grey_show(true); /* switch on greyscale overlay */
795 /* drawing is now finished, play around with scrolling
796 * until you press OFF or connect USB
800 status
= scroll_bmp(info
);
801 if (status
== ZOOM_IN
)
803 #if UNSCALED_IS_AVAILABLE
809 #if UNSCALED_IS_AVAILABLE
810 /* if 1/1 is always available, jump ds from ds_min to 1. */
811 int zoom
= (ds
== ds_min
)? ds_min
: 2;
815 ds
/= zoom
; /* reduce downscaling to zoom in */
816 get_view(info
, &cx
, &cy
);
817 cx
*= zoom
; /* prepare the position in the new image */
824 if (status
== ZOOM_OUT
)
828 #if UNSCALED_IS_AVAILABLE
829 /* if ds is 1 and ds_min is > 1, jump ds to ds_min. */
830 int zoom
= (ds
< ds_min
)? ds_min
: 2;
834 ds
*= zoom
; /* increase downscaling to zoom out */
835 get_view(info
, &cx
, &cy
);
836 cx
/= zoom
; /* prepare the position in the new image */
846 grey_show(false); /* switch off overlay */
848 rb
->lcd_clear_display();
850 while (status
> PLUGIN_OTHER
);
857 /******************** Plugin entry point *********************/
859 enum plugin_status
plugin_start(const void* parameter
)
863 long greysize
; /* helper */
866 if(!parameter
) return PLUGIN_ERROR
;
869 buf
= rb
->plugin_get_buffer(&buf_size
);
871 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
874 rb
->strcpy(np_file
, parameter
);
877 if(!entries
) return PLUGIN_ERROR
;
880 if(!rb
->audio_status())
883 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
888 if (!grey_init(buf
, buf_size
, GREY_ON_COP
,
889 LCD_WIDTH
, LCD_HEIGHT
, &greysize
))
891 rb
->splash(HZ
, "grey buf error");
895 buf_size
-= greysize
;
898 /* should be ok to just load settings since the plugin itself has
899 just been loaded from disk and the drive should be spinning */
900 configfile_load(IMGVIEW_CONFIGFILE
, config
,
901 ARRAYLEN(config
), IMGVIEW_SETTINGS_MINVERSION
);
902 rb
->memcpy(&old_settings
, &settings
, sizeof (settings
));
904 /* Turn off backlight timeout */
905 backlight_force_on(); /* backlight control in lib/helper.c */
908 rb
->lcd_set_backdrop(NULL
);
909 rb
->lcd_set_foreground(LCD_WHITE
);
910 rb
->lcd_set_background(LCD_BLACK
);
915 condition
= load_and_show(np_file
, &image_info
);
916 } while (condition
>= PLUGIN_OTHER
);
918 if (rb
->memcmp(&settings
, &old_settings
, sizeof (settings
)))
920 /* Just in case drive has to spin, keep it from looking locked */
921 rb
->splash(0, "Saving Settings");
922 configfile_save(IMGVIEW_CONFIGFILE
, config
,
923 ARRAYLEN(config
), IMGVIEW_SETTINGS_VERSION
);
927 /* set back ata spindown time in case we changed it */
928 rb
->storage_spindown(rb
->global_settings
->disk_spindown
);
931 /* Turn on backlight timeout (revert to settings) */
932 backlight_use_settings(); /* backlight control in lib/helper.c */
935 grey_release(); /* deinitialize */