Rearange menu of mpegplayer. Add new menu with "settings" and "quit", and remove...
[kugel-rb.git] / apps / plugins / jpeg / jpeg.c
blobfc98834a7d59e2022b2a0cd34a22bf7846d7adb1
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * JPEG image viewer
11 * (This is a real mess if it has to be coded in one single C file)
13 * File scrolling addition (C) 2005 Alexander Spyridakis
14 * Copyright (C) 2004 Jörg Hohensohn aka [IDC]Dragon
15 * Heavily borrowed from the IJG implementation (C) Thomas G. Lane
16 * Small & fast downscaling IDCT (C) 2002 by Guido Vollbeding JPEGclub.org
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation; either version 2
21 * of the License, or (at your option) any later version.
23 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
24 * KIND, either express or implied.
26 ****************************************************************************/
28 #include "plugin.h"
29 #include <lib/playback_control.h>
30 #include <lib/helper.h>
31 #include <lib/configfile.h>
33 #include <lib/grey.h>
34 #include <lib/xlcd.h>
36 #include "jpeg.h"
37 #include "jpeg_decoder.h"
39 PLUGIN_HEADER
41 #ifdef HAVE_LCD_COLOR
42 #include "yuv2rgb.h"
43 #endif
45 /* different graphics libraries */
46 #if LCD_DEPTH < 8
47 #define USEGSLIB
48 GREY_INFO_STRUCT
49 #define MYLCD(fn) grey_ub_ ## fn
50 #define MYLCD_UPDATE()
51 #define MYXLCD(fn) grey_ub_ ## fn
52 #else
53 #define MYLCD(fn) rb->lcd_ ## fn
54 #define MYLCD_UPDATE() rb->lcd_update();
55 #define MYXLCD(fn) xlcd_ ## fn
56 #endif
58 #define MAX_X_SIZE LCD_WIDTH*8
60 /* Min memory allowing us to use the plugin buffer
61 * and thus not stopping the music
62 * *Very* rough estimation:
63 * Max 10 000 dir entries * 4bytes/entry (char **) = 40000 bytes
64 * + 20k code size = 60 000
65 * + 50k min for jpeg = 120 000
67 #define MIN_MEM 120000
69 /* Headings */
70 #define DIR_PREV 1
71 #define DIR_NEXT -1
72 #define DIR_NONE 0
74 #define PLUGIN_OTHER 10 /* State code for output with return. */
76 /******************************* Globals ***********************************/
78 static int slideshow_enabled = false; /* run slideshow */
79 static int running_slideshow = false; /* loading image because of slideshw */
80 #ifndef SIMULATOR
81 static int immediate_ata_off = false; /* power down disk after loading */
82 #endif
84 #ifdef HAVE_LCD_COLOR
85 fb_data rgb_linebuf[LCD_WIDTH]; /* Line buffer for scrolling when
86 DITHER_DIFFUSION is set */
87 #endif
90 /* Persistent configuration */
91 #define JPEG_CONFIGFILE "jpeg.cfg"
92 #define JPEG_SETTINGS_MINVERSION 1
93 #define JPEG_SETTINGS_VERSION 2
95 /* Slideshow times */
96 #define SS_MIN_TIMEOUT 1
97 #define SS_MAX_TIMEOUT 20
98 #define SS_DEFAULT_TIMEOUT 5
100 struct jpeg_settings
102 #ifdef HAVE_LCD_COLOR
103 int colour_mode;
104 int dither_mode;
105 #endif
106 int ss_timeout;
109 static struct jpeg_settings jpeg_settings =
111 #ifdef HAVE_LCD_COLOR
112 COLOURMODE_COLOUR,
113 DITHER_NONE,
114 #endif
115 SS_DEFAULT_TIMEOUT
117 static struct jpeg_settings old_settings;
119 static struct configdata jpeg_config[] =
121 #ifdef HAVE_LCD_COLOR
122 { TYPE_ENUM, 0, COLOUR_NUM_MODES, { .int_p = &jpeg_settings.colour_mode },
123 "Colour Mode", (char *[]){ "Colour", "Grayscale" } },
124 { TYPE_ENUM, 0, DITHER_NUM_MODES, { .int_p = &jpeg_settings.dither_mode },
125 "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" } },
126 #endif
127 { TYPE_INT, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT,
128 { .int_p = &jpeg_settings.ss_timeout }, "Slideshow Time", NULL },
131 #if LCD_DEPTH > 1
132 static fb_data* old_backdrop;
133 #endif
135 /**************** begin Application ********************/
138 /************************* Types ***************************/
140 struct t_disp
142 #ifdef HAVE_LCD_COLOR
143 unsigned char* bitmap[3]; /* Y, Cr, Cb */
144 int csub_x, csub_y;
145 #else
146 unsigned char* bitmap[1]; /* Y only */
147 #endif
148 int width;
149 int height;
150 int stride;
151 int x, y;
154 /************************* Globals ***************************/
156 /* decompressed image in the possible sizes (1,2,4,8), wasting the other */
157 static struct t_disp disp[9];
159 /* my memory pool (from the mp3 buffer) */
160 static char print[32]; /* use a common snprintf() buffer */
161 static unsigned char* buf; /* up to here currently used by image(s) */
163 /* the remaining free part of the buffer for compressed+uncompressed images */
164 static unsigned char* buf_images;
166 static ssize_t buf_size, buf_images_size;
167 /* the root of the images, hereafter are decompresed ones */
168 static unsigned char* buf_root;
169 static int root_size;
171 static int ds, ds_min, ds_max; /* downscaling and limits */
172 static struct jpeg jpg; /* too large for stack */
174 static struct tree_context *tree;
176 /* the current full file name */
177 static char np_file[MAX_PATH];
178 static int curfile = 0, direction = DIR_NONE, entries = 0;
180 /* list of the jpeg files */
181 static char **file_pt;
182 /* are we using the plugin buffer or the audio buffer? */
183 bool plug_buf = false;
186 /************************* Implementation ***************************/
188 /* support function for qsort() */
189 static int compare(const void* p1, const void* p2)
191 return rb->strcasecmp(*((char **)p1), *((char **)p2));
194 bool jpg_ext(const char ext[])
196 if(!ext)
197 return false;
198 if(!rb->strcasecmp(ext,".jpg") ||
199 !rb->strcasecmp(ext,".jpe") ||
200 !rb->strcasecmp(ext,".jpeg"))
201 return true;
202 else
203 return false;
206 /*Read directory contents for scrolling. */
207 void get_pic_list(void)
209 int i;
210 long int str_len = 0;
211 char *pname;
212 tree = rb->tree_get_context();
214 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
215 file_pt = rb->plugin_get_buffer((size_t *)&buf_size);
216 #else
217 file_pt = rb->plugin_get_audio_buffer((size_t *)&buf_size);
218 #endif
220 for(i = 0; i < tree->filesindir; i++)
222 if(jpg_ext(rb->strrchr(&tree->name_buffer[str_len],'.')))
223 file_pt[entries++] = &tree->name_buffer[str_len];
225 str_len += rb->strlen(&tree->name_buffer[str_len]) + 1;
228 rb->qsort(file_pt, entries, sizeof(char**), compare);
230 /* Remove path and leave only the name.*/
231 pname = rb->strrchr(np_file,'/');
232 pname++;
234 /* Find Selected File. */
235 for(i = 0; i < entries; i++)
236 if(!rb->strcmp(file_pt[i], pname))
237 curfile = i;
240 int change_filename(int direct)
242 int count = 0;
243 direction = direct;
245 if(direct == DIR_PREV)
249 count++;
250 if(curfile == 0)
251 curfile = entries - 1;
252 else
253 curfile--;
254 }while(file_pt[curfile] == '\0' && count < entries);
255 /* we "erase" the file name if we encounter
256 * a non-supported file, so skip it now */
258 else /* DIR_NEXT/DIR_NONE */
262 count++;
263 if(curfile == entries - 1)
264 curfile = 0;
265 else
266 curfile++;
267 }while(file_pt[curfile] == '\0' && count < entries);
270 if(count == entries && file_pt[curfile] == '\0')
272 rb->splash(HZ, "No supported files");
273 return PLUGIN_ERROR;
275 if(rb->strlen(tree->currdir) > 1)
277 rb->strcpy(np_file, tree->currdir);
278 rb->strcat(np_file, "/");
280 else
281 rb->strcpy(np_file, tree->currdir);
283 rb->strcat(np_file, file_pt[curfile]);
285 return PLUGIN_OTHER;
288 /* switch off overlay, for handling SYS_ events */
289 void cleanup(void *parameter)
291 (void)parameter;
292 #ifdef USEGSLIB
293 grey_show(false);
294 #endif
297 #define VSCROLL (LCD_HEIGHT/8)
298 #define HSCROLL (LCD_WIDTH/10)
300 #define ZOOM_IN 100 /* return codes for below function */
301 #define ZOOM_OUT 101
303 #ifdef HAVE_LCD_COLOR
304 bool set_option_grayscale(void)
306 bool gray = jpeg_settings.colour_mode == COLOURMODE_GRAY;
307 rb->set_bool("Grayscale", &gray);
308 jpeg_settings.colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR;
309 return false;
312 bool set_option_dithering(void)
314 static const struct opt_items dithering[DITHER_NUM_MODES] = {
315 [DITHER_NONE] = { "Off", -1 },
316 [DITHER_ORDERED] = { "Ordered", -1 },
317 [DITHER_DIFFUSION] = { "Diffusion", -1 },
320 rb->set_option("Dithering", &jpeg_settings.dither_mode, INT,
321 dithering, DITHER_NUM_MODES, NULL);
322 return false;
325 MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale",
326 set_option_grayscale, NULL, NULL, Icon_NOICON);
327 MENUITEM_FUNCTION(dithering_item, 0, "Dithering",
328 set_option_dithering, NULL, NULL, Icon_NOICON);
329 MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON,
330 &grayscale_item, &dithering_item);
332 static void display_options(void)
334 rb->do_menu(&display_menu, NULL, NULL, false);
336 #endif /* HAVE_LCD_COLOR */
338 int show_menu(void) /* return 1 to quit */
340 #if LCD_DEPTH > 1
341 rb->lcd_set_backdrop(old_backdrop);
342 #ifdef HAVE_LCD_COLOR
343 rb->lcd_set_foreground(rb->global_settings->fg_color);
344 rb->lcd_set_background(rb->global_settings->bg_color);
345 #else
346 rb->lcd_set_foreground(LCD_BLACK);
347 rb->lcd_set_background(LCD_WHITE);
348 #endif
349 #endif
350 int result;
352 enum menu_id
354 MIID_RETURN = 0,
355 MIID_TOGGLE_SS_MODE,
356 MIID_CHANGE_SS_MODE,
357 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
358 MIID_SHOW_PLAYBACK_MENU,
359 #endif
360 #ifdef HAVE_LCD_COLOR
361 MIID_DISPLAY_OPTIONS,
362 #endif
363 MIID_QUIT,
366 MENUITEM_STRINGLIST(menu, "Jpeg Menu", NULL,
367 "Return", "Toggle Slideshow Mode",
368 "Change Slideshow Time",
369 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
370 "Show Playback Menu",
371 #endif
372 #ifdef HAVE_LCD_COLOR
373 "Display Options",
374 #endif
375 "Quit");
377 static const struct opt_items slideshow[2] = {
378 { "Disable", -1 },
379 { "Enable", -1 },
382 result=rb->do_menu(&menu, NULL, NULL, false);
384 switch (result)
386 case MIID_RETURN:
387 break;
388 case MIID_TOGGLE_SS_MODE:
389 rb->set_option("Toggle Slideshow", &slideshow_enabled, INT,
390 slideshow , 2, NULL);
391 break;
392 case MIID_CHANGE_SS_MODE:
393 rb->set_int("Slideshow Time", "s", UNIT_SEC,
394 &jpeg_settings.ss_timeout, NULL, 1,
395 SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL);
396 break;
398 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
399 case MIID_SHOW_PLAYBACK_MENU:
400 if (plug_buf)
402 playback_control(NULL);
404 else
406 rb->splash(HZ, "Cannot restart playback");
408 break;
409 #endif
410 #ifdef HAVE_LCD_COLOR
411 case MIID_DISPLAY_OPTIONS:
412 display_options();
413 break;
414 #endif
415 case MIID_QUIT:
416 return 1;
417 break;
420 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
421 /* change ata spindown time based on slideshow time setting */
422 immediate_ata_off = false;
423 rb->storage_spindown(rb->global_settings->disk_spindown);
425 if (slideshow_enabled)
427 if(jpeg_settings.ss_timeout < 10)
429 /* slideshow times < 10s keep disk spinning */
430 rb->storage_spindown(0);
432 else if (!rb->mp3_is_playing())
434 /* slideshow times > 10s and not playing: ata_off after load */
435 immediate_ata_off = true;
438 #endif
439 #if LCD_DEPTH > 1
440 rb->lcd_set_backdrop(NULL);
441 rb->lcd_set_foreground(LCD_WHITE);
442 rb->lcd_set_background(LCD_BLACK);
443 #endif
444 rb->lcd_clear_display();
445 return 0;
448 /* Pan the viewing window right - move image to the left and fill in
449 the right-hand side */
450 static void pan_view_right(struct t_disp* pdisp)
452 int move;
454 move = MIN(HSCROLL, pdisp->width - pdisp->x - LCD_WIDTH);
455 if (move > 0)
457 MYXLCD(scroll_left)(move); /* scroll left */
458 pdisp->x += move;
459 #ifdef HAVE_LCD_COLOR
460 yuv_bitmap_part(
461 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
462 pdisp->x + LCD_WIDTH - move, pdisp->y, pdisp->stride,
463 LCD_WIDTH - move, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
464 move, MIN(LCD_HEIGHT, pdisp->height), /* w, h */
465 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
466 #else
467 MYXLCD(gray_bitmap_part)(
468 pdisp->bitmap[0], pdisp->x + LCD_WIDTH - move,
469 pdisp->y, pdisp->stride,
470 LCD_WIDTH - move, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
471 move, MIN(LCD_HEIGHT, pdisp->height)); /* w, h */
472 #endif
473 MYLCD_UPDATE();
477 /* Pan the viewing window left - move image to the right and fill in
478 the left-hand side */
479 static void pan_view_left(struct t_disp* pdisp)
481 int move;
483 move = MIN(HSCROLL, pdisp->x);
484 if (move > 0)
486 MYXLCD(scroll_right)(move); /* scroll right */
487 pdisp->x -= move;
488 #ifdef HAVE_LCD_COLOR
489 yuv_bitmap_part(
490 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
491 pdisp->x, pdisp->y, pdisp->stride,
492 0, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
493 move, MIN(LCD_HEIGHT, pdisp->height), /* w, h */
494 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
495 #else
496 MYXLCD(gray_bitmap_part)(
497 pdisp->bitmap[0], pdisp->x, pdisp->y, pdisp->stride,
498 0, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
499 move, MIN(LCD_HEIGHT, pdisp->height)); /* w, h */
500 #endif
501 MYLCD_UPDATE();
506 /* Pan the viewing window up - move image down and fill in
507 the top */
508 static void pan_view_up(struct t_disp* pdisp)
510 int move;
512 move = MIN(VSCROLL, pdisp->y);
513 if (move > 0)
515 MYXLCD(scroll_down)(move); /* scroll down */
516 pdisp->y -= move;
517 #ifdef HAVE_LCD_COLOR
518 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
520 /* Draw over the band at the top of the last update
521 caused by lack of error history on line zero. */
522 move = MIN(move + 1, pdisp->y + pdisp->height);
525 yuv_bitmap_part(
526 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
527 pdisp->x, pdisp->y, pdisp->stride,
528 MAX(0, (LCD_WIDTH-pdisp->width)/2), 0, /* x, y */
529 MIN(LCD_WIDTH, pdisp->width), move, /* w, h */
530 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
531 #else
532 MYXLCD(gray_bitmap_part)(
533 pdisp->bitmap[0], pdisp->x, pdisp->y, pdisp->stride,
534 MAX(0, (LCD_WIDTH-pdisp->width)/2), 0, /* x, y */
535 MIN(LCD_WIDTH, pdisp->width), move); /* w, h */
536 #endif
537 MYLCD_UPDATE();
541 /* Pan the viewing window down - move image up and fill in
542 the bottom */
543 static void pan_view_down(struct t_disp* pdisp)
545 int move;
547 move = MIN(VSCROLL, pdisp->height - pdisp->y - LCD_HEIGHT);
548 if (move > 0)
550 MYXLCD(scroll_up)(move); /* scroll up */
551 pdisp->y += move;
552 #ifdef HAVE_LCD_COLOR
553 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
555 /* Save the line that was on the last line of the display
556 and draw one extra line above then recover the line with
557 image data that had an error history when it was drawn.
559 move++, pdisp->y--;
560 rb->memcpy(rgb_linebuf,
561 rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
562 LCD_WIDTH*sizeof (fb_data));
565 yuv_bitmap_part(
566 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x,
567 pdisp->y + LCD_HEIGHT - move, pdisp->stride,
568 MAX(0, (LCD_WIDTH-pdisp->width)/2), LCD_HEIGHT - move, /* x, y */
569 MIN(LCD_WIDTH, pdisp->width), move, /* w, h */
570 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
572 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
574 /* Cover the first row drawn with previous image data. */
575 rb->memcpy(rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
576 rgb_linebuf,
577 LCD_WIDTH*sizeof (fb_data));
578 pdisp->y++;
580 #else
581 MYXLCD(gray_bitmap_part)(
582 pdisp->bitmap[0], pdisp->x,
583 pdisp->y + LCD_HEIGHT - move, pdisp->stride,
584 MAX(0, (LCD_WIDTH-pdisp->width)/2), LCD_HEIGHT - move, /* x, y */
585 MIN(LCD_WIDTH, pdisp->width), move); /* w, h */
586 #endif
587 MYLCD_UPDATE();
591 /* interactively scroll around the image */
592 int scroll_bmp(struct t_disp* pdisp)
594 int button;
595 int lastbutton = 0;
597 while (true)
599 if (slideshow_enabled)
600 button = rb->button_get_w_tmo(jpeg_settings.ss_timeout * HZ);
601 else button = rb->button_get(true);
603 running_slideshow = false;
605 switch(button)
607 case JPEG_LEFT:
608 if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE)
609 return change_filename(DIR_PREV);
610 case JPEG_LEFT | BUTTON_REPEAT:
611 pan_view_left(pdisp);
612 break;
614 case JPEG_RIGHT:
615 if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE)
616 return change_filename(DIR_NEXT);
617 case JPEG_RIGHT | BUTTON_REPEAT:
618 pan_view_right(pdisp);
619 break;
621 case JPEG_UP:
622 case JPEG_UP | BUTTON_REPEAT:
623 pan_view_up(pdisp);
624 break;
626 case JPEG_DOWN:
627 case JPEG_DOWN | BUTTON_REPEAT:
628 pan_view_down(pdisp);
629 break;
631 case BUTTON_NONE:
632 if (!slideshow_enabled)
633 break;
634 running_slideshow = true;
635 if (entries > 0)
636 return change_filename(DIR_NEXT);
637 break;
639 #ifdef JPEG_SLIDE_SHOW
640 case JPEG_SLIDE_SHOW:
641 slideshow_enabled = !slideshow_enabled;
642 running_slideshow = slideshow_enabled;
643 break;
644 #endif
646 #ifdef JPEG_NEXT_REPEAT
647 case JPEG_NEXT_REPEAT:
648 #endif
649 case JPEG_NEXT:
650 if (entries > 0)
651 return change_filename(DIR_NEXT);
652 break;
654 #ifdef JPEG_PREVIOUS_REPEAT
655 case JPEG_PREVIOUS_REPEAT:
656 #endif
657 case JPEG_PREVIOUS:
658 if (entries > 0)
659 return change_filename(DIR_PREV);
660 break;
662 case JPEG_ZOOM_IN:
663 #ifdef JPEG_ZOOM_PRE
664 if (lastbutton != JPEG_ZOOM_PRE)
665 break;
666 #endif
667 return ZOOM_IN;
668 break;
670 case JPEG_ZOOM_OUT:
671 #ifdef JPEG_ZOOM_PRE
672 if (lastbutton != JPEG_ZOOM_PRE)
673 break;
674 #endif
675 return ZOOM_OUT;
676 break;
677 #ifdef JPEG_RC_MENU
678 case JPEG_RC_MENU:
679 #endif
680 case JPEG_MENU:
681 #ifdef USEGSLIB
682 grey_show(false); /* switch off greyscale overlay */
683 #endif
684 if (show_menu() == 1)
685 return PLUGIN_OK;
687 #ifdef USEGSLIB
688 grey_show(true); /* switch on greyscale overlay */
689 #else
690 yuv_bitmap_part(
691 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
692 pdisp->x, pdisp->y, pdisp->stride,
693 MAX(0, (LCD_WIDTH - pdisp->width) / 2),
694 MAX(0, (LCD_HEIGHT - pdisp->height) / 2),
695 MIN(LCD_WIDTH, pdisp->width),
696 MIN(LCD_HEIGHT, pdisp->height),
697 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
698 MYLCD_UPDATE();
699 #endif
700 break;
701 default:
702 if (rb->default_event_handler_ex(button, cleanup, NULL)
703 == SYS_USB_CONNECTED)
704 return PLUGIN_USB_CONNECTED;
705 break;
707 } /* switch */
709 if (button != BUTTON_NONE)
710 lastbutton = button;
711 } /* while (true) */
714 /********************* main function *************************/
716 /* callback updating a progress meter while JPEG decoding */
717 void cb_progess(int current, int total)
719 rb->yield(); /* be nice to the other threads */
720 if(!running_slideshow)
722 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-8, LCD_WIDTH, 8, total, 0,
723 current, HORIZONTAL);
724 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
726 #ifndef USEGSLIB
727 else
729 /* in slideshow mode, keep gui interference to a minimum */
730 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-4, LCD_WIDTH, 4, total, 0,
731 current, HORIZONTAL);
732 rb->lcd_update_rect(0, LCD_HEIGHT-4, LCD_WIDTH, 4);
734 #endif
737 int jpegmem(struct jpeg *p_jpg, int ds)
739 int size;
741 size = (p_jpg->x_phys/ds/p_jpg->subsample_x[0])
742 * (p_jpg->y_phys/ds/p_jpg->subsample_y[0]);
743 #ifdef HAVE_LCD_COLOR
744 if (p_jpg->blocks > 1) /* colour, add requirements for chroma */
746 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[1])
747 * (p_jpg->y_phys/ds/p_jpg->subsample_y[1]);
748 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[2])
749 * (p_jpg->y_phys/ds/p_jpg->subsample_y[2]);
751 #endif
752 return size;
755 /* how far can we zoom in without running out of memory */
756 int min_downscale(struct jpeg *p_jpg, int bufsize)
758 int downscale = 8;
760 if (jpegmem(p_jpg, 8) > bufsize)
761 return 0; /* error, too large, even 1:8 doesn't fit */
763 while (downscale > 1 && jpegmem(p_jpg, downscale/2) <= bufsize)
764 downscale /= 2;
766 return downscale;
770 /* how far can we zoom out, to fit image into the LCD */
771 int max_downscale(struct jpeg *p_jpg)
773 int downscale = 1;
775 while (downscale < 8 && (p_jpg->x_size > LCD_WIDTH*downscale
776 || p_jpg->y_size > LCD_HEIGHT*downscale))
778 downscale *= 2;
781 return downscale;
785 /* return decoded or cached image */
786 struct t_disp* get_image(struct jpeg* p_jpg, int ds)
788 int w, h; /* used to center output */
789 int size; /* decompressed image size */
790 long time; /* measured ticks */
791 int status;
793 struct t_disp* p_disp = &disp[ds]; /* short cut */
795 if (p_disp->bitmap[0] != NULL)
797 return p_disp; /* we still have it */
800 /* assign image buffer */
802 /* physical size needed for decoding */
803 size = jpegmem(p_jpg, ds);
804 if (buf_size <= size)
805 { /* have to discard the current */
806 int i;
807 for (i=1; i<=8; i++)
808 disp[i].bitmap[0] = NULL; /* invalidate all bitmaps */
809 buf = buf_root; /* start again from the beginning of the buffer */
810 buf_size = root_size;
813 #ifdef HAVE_LCD_COLOR
814 if (p_jpg->blocks > 1) /* colour jpeg */
816 int i;
818 for (i = 1; i < 3; i++)
820 size = (p_jpg->x_phys / ds / p_jpg->subsample_x[i])
821 * (p_jpg->y_phys / ds / p_jpg->subsample_y[i]);
822 p_disp->bitmap[i] = buf;
823 buf += size;
824 buf_size -= size;
826 p_disp->csub_x = p_jpg->subsample_x[1];
827 p_disp->csub_y = p_jpg->subsample_y[1];
829 else
831 p_disp->csub_x = p_disp->csub_y = 0;
832 p_disp->bitmap[1] = p_disp->bitmap[2] = buf;
834 #endif
835 /* size may be less when decoded (if height is not block aligned) */
836 size = (p_jpg->x_phys/ds) * (p_jpg->y_size / ds);
837 p_disp->bitmap[0] = buf;
838 buf += size;
839 buf_size -= size;
841 if(!running_slideshow)
843 rb->snprintf(print, sizeof(print), "decoding %d*%d",
844 p_jpg->x_size/ds, p_jpg->y_size/ds);
845 rb->lcd_puts(0, 3, print);
846 rb->lcd_update();
849 /* update image properties */
850 p_disp->width = p_jpg->x_size / ds;
851 p_disp->stride = p_jpg->x_phys / ds; /* use physical size for stride */
852 p_disp->height = p_jpg->y_size / ds;
854 /* the actual decoding */
855 time = *rb->current_tick;
856 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
857 rb->cpu_boost(true);
858 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess);
859 rb->cpu_boost(false);
860 #else
861 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess);
862 #endif
863 if (status)
865 rb->splashf(HZ, "decode error %d", status);
866 file_pt[curfile] = '\0';
867 return NULL;
869 time = *rb->current_tick - time;
871 if(!running_slideshow)
873 rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ);
874 rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */
875 rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print);
876 rb->lcd_update();
879 return p_disp;
883 /* set the view to the given center point, limit if necessary */
884 void set_view (struct t_disp* p_disp, int cx, int cy)
886 int x, y;
888 /* plain center to available width/height */
889 x = cx - MIN(LCD_WIDTH, p_disp->width) / 2;
890 y = cy - MIN(LCD_HEIGHT, p_disp->height) / 2;
892 /* limit against upper image size */
893 x = MIN(p_disp->width - LCD_WIDTH, x);
894 y = MIN(p_disp->height - LCD_HEIGHT, y);
896 /* limit against negative side */
897 x = MAX(0, x);
898 y = MAX(0, y);
900 p_disp->x = x; /* set the values */
901 p_disp->y = y;
905 /* calculate the view center based on the bitmap position */
906 void get_view(struct t_disp* p_disp, int* p_cx, int* p_cy)
908 *p_cx = p_disp->x + MIN(LCD_WIDTH, p_disp->width) / 2;
909 *p_cy = p_disp->y + MIN(LCD_HEIGHT, p_disp->height) / 2;
913 /* load, decode, display the image */
914 int load_and_show(char* filename)
916 int fd;
917 int filesize;
918 unsigned char* buf_jpeg; /* compressed JPEG image */
919 int status;
920 struct t_disp* p_disp; /* currenly displayed image */
921 int cx, cy; /* view center */
923 fd = rb->open(filename, O_RDONLY);
924 if (fd < 0)
926 rb->snprintf(print,sizeof(print),"err opening %s:%d",filename,fd);
927 rb->splash(HZ, print);
928 return PLUGIN_ERROR;
930 filesize = rb->filesize(fd);
931 rb->memset(&disp, 0, sizeof(disp));
933 buf = buf_images + filesize;
934 buf_size = buf_images_size - filesize;
935 /* allocate JPEG buffer */
936 buf_jpeg = buf_images;
938 buf_root = buf; /* we can start the decompressed images behind it */
939 root_size = buf_size;
941 if (buf_size <= 0)
943 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
944 if(plug_buf)
946 rb->close(fd);
947 rb->lcd_setfont(FONT_SYSFIXED);
948 rb->lcd_clear_display();
949 rb->snprintf(print,sizeof(print),"%s:",rb->strrchr(filename,'/')+1);
950 rb->lcd_puts(0,0,print);
951 rb->lcd_puts(0,1,"Not enough plugin memory!");
952 rb->lcd_puts(0,2,"Zoom In: Stop playback.");
953 if(entries>1)
954 rb->lcd_puts(0,3,"Left/Right: Skip File.");
955 rb->lcd_puts(0,4,"Off: Quit.");
956 rb->lcd_update();
957 rb->lcd_setfont(FONT_UI);
959 rb->button_clear_queue();
961 while (1)
963 int button = rb->button_get(true);
964 switch(button)
966 case JPEG_ZOOM_IN:
967 plug_buf = false;
968 buf_images = rb->plugin_get_audio_buffer(
969 (size_t *)&buf_images_size);
970 /*try again this file, now using the audio buffer */
971 return PLUGIN_OTHER;
972 #ifdef JPEG_RC_MENU
973 case JPEG_RC_MENU:
974 #endif
975 case JPEG_MENU:
976 return PLUGIN_OK;
978 case JPEG_LEFT:
979 if(entries>1)
981 rb->lcd_clear_display();
982 return change_filename(DIR_PREV);
984 break;
986 case JPEG_RIGHT:
987 if(entries>1)
989 rb->lcd_clear_display();
990 return change_filename(DIR_NEXT);
992 break;
993 default:
994 if(rb->default_event_handler_ex(button, cleanup, NULL)
995 == SYS_USB_CONNECTED)
996 return PLUGIN_USB_CONNECTED;
1001 else
1002 #endif
1004 rb->splash(HZ, "Out of Memory");
1005 rb->close(fd);
1006 return PLUGIN_ERROR;
1010 if(!running_slideshow)
1012 #if LCD_DEPTH > 1
1013 rb->lcd_set_foreground(LCD_WHITE);
1014 rb->lcd_set_background(LCD_BLACK);
1015 rb->lcd_set_backdrop(NULL);
1016 #endif
1018 rb->lcd_clear_display();
1019 rb->snprintf(print, sizeof(print), "%s:", rb->strrchr(filename,'/')+1);
1020 rb->lcd_puts(0, 0, print);
1021 rb->lcd_update();
1023 rb->snprintf(print, sizeof(print), "loading %d bytes", filesize);
1024 rb->lcd_puts(0, 1, print);
1025 rb->lcd_update();
1028 rb->read(fd, buf_jpeg, filesize);
1029 rb->close(fd);
1031 if(!running_slideshow)
1033 rb->snprintf(print, sizeof(print), "decoding markers");
1034 rb->lcd_puts(0, 2, print);
1035 rb->lcd_update();
1037 #ifndef SIMULATOR
1038 else if(immediate_ata_off)
1040 /* running slideshow and time is long enough: power down disk */
1041 rb->storage_sleep();
1043 #endif
1045 rb->memset(&jpg, 0, sizeof(jpg)); /* clear info struct */
1046 /* process markers, unstuffing */
1047 status = process_markers(buf_jpeg, filesize, &jpg);
1049 if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0))
1050 { /* bad format or minimum components not contained */
1051 rb->splashf(HZ, "unsupported %d", status);
1052 file_pt[curfile] = '\0';
1053 return change_filename(direction);
1056 if (!(status & DHT)) /* if no Huffman table present: */
1057 default_huff_tbl(&jpg); /* use default */
1058 build_lut(&jpg); /* derive Huffman and other lookup-tables */
1060 if(!running_slideshow)
1062 rb->snprintf(print, sizeof(print), "image %dx%d", jpg.x_size, jpg.y_size);
1063 rb->lcd_puts(0, 2, print);
1064 rb->lcd_update();
1066 ds_max = max_downscale(&jpg); /* check display constraint */
1067 ds_min = min_downscale(&jpg, buf_size); /* check memory constraint */
1068 if (ds_min == 0)
1070 rb->splash(HZ, "too large");
1071 file_pt[curfile] = '\0';
1072 return change_filename(direction);
1075 ds = ds_max; /* initials setting */
1076 cx = jpg.x_size/ds/2; /* center the view */
1077 cy = jpg.y_size/ds/2;
1079 do /* loop the image prepare and decoding when zoomed */
1081 p_disp = get_image(&jpg, ds); /* decode or fetch from cache */
1082 if (p_disp == NULL)
1083 return change_filename(direction);
1085 set_view(p_disp, cx, cy);
1087 if(!running_slideshow)
1089 rb->snprintf(print, sizeof(print), "showing %dx%d",
1090 p_disp->width, p_disp->height);
1091 rb->lcd_puts(0, 3, print);
1092 rb->lcd_update();
1094 MYLCD(clear_display)();
1095 #ifdef HAVE_LCD_COLOR
1096 yuv_bitmap_part(
1097 p_disp->bitmap, p_disp->csub_x, p_disp->csub_y,
1098 p_disp->x, p_disp->y, p_disp->stride,
1099 MAX(0, (LCD_WIDTH - p_disp->width) / 2),
1100 MAX(0, (LCD_HEIGHT - p_disp->height) / 2),
1101 MIN(LCD_WIDTH, p_disp->width),
1102 MIN(LCD_HEIGHT, p_disp->height),
1103 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
1104 #else
1105 MYXLCD(gray_bitmap_part)(
1106 p_disp->bitmap[0], p_disp->x, p_disp->y, p_disp->stride,
1107 MAX(0, (LCD_WIDTH - p_disp->width) / 2),
1108 MAX(0, (LCD_HEIGHT - p_disp->height) / 2),
1109 MIN(LCD_WIDTH, p_disp->width),
1110 MIN(LCD_HEIGHT, p_disp->height));
1111 #endif
1112 MYLCD_UPDATE();
1114 #ifdef USEGSLIB
1115 grey_show(true); /* switch on greyscale overlay */
1116 #endif
1118 /* drawing is now finished, play around with scrolling
1119 * until you press OFF or connect USB
1121 while (1)
1123 status = scroll_bmp(p_disp);
1124 if (status == ZOOM_IN)
1126 if (ds > ds_min)
1128 ds /= 2; /* reduce downscaling to zoom in */
1129 get_view(p_disp, &cx, &cy);
1130 cx *= 2; /* prepare the position in the new image */
1131 cy *= 2;
1133 else
1134 continue;
1137 if (status == ZOOM_OUT)
1139 if (ds < ds_max)
1141 ds *= 2; /* increase downscaling to zoom out */
1142 get_view(p_disp, &cx, &cy);
1143 cx /= 2; /* prepare the position in the new image */
1144 cy /= 2;
1146 else
1147 continue;
1149 break;
1152 #ifdef USEGSLIB
1153 grey_show(false); /* switch off overlay */
1154 #endif
1155 rb->lcd_clear_display();
1157 while (status != PLUGIN_OK && status != PLUGIN_USB_CONNECTED
1158 && status != PLUGIN_OTHER);
1159 #ifdef USEGSLIB
1160 rb->lcd_update();
1161 #endif
1162 return status;
1165 /******************** Plugin entry point *********************/
1167 enum plugin_status plugin_start(const void* parameter)
1169 int condition;
1170 #ifdef USEGSLIB
1171 long greysize; /* helper */
1172 #endif
1173 #if LCD_DEPTH > 1
1174 old_backdrop = rb->lcd_get_backdrop();
1175 #endif
1177 if(!parameter) return PLUGIN_ERROR;
1179 rb->strcpy(np_file, parameter);
1180 get_pic_list();
1182 if(!entries) return PLUGIN_ERROR;
1184 #if (PLUGIN_BUFFER_SIZE >= MIN_MEM) && !defined(SIMULATOR)
1185 if(rb->audio_status())
1187 buf = rb->plugin_get_buffer((size_t *)&buf_size) +
1188 (entries * sizeof(char**));
1189 buf_size -= (entries * sizeof(char**));
1190 plug_buf = true;
1192 else
1193 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
1194 #else
1195 buf = rb->plugin_get_audio_buffer(&buf_size) +
1196 (entries * sizeof(char**));
1197 buf_size -= (entries * sizeof(char**));
1198 #endif
1200 #ifdef USEGSLIB
1201 if (!grey_init(buf, buf_size, GREY_ON_COP,
1202 LCD_WIDTH, LCD_HEIGHT, &greysize))
1204 rb->splash(HZ, "grey buf error");
1205 return PLUGIN_ERROR;
1207 buf += greysize;
1208 buf_size -= greysize;
1209 #endif
1211 /* should be ok to just load settings since the plugin itself has
1212 just been loaded from disk and the drive should be spinning */
1213 configfile_load(JPEG_CONFIGFILE, jpeg_config,
1214 ARRAYLEN(jpeg_config), JPEG_SETTINGS_MINVERSION);
1215 old_settings = jpeg_settings;
1217 buf_images = buf; buf_images_size = buf_size;
1219 /* Turn off backlight timeout */
1220 backlight_force_on(); /* backlight control in lib/helper.c */
1224 condition = load_and_show(np_file);
1225 }while (condition != PLUGIN_OK && condition != PLUGIN_USB_CONNECTED
1226 && condition != PLUGIN_ERROR);
1228 if (rb->memcmp(&jpeg_settings, &old_settings, sizeof (jpeg_settings)))
1230 /* Just in case drive has to spin, keep it from looking locked */
1231 rb->splash(0, "Saving Settings");
1232 configfile_save(JPEG_CONFIGFILE, jpeg_config,
1233 ARRAYLEN(jpeg_config), JPEG_SETTINGS_VERSION);
1236 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
1237 /* set back ata spindown time in case we changed it */
1238 rb->storage_spindown(rb->global_settings->disk_spindown);
1239 #endif
1241 /* Turn on backlight timeout (revert to settings) */
1242 backlight_use_settings(); /* backlight control in lib/helper.c */
1244 #ifdef USEGSLIB
1245 grey_release(); /* deinitialize */
1246 #endif
1248 return condition;