1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * user intereface of image viewer
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 ****************************************************************************/
24 * - check magick value in file header to determine image type.
27 #include <lib/playback_control.h>
28 #include <lib/helper.h>
29 #include <lib/configfile.h>
30 #include "imageviewer.h"
31 #include "image_decoder.h"
44 /******************************* Globals ***********************************/
46 /* Persistent configuration */
47 #define IMGVIEW_CONFIGFILE "imageviewer.cfg"
48 #define IMGVIEW_SETTINGS_MINVERSION 1
49 #define IMGVIEW_SETTINGS_VERSION 2
52 #define SS_MIN_TIMEOUT 1
53 #define SS_MAX_TIMEOUT 20
54 #define SS_DEFAULT_TIMEOUT 5
57 /* needed for value of settings */
58 #include "jpeg/yuv2rgb.h"
61 static struct imgview_settings settings
=
69 static struct imgview_settings old_settings
;
71 static struct configdata config
[] =
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" } },
79 { TYPE_INT
, SS_MIN_TIMEOUT
, SS_MAX_TIMEOUT
,
80 { .int_p
= &settings
.ss_timeout
}, "Slideshow Time", NULL
},
83 static void cb_progress(int current
, int total
);
85 static struct imgdec_api iv_api
= {
86 .settings
= &settings
,
87 .slideshow_enabled
= false,
88 .running_slideshow
= false,
90 .immediate_ata_off
= false,
96 .cb_progress
= cb_progress
,
99 .gray_bitmap_part
= myxlcd_ub_(gray_bitmap_part
),
103 /**************** begin Application ********************/
106 /************************* Globals ***************************/
108 #ifdef HAVE_LCD_COLOR
109 static fb_data rgb_linebuf
[LCD_WIDTH
]; /* Line buffer for scrolling when
110 DITHER_DIFFUSION is set */
113 /* buffer to load image decoder */
114 static unsigned char* decoder_buf
;
115 static size_t decoder_buf_size
;
116 /* the remaining free part of the buffer for loaded+resized images */
117 static unsigned char* buf
;
118 static size_t buf_size
;
120 static int ds
, ds_min
, ds_max
; /* downscaling and limits */
121 static struct image_info image_info
;
123 /* the current full file name */
124 static char np_file
[MAX_PATH
];
125 static int curfile
= -1, direction
= DIR_NEXT
, entries
= 0;
127 /* list of the supported image files */
128 static char **file_pt
;
130 static const struct image_decoder
*imgdec
= NULL
;
131 static enum image_type image_type
= IMAGE_UNKNOWN
;
133 /************************* Implementation ***************************/
135 /* Read directory contents for scrolling. */
136 static void get_pic_list(void)
138 struct tree_context
*tree
= rb
->tree_get_context();
139 struct entry
*dircache
= tree
->dircache
;
143 file_pt
= (char **) buf
;
145 /* Remove path and leave only the name.*/
146 pname
= rb
->strrchr(np_file
,'/');
149 for (i
= 0; i
< tree
->filesindir
&& buf_size
> sizeof(char**); i
++)
151 if (!(dircache
[i
].attr
& ATTR_DIRECTORY
)
152 && get_image_type(dircache
[i
].name
) != IMAGE_UNKNOWN
)
154 file_pt
[entries
] = dircache
[i
].name
;
155 /* Set Selected File. */
156 if (!rb
->strcmp(file_pt
[entries
], pname
))
160 buf
+= (sizeof(char**));
161 buf_size
-= (sizeof(char**));
166 static int change_filename(int direct
)
168 bool file_erased
= (file_pt
[curfile
] == NULL
);
171 curfile
+= (direct
== DIR_PREV
? entries
- 1: 1);
172 if (curfile
>= entries
)
177 /* remove 'erased' file names from list. */
179 for (count
= i
= 0; i
< entries
; i
++)
183 if (file_pt
[i
] != NULL
)
184 file_pt
[count
++] = file_pt
[i
];
191 rb
->splash(HZ
, "No supported files");
195 rb
->strcpy(rb
->strrchr(np_file
, '/')+1, file_pt
[curfile
]);
200 /* switch off overlay, for handling SYS_ events */
201 static void cleanup(void *parameter
)
209 #ifdef HAVE_LCD_COLOR
210 static bool set_option_grayscale(void)
212 bool gray
= settings
.jpeg_colour_mode
== COLOURMODE_GRAY
;
213 rb
->set_bool("Grayscale (Jpeg)", &gray
);
214 settings
.jpeg_colour_mode
= gray
? COLOURMODE_GRAY
: COLOURMODE_COLOUR
;
218 static bool set_option_dithering(void)
220 static const struct opt_items dithering
[DITHER_NUM_MODES
] = {
221 [DITHER_NONE
] = { "Off", -1 },
222 [DITHER_ORDERED
] = { "Ordered", -1 },
223 [DITHER_DIFFUSION
] = { "Diffusion", -1 },
226 rb
->set_option("Dithering (Jpeg)", &settings
.jpeg_dither_mode
, INT
,
227 dithering
, DITHER_NUM_MODES
, NULL
);
231 MENUITEM_FUNCTION(grayscale_item
, 0, "Greyscale (Jpeg)",
232 set_option_grayscale
, NULL
, NULL
, Icon_NOICON
);
233 MENUITEM_FUNCTION(dithering_item
, 0, "Dithering (Jpeg)",
234 set_option_dithering
, NULL
, NULL
, Icon_NOICON
);
235 MAKE_MENU(display_menu
, "Display Options", NULL
, Icon_NOICON
,
236 &grayscale_item
, &dithering_item
);
238 static void display_options(void)
240 rb
->do_menu(&display_menu
, NULL
, NULL
, false);
242 #endif /* HAVE_LCD_COLOR */
244 static int show_menu(void) /* return 1 to quit */
254 MIID_SHOW_PLAYBACK_MENU
,
256 #ifdef HAVE_LCD_COLOR
257 MIID_DISPLAY_OPTIONS
,
262 MENUITEM_STRINGLIST(menu
, "Image Viewer Menu", NULL
,
263 "Return", "Toggle Slideshow Mode",
264 "Change Slideshow Time",
266 "Show Playback Menu",
268 #ifdef HAVE_LCD_COLOR
273 static const struct opt_items slideshow
[2] = {
278 result
=rb
->do_menu(&menu
, NULL
, NULL
, false);
284 case MIID_TOGGLE_SS_MODE
:
285 rb
->set_option("Toggle Slideshow", &iv_api
.slideshow_enabled
, BOOL
,
286 slideshow
, 2, NULL
);
288 case MIID_CHANGE_SS_MODE
:
289 rb
->set_int("Slideshow Time", "s", UNIT_SEC
,
290 &settings
.ss_timeout
, NULL
, 1,
291 SS_MIN_TIMEOUT
, SS_MAX_TIMEOUT
, NULL
);
295 case MIID_SHOW_PLAYBACK_MENU
:
298 playback_control(NULL
);
302 rb
->splash(HZ
, "Cannot restart playback");
306 #ifdef HAVE_LCD_COLOR
307 case MIID_DISPLAY_OPTIONS
:
317 /* change ata spindown time based on slideshow time setting */
318 iv_api
.immediate_ata_off
= false;
319 rb
->storage_spindown(rb
->global_settings
->disk_spindown
);
321 if (iv_api
.slideshow_enabled
)
323 if(settings
.ss_timeout
< 10)
325 /* slideshow times < 10s keep disk spinning */
326 rb
->storage_spindown(0);
328 else if (!rb
->mp3_is_playing())
330 /* slideshow times > 10s and not playing: ata_off after load */
331 iv_api
.immediate_ata_off
= true;
336 rb
->lcd_set_backdrop(NULL
);
337 rb
->lcd_set_foreground(LCD_WHITE
);
338 rb
->lcd_set_background(LCD_BLACK
);
340 rb
->lcd_clear_display();
345 static int ask_and_get_audio_buffer(const char *filename
)
347 rb
->lcd_setfont(FONT_SYSFIXED
);
348 rb
->lcd_clear_display();
349 rb
->lcd_puts(0, 0, rb
->strrchr(filename
,'/')+1);
350 rb
->lcd_puts(0, 1, "Not enough plugin memory!");
351 rb
->lcd_puts(0, 2, "Zoom In: Stop playback.");
353 rb
->lcd_puts(0, 3, "Left/Right: Skip File.");
354 rb
->lcd_puts(0, 4, "Show Menu: Quit.");
356 rb
->lcd_setfont(FONT_UI
);
358 rb
->button_clear_queue();
362 int button
= rb
->button_get(true);
365 case IMGVIEW_ZOOM_IN
:
366 iv_api
.plug_buf
= false;
367 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
368 /*try again this file, now using the audio buffer */
370 #ifdef IMGVIEW_RC_MENU
371 case IMGVIEW_RC_MENU
:
382 rb
->lcd_clear_display();
383 return change_filename(DIR_PREV
);
390 rb
->lcd_clear_display();
391 return change_filename(DIR_NEXT
);
395 if(rb
->default_event_handler_ex(button
, cleanup
, NULL
)
396 == SYS_USB_CONNECTED
)
397 return PLUGIN_USB_CONNECTED
;
401 #endif /* USE_PLUG_BUF */
403 /* callback updating a progress meter while image decoding */
404 static void cb_progress(int current
, int total
)
406 rb
->yield(); /* be nice to the other threads */
408 /* in slideshow mode, keep gui interference to a minimum */
409 const int size
= (!iv_api
.running_slideshow
? 8 : 4);
412 if(!iv_api
.running_slideshow
)
415 rb
->gui_scrollbar_draw(rb
->screens
[SCREEN_MAIN
],
416 0, LCD_HEIGHT
-size
, LCD_WIDTH
, size
,
417 total
, 0, current
, HORIZONTAL
);
418 rb
->lcd_update_rect(0, LCD_HEIGHT
-size
, LCD_WIDTH
, size
);
422 #define VSCROLL (LCD_HEIGHT/8)
423 #define HSCROLL (LCD_WIDTH/10)
425 /* Pan the viewing window right - move image to the left and fill in
426 the right-hand side */
427 static void pan_view_right(struct image_info
*info
)
431 move
= MIN(HSCROLL
, info
->width
- info
->x
- LCD_WIDTH
);
434 mylcd_ub_scroll_left(move
); /* scroll left */
436 imgdec
->draw_image_rect(info
, LCD_WIDTH
- move
, 0,
437 move
, info
->height
-info
->y
);
442 /* Pan the viewing window left - move image to the right and fill in
443 the left-hand side */
444 static void pan_view_left(struct image_info
*info
)
448 move
= MIN(HSCROLL
, info
->x
);
451 mylcd_ub_scroll_right(move
); /* scroll right */
453 imgdec
->draw_image_rect(info
, 0, 0, move
, info
->height
-info
->y
);
458 /* Pan the viewing window up - move image down and fill in
460 static void pan_view_up(struct image_info
*info
)
464 move
= MIN(VSCROLL
, info
->y
);
467 mylcd_ub_scroll_down(move
); /* scroll down */
469 #ifdef HAVE_LCD_COLOR
470 if (image_type
== IMAGE_JPEG
471 && settings
.jpeg_dither_mode
== DITHER_DIFFUSION
)
473 /* Draw over the band at the top of the last update
474 caused by lack of error history on line zero. */
475 move
= MIN(move
+ 1, info
->y
+ info
->height
);
478 imgdec
->draw_image_rect(info
, 0, 0, info
->width
-info
->x
, move
);
483 /* Pan the viewing window down - move image up and fill in
485 static void pan_view_down(struct image_info
*info
)
489 move
= MIN(VSCROLL
, info
->height
- info
->y
- LCD_HEIGHT
);
492 mylcd_ub_scroll_up(move
); /* scroll up */
494 #ifdef HAVE_LCD_COLOR
495 if (image_type
== IMAGE_JPEG
496 && settings
.jpeg_dither_mode
== DITHER_DIFFUSION
)
498 /* Save the line that was on the last line of the display
499 and draw one extra line above then recover the line with
500 image data that had an error history when it was drawn.
503 rb
->memcpy(rgb_linebuf
,
504 rb
->lcd_framebuffer
+ (LCD_HEIGHT
- move
)*LCD_WIDTH
,
505 LCD_WIDTH
*sizeof (fb_data
));
509 imgdec
->draw_image_rect(info
, 0, LCD_HEIGHT
- move
,
510 info
->width
-info
->x
, move
);
512 #ifdef HAVE_LCD_COLOR
513 if (image_type
== IMAGE_JPEG
514 && settings
.jpeg_dither_mode
== DITHER_DIFFUSION
)
516 /* Cover the first row drawn with previous image data. */
517 rb
->memcpy(rb
->lcd_framebuffer
+ (LCD_HEIGHT
- move
)*LCD_WIDTH
,
518 rgb_linebuf
, LCD_WIDTH
*sizeof (fb_data
));
526 /* interactively scroll around the image */
527 static int scroll_bmp(struct image_info
*info
)
530 int lastbutton
= BUTTON_NONE
;
534 if (iv_api
.slideshow_enabled
)
535 button
= rb
->button_get_w_tmo(settings
.ss_timeout
* HZ
);
537 button
= rb
->button_get(true);
539 iv_api
.running_slideshow
= false;
544 if (entries
> 1 && info
->width
<= LCD_WIDTH
545 && info
->height
<= LCD_HEIGHT
)
546 return change_filename(DIR_PREV
);
547 case IMGVIEW_LEFT
| BUTTON_REPEAT
:
552 if (entries
> 1 && info
->width
<= LCD_WIDTH
553 && info
->height
<= LCD_HEIGHT
)
554 return change_filename(DIR_NEXT
);
555 case IMGVIEW_RIGHT
| BUTTON_REPEAT
:
556 pan_view_right(info
);
560 case IMGVIEW_UP
| BUTTON_REPEAT
:
565 case IMGVIEW_DOWN
| BUTTON_REPEAT
:
570 if (iv_api
.slideshow_enabled
&& entries
> 1)
572 iv_api
.running_slideshow
= true;
573 return change_filename(DIR_NEXT
);
577 #ifdef IMGVIEW_SLIDE_SHOW
578 case IMGVIEW_SLIDE_SHOW
:
579 iv_api
.slideshow_enabled
= !iv_api
.slideshow_enabled
;
583 #ifdef IMGVIEW_NEXT_REPEAT
584 case IMGVIEW_NEXT_REPEAT
:
588 return change_filename(DIR_NEXT
);
591 #ifdef IMGVIEW_PREVIOUS_REPEAT
592 case IMGVIEW_PREVIOUS_REPEAT
:
594 case IMGVIEW_PREVIOUS
:
596 return change_filename(DIR_PREV
);
599 case IMGVIEW_ZOOM_IN
:
600 #ifdef IMGVIEW_ZOOM_PRE
601 if (lastbutton
!= IMGVIEW_ZOOM_PRE
)
607 case IMGVIEW_ZOOM_OUT
:
608 #ifdef IMGVIEW_ZOOM_PRE
609 if (lastbutton
!= IMGVIEW_ZOOM_PRE
)
615 #ifdef IMGVIEW_RC_MENU
616 case IMGVIEW_RC_MENU
:
619 #ifdef IMGVIEW_MENU_PRE
620 if (lastbutton
!= IMGVIEW_MENU_PRE
)
624 grey_show(false); /* switch off greyscale overlay */
626 if (show_menu() == 1)
630 grey_show(true); /* switch on greyscale overlay */
632 imgdec
->draw_image_rect(info
, 0, 0,
633 info
->width
-info
->x
, info
->height
-info
->y
);
645 if (rb
->default_event_handler_ex(button
, cleanup
, NULL
)
646 == SYS_USB_CONNECTED
)
647 return PLUGIN_USB_CONNECTED
;
652 if (button
!= BUTTON_NONE
)
657 /********************* main function *************************/
659 /* how far can we zoom in without running out of memory */
660 static int min_downscale(int bufsize
)
664 if (imgdec
->img_mem(8) > bufsize
)
665 return 0; /* error, too large, even 1:8 doesn't fit */
667 while (downscale
> 1 && imgdec
->img_mem(downscale
/2) <= bufsize
)
673 /* how far can we zoom out, to fit image into the LCD */
674 static int max_downscale(struct image_info
*info
)
678 while (downscale
< 8 && (info
->x_size
/downscale
> LCD_WIDTH
679 || info
->y_size
/downscale
> LCD_HEIGHT
))
687 /* set the view to the given center point, limit if necessary */
688 static void set_view(struct image_info
*info
, int cx
, int cy
)
692 /* plain center to available width/height */
693 x
= cx
- MIN(LCD_WIDTH
, info
->width
) / 2;
694 y
= cy
- MIN(LCD_HEIGHT
, info
->height
) / 2;
696 /* limit against upper image size */
697 x
= MIN(info
->width
- LCD_WIDTH
, x
);
698 y
= MIN(info
->height
- LCD_HEIGHT
, y
);
700 /* limit against negative side */
704 info
->x
= x
; /* set the values */
708 /* calculate the view center based on the bitmap position */
709 static void get_view(struct image_info
*info
, int *p_cx
, int *p_cy
)
711 *p_cx
= info
->x
+ MIN(LCD_WIDTH
, info
->width
) / 2;
712 *p_cy
= info
->y
+ MIN(LCD_HEIGHT
, info
->height
) / 2;
715 /* load, decode, display the image */
716 static int load_and_show(char* filename
, struct image_info
*info
)
722 rb
->lcd_clear_display();
724 status
= get_image_type(filename
);
725 if (image_type
!= status
) /* type of image is changed, load decoder. */
727 struct loader_info loader_info
= {
728 status
, &iv_api
, decoder_buf
, decoder_buf_size
,
731 imgdec
= load_decoder(&loader_info
);
734 /* something is wrong */
740 buf
= loader_info
.buffer
;
741 buf_size
= loader_info
.size
;
745 rb
->memset(info
, 0, sizeof(*info
));
746 remaining
= buf_size
;
748 if (rb
->button_get(false) == IMGVIEW_MENU
)
749 status
= PLUGIN_ABORT
;
751 status
= imgdec
->load_image(filename
, info
, buf
, &remaining
);
753 if (status
== PLUGIN_OUTOFMEM
)
758 return ask_and_get_audio_buffer(filename
);
763 rb
->splash(HZ
, "Out of Memory");
764 file_pt
[curfile
] = NULL
;
765 return change_filename(direction
);
768 else if (status
== PLUGIN_ERROR
)
770 file_pt
[curfile
] = NULL
;
771 return change_filename(direction
);
773 else if (status
== PLUGIN_ABORT
) {
774 rb
->splash(HZ
, "aborted");
778 ds_max
= max_downscale(info
); /* check display constraint */
779 ds_min
= min_downscale(remaining
); /* check memory constraint */
782 if (imgdec
->unscaled_avail
)
784 /* Can not resize the image but original one is available, so use it. */
791 return ask_and_get_audio_buffer(filename
);
796 rb
->splash(HZ
, "too large");
797 file_pt
[curfile
] = NULL
;
798 return change_filename(direction
);
801 else if (ds_max
< ds_min
)
804 ds
= ds_max
; /* initialize setting */
805 cx
= info
->x_size
/ds
/2; /* center the view */
806 cy
= info
->y_size
/ds
/2;
808 do /* loop the image prepare and decoding when zoomed */
810 status
= imgdec
->get_image(info
, ds
); /* decode or fetch from cache */
811 if (status
== PLUGIN_ERROR
)
813 file_pt
[curfile
] = NULL
;
814 return change_filename(direction
);
817 set_view(info
, cx
, cy
);
819 if(!iv_api
.running_slideshow
)
821 rb
->lcd_putsf(0, 3, "showing %dx%d", info
->width
, info
->height
);
825 mylcd_ub_clear_display();
826 imgdec
->draw_image_rect(info
, 0, 0,
827 info
->width
-info
->x
, info
->height
-info
->y
);
831 grey_show(true); /* switch on greyscale overlay */
834 /* drawing is now finished, play around with scrolling
835 * until you press OFF or connect USB
839 status
= scroll_bmp(info
);
840 if (status
== ZOOM_IN
)
842 if (ds
> ds_min
|| (imgdec
->unscaled_avail
&& ds
> 1))
844 /* if 1/1 is always available, jump ds from ds_min to 1. */
845 int zoom
= (ds
== ds_min
)? ds_min
: 2;
846 ds
/= zoom
; /* reduce downscaling to zoom in */
847 get_view(info
, &cx
, &cy
);
848 cx
*= zoom
; /* prepare the position in the new image */
855 if (status
== ZOOM_OUT
)
859 /* if ds is 1 and ds_min is > 1, jump ds to ds_min. */
860 int zoom
= (ds
< ds_min
)? ds_min
: 2;
861 ds
*= zoom
; /* increase downscaling to zoom out */
862 get_view(info
, &cx
, &cy
);
863 cx
/= zoom
; /* prepare the position in the new image */
873 grey_show(false); /* switch off overlay */
875 rb
->lcd_clear_display();
877 while (status
> PLUGIN_OTHER
);
884 /******************** Plugin entry point *********************/
886 enum plugin_status
plugin_start(const void* parameter
)
890 long greysize
; /* helper */
893 if(!parameter
) return PLUGIN_ERROR
;
895 rb
->strcpy(np_file
, parameter
);
896 if (get_image_type(np_file
) == IMAGE_UNKNOWN
)
898 rb
->splash(HZ
*2, "Unsupported file");
903 buf
= rb
->plugin_get_buffer(&buf_size
);
905 decoder_buf
= rb
->plugin_get_buffer(&decoder_buf_size
);
906 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
911 if(!entries
) return PLUGIN_ERROR
;
914 if (!grey_init(buf
, buf_size
, GREY_ON_COP
,
915 LCD_WIDTH
, LCD_HEIGHT
, &greysize
))
917 rb
->splash(HZ
, "grey buf error");
921 buf_size
-= greysize
;
926 decoder_buf_size
= buf_size
;
927 if(!rb
->audio_status())
929 iv_api
.plug_buf
= false;
930 buf
= rb
->plugin_get_audio_buffer(&buf_size
);
934 /* should be ok to just load settings since the plugin itself has
935 just been loaded from disk and the drive should be spinning */
936 configfile_load(IMGVIEW_CONFIGFILE
, config
,
937 ARRAYLEN(config
), IMGVIEW_SETTINGS_MINVERSION
);
938 rb
->memcpy(&old_settings
, &settings
, sizeof (settings
));
940 /* Turn off backlight timeout */
941 backlight_force_on(); /* backlight control in lib/helper.c */
944 rb
->lcd_set_backdrop(NULL
);
945 rb
->lcd_set_foreground(LCD_WHITE
);
946 rb
->lcd_set_background(LCD_BLACK
);
951 condition
= load_and_show(np_file
, &image_info
);
952 } while (condition
>= PLUGIN_OTHER
);
955 if (rb
->memcmp(&settings
, &old_settings
, sizeof (settings
)))
957 /* Just in case drive has to spin, keep it from looking locked */
958 rb
->splash(0, "Saving Settings");
959 configfile_save(IMGVIEW_CONFIGFILE
, config
,
960 ARRAYLEN(config
), IMGVIEW_SETTINGS_VERSION
);
964 /* set back ata spindown time in case we changed it */
965 rb
->storage_spindown(rb
->global_settings
->disk_spindown
);
968 /* Turn on backlight timeout (revert to settings) */
969 backlight_use_settings(); /* backlight control in lib/helper.c */
972 grey_release(); /* deinitialize */