jpeg/png: change file list handling a bit.
[kugel-rb.git] / apps / plugins / jpeg / jpeg.c
blob44907e6bd25f3a3549c703ad4a39f780b2e39845
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 bool jpg_ext(const char ext[])
190 if(!ext)
191 return false;
192 if(!rb->strcasecmp(ext,".jpg") ||
193 !rb->strcasecmp(ext,".jpe") ||
194 !rb->strcasecmp(ext,".jpeg"))
195 return true;
196 else
197 return false;
200 /*Read directory contents for scrolling. */
201 void get_pic_list(void)
203 int i;
204 struct entry *dircache;
205 char *pname;
206 tree = rb->tree_get_context();
207 dircache = tree->dircache;
209 file_pt = (char **) buf;
211 /* Remove path and leave only the name.*/
212 pname = rb->strrchr(np_file,'/');
213 pname++;
215 for (i = 0; i < tree->filesindir; i++)
217 if (!(dircache[i].attr & ATTR_DIRECTORY)
218 && jpg_ext(rb->strrchr(dircache[i].name,'.')))
220 file_pt[entries] = dircache[i].name;
221 /* Set Selected File. */
222 if (!rb->strcmp(file_pt[entries], pname))
223 curfile = entries;
224 entries++;
228 buf += (entries * sizeof(char**));
229 buf_size -= (entries * sizeof(char**));
232 int change_filename(int direct)
234 int count = 0;
235 direction = direct;
237 if(direct == DIR_PREV)
241 count++;
242 if(curfile == 0)
243 curfile = entries - 1;
244 else
245 curfile--;
246 }while(file_pt[curfile] == NULL && count < entries);
247 /* we "erase" the file name if we encounter
248 * a non-supported file, so skip it now */
250 else /* DIR_NEXT/DIR_NONE */
254 count++;
255 if(curfile == entries - 1)
256 curfile = 0;
257 else
258 curfile++;
259 }while(file_pt[curfile] == NULL && count < entries);
262 if(count == entries)
264 rb->splash(HZ, "No supported files");
265 return PLUGIN_ERROR;
267 if(rb->strlen(tree->currdir) > 1)
269 rb->strcpy(np_file, tree->currdir);
270 rb->strcat(np_file, "/");
272 else
273 rb->strcpy(np_file, tree->currdir);
275 rb->strcat(np_file, file_pt[curfile]);
277 return PLUGIN_OTHER;
280 /* switch off overlay, for handling SYS_ events */
281 void cleanup(void *parameter)
283 (void)parameter;
284 #ifdef USEGSLIB
285 grey_show(false);
286 #endif
289 #define VSCROLL (LCD_HEIGHT/8)
290 #define HSCROLL (LCD_WIDTH/10)
292 #define ZOOM_IN 100 /* return codes for below function */
293 #define ZOOM_OUT 101
295 #ifdef HAVE_LCD_COLOR
296 bool set_option_grayscale(void)
298 bool gray = jpeg_settings.colour_mode == COLOURMODE_GRAY;
299 rb->set_bool("Grayscale", &gray);
300 jpeg_settings.colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR;
301 return false;
304 bool set_option_dithering(void)
306 static const struct opt_items dithering[DITHER_NUM_MODES] = {
307 [DITHER_NONE] = { "Off", -1 },
308 [DITHER_ORDERED] = { "Ordered", -1 },
309 [DITHER_DIFFUSION] = { "Diffusion", -1 },
312 rb->set_option("Dithering", &jpeg_settings.dither_mode, INT,
313 dithering, DITHER_NUM_MODES, NULL);
314 return false;
317 MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale",
318 set_option_grayscale, NULL, NULL, Icon_NOICON);
319 MENUITEM_FUNCTION(dithering_item, 0, "Dithering",
320 set_option_dithering, NULL, NULL, Icon_NOICON);
321 MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON,
322 &grayscale_item, &dithering_item);
324 static void display_options(void)
326 rb->do_menu(&display_menu, NULL, NULL, false);
328 #endif /* HAVE_LCD_COLOR */
330 int show_menu(void) /* return 1 to quit */
332 #if LCD_DEPTH > 1
333 rb->lcd_set_backdrop(old_backdrop);
334 #ifdef HAVE_LCD_COLOR
335 rb->lcd_set_foreground(rb->global_settings->fg_color);
336 rb->lcd_set_background(rb->global_settings->bg_color);
337 #else
338 rb->lcd_set_foreground(LCD_BLACK);
339 rb->lcd_set_background(LCD_WHITE);
340 #endif
341 #endif
342 int result;
344 enum menu_id
346 MIID_RETURN = 0,
347 MIID_TOGGLE_SS_MODE,
348 MIID_CHANGE_SS_MODE,
349 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
350 MIID_SHOW_PLAYBACK_MENU,
351 #endif
352 #ifdef HAVE_LCD_COLOR
353 MIID_DISPLAY_OPTIONS,
354 #endif
355 MIID_QUIT,
358 MENUITEM_STRINGLIST(menu, "Jpeg Menu", NULL,
359 "Return", "Toggle Slideshow Mode",
360 "Change Slideshow Time",
361 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
362 "Show Playback Menu",
363 #endif
364 #ifdef HAVE_LCD_COLOR
365 "Display Options",
366 #endif
367 "Quit");
369 static const struct opt_items slideshow[2] = {
370 { "Disable", -1 },
371 { "Enable", -1 },
374 result=rb->do_menu(&menu, NULL, NULL, false);
376 switch (result)
378 case MIID_RETURN:
379 break;
380 case MIID_TOGGLE_SS_MODE:
381 rb->set_option("Toggle Slideshow", &slideshow_enabled, INT,
382 slideshow , 2, NULL);
383 break;
384 case MIID_CHANGE_SS_MODE:
385 rb->set_int("Slideshow Time", "s", UNIT_SEC,
386 &jpeg_settings.ss_timeout, NULL, 1,
387 SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL);
388 break;
390 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
391 case MIID_SHOW_PLAYBACK_MENU:
392 if (plug_buf)
394 playback_control(NULL);
396 else
398 rb->splash(HZ, "Cannot restart playback");
400 break;
401 #endif
402 #ifdef HAVE_LCD_COLOR
403 case MIID_DISPLAY_OPTIONS:
404 display_options();
405 break;
406 #endif
407 case MIID_QUIT:
408 return 1;
409 break;
412 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
413 /* change ata spindown time based on slideshow time setting */
414 immediate_ata_off = false;
415 rb->storage_spindown(rb->global_settings->disk_spindown);
417 if (slideshow_enabled)
419 if(jpeg_settings.ss_timeout < 10)
421 /* slideshow times < 10s keep disk spinning */
422 rb->storage_spindown(0);
424 else if (!rb->mp3_is_playing())
426 /* slideshow times > 10s and not playing: ata_off after load */
427 immediate_ata_off = true;
430 #endif
431 #if LCD_DEPTH > 1
432 rb->lcd_set_backdrop(NULL);
433 rb->lcd_set_foreground(LCD_WHITE);
434 rb->lcd_set_background(LCD_BLACK);
435 #endif
436 rb->lcd_clear_display();
437 return 0;
440 void draw_image_rect(struct t_disp* pdisp, int x, int y, int width, int height)
442 #ifdef HAVE_LCD_COLOR
443 yuv_bitmap_part(
444 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
445 pdisp->x + x, pdisp->y + y, pdisp->stride,
446 x + MAX(0, (LCD_WIDTH - pdisp->width) / 2),
447 y + MAX(0, (LCD_HEIGHT - pdisp->height) / 2),
448 width, height,
449 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
450 #else
451 MYXLCD(gray_bitmap_part)(
452 pdisp->bitmap[0], pdisp->x + x, pdisp->y + y, pdisp->stride,
453 x + MAX(0, (LCD_WIDTH-pdisp->width)/2),
454 y + MAX(0, (LCD_HEIGHT-pdisp->height)/2),
455 width, height);
456 #endif
459 /* Pan the viewing window right - move image to the left and fill in
460 the right-hand side */
461 static void pan_view_right(struct t_disp* pdisp)
463 int move;
465 move = MIN(HSCROLL, pdisp->width - pdisp->x - LCD_WIDTH);
466 if (move > 0)
468 MYXLCD(scroll_left)(move); /* scroll left */
469 pdisp->x += move;
470 draw_image_rect(pdisp, LCD_WIDTH - move, 0, move, pdisp->height-pdisp->y);
471 MYLCD_UPDATE();
475 /* Pan the viewing window left - move image to the right and fill in
476 the left-hand side */
477 static void pan_view_left(struct t_disp* pdisp)
479 int move;
481 move = MIN(HSCROLL, pdisp->x);
482 if (move > 0)
484 MYXLCD(scroll_right)(move); /* scroll right */
485 pdisp->x -= move;
486 draw_image_rect(pdisp, 0, 0, move, pdisp->height-pdisp->y);
487 MYLCD_UPDATE();
491 /* Pan the viewing window up - move image down and fill in
492 the top */
493 static void pan_view_up(struct t_disp* pdisp)
495 int move;
497 move = MIN(VSCROLL, pdisp->y);
498 if (move > 0)
500 MYXLCD(scroll_down)(move); /* scroll down */
501 pdisp->y -= move;
502 #ifdef HAVE_LCD_COLOR
503 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
505 /* Draw over the band at the top of the last update
506 caused by lack of error history on line zero. */
507 move = MIN(move + 1, pdisp->y + pdisp->height);
509 #endif
510 draw_image_rect(pdisp, 0, 0, pdisp->width-pdisp->x, move);
511 MYLCD_UPDATE();
515 /* Pan the viewing window down - move image up and fill in
516 the bottom */
517 static void pan_view_down(struct t_disp* pdisp)
519 int move;
521 move = MIN(VSCROLL, pdisp->height - pdisp->y - LCD_HEIGHT);
522 if (move > 0)
524 MYXLCD(scroll_up)(move); /* scroll up */
525 pdisp->y += move;
526 #ifdef HAVE_LCD_COLOR
527 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
529 /* Save the line that was on the last line of the display
530 and draw one extra line above then recover the line with
531 image data that had an error history when it was drawn.
533 move++, pdisp->y--;
534 rb->memcpy(rgb_linebuf,
535 rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
536 LCD_WIDTH*sizeof (fb_data));
538 #endif
540 draw_image_rect(pdisp, 0, LCD_HEIGHT - move, pdisp->width-pdisp->x, move);
542 #ifdef HAVE_LCD_COLOR
543 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
545 /* Cover the first row drawn with previous image data. */
546 rb->memcpy(rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
547 rgb_linebuf,
548 LCD_WIDTH*sizeof (fb_data));
549 pdisp->y++;
551 #endif
552 MYLCD_UPDATE();
556 /* interactively scroll around the image */
557 int scroll_bmp(struct t_disp* pdisp)
559 int button;
560 int lastbutton = 0;
562 while (true)
564 if (slideshow_enabled)
565 button = rb->button_get_w_tmo(jpeg_settings.ss_timeout * HZ);
566 else button = rb->button_get(true);
568 running_slideshow = false;
570 switch(button)
572 case JPEG_LEFT:
573 if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE)
574 return change_filename(DIR_PREV);
575 case JPEG_LEFT | BUTTON_REPEAT:
576 pan_view_left(pdisp);
577 break;
579 case JPEG_RIGHT:
580 if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE)
581 return change_filename(DIR_NEXT);
582 case JPEG_RIGHT | BUTTON_REPEAT:
583 pan_view_right(pdisp);
584 break;
586 case JPEG_UP:
587 case JPEG_UP | BUTTON_REPEAT:
588 pan_view_up(pdisp);
589 break;
591 case JPEG_DOWN:
592 case JPEG_DOWN | BUTTON_REPEAT:
593 pan_view_down(pdisp);
594 break;
596 case BUTTON_NONE:
597 if (!slideshow_enabled)
598 break;
599 running_slideshow = true;
600 if (entries > 0)
601 return change_filename(DIR_NEXT);
602 break;
604 #ifdef JPEG_SLIDE_SHOW
605 case JPEG_SLIDE_SHOW:
606 slideshow_enabled = !slideshow_enabled;
607 running_slideshow = slideshow_enabled;
608 break;
609 #endif
611 #ifdef JPEG_NEXT_REPEAT
612 case JPEG_NEXT_REPEAT:
613 #endif
614 case JPEG_NEXT:
615 if (entries > 0)
616 return change_filename(DIR_NEXT);
617 break;
619 #ifdef JPEG_PREVIOUS_REPEAT
620 case JPEG_PREVIOUS_REPEAT:
621 #endif
622 case JPEG_PREVIOUS:
623 if (entries > 0)
624 return change_filename(DIR_PREV);
625 break;
627 case JPEG_ZOOM_IN:
628 #ifdef JPEG_ZOOM_PRE
629 if (lastbutton != JPEG_ZOOM_PRE)
630 break;
631 #endif
632 return ZOOM_IN;
633 break;
635 case JPEG_ZOOM_OUT:
636 #ifdef JPEG_ZOOM_PRE
637 if (lastbutton != JPEG_ZOOM_PRE)
638 break;
639 #endif
640 return ZOOM_OUT;
641 break;
642 #ifdef JPEG_RC_MENU
643 case JPEG_RC_MENU:
644 #endif
645 case JPEG_MENU:
646 #ifdef USEGSLIB
647 grey_show(false); /* switch off greyscale overlay */
648 #endif
649 if (show_menu() == 1)
650 return PLUGIN_OK;
652 #ifdef USEGSLIB
653 grey_show(true); /* switch on greyscale overlay */
654 #else
655 draw_image_rect(pdisp, 0, 0,
656 pdisp->width-pdisp->x, pdisp->height-pdisp->y);
657 MYLCD_UPDATE();
658 #endif
659 break;
660 default:
661 if (rb->default_event_handler_ex(button, cleanup, NULL)
662 == SYS_USB_CONNECTED)
663 return PLUGIN_USB_CONNECTED;
664 break;
666 } /* switch */
668 if (button != BUTTON_NONE)
669 lastbutton = button;
670 } /* while (true) */
673 /********************* main function *************************/
675 /* callback updating a progress meter while JPEG decoding */
676 void cb_progess(int current, int total)
678 rb->yield(); /* be nice to the other threads */
679 if(!running_slideshow)
681 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-8, LCD_WIDTH, 8, total, 0,
682 current, HORIZONTAL);
683 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
685 #ifndef USEGSLIB
686 else
688 /* in slideshow mode, keep gui interference to a minimum */
689 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-4, LCD_WIDTH, 4, total, 0,
690 current, HORIZONTAL);
691 rb->lcd_update_rect(0, LCD_HEIGHT-4, LCD_WIDTH, 4);
693 #endif
696 int jpegmem(struct jpeg *p_jpg, int ds)
698 int size;
700 size = (p_jpg->x_phys/ds/p_jpg->subsample_x[0])
701 * (p_jpg->y_phys/ds/p_jpg->subsample_y[0]);
702 #ifdef HAVE_LCD_COLOR
703 if (p_jpg->blocks > 1) /* colour, add requirements for chroma */
705 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[1])
706 * (p_jpg->y_phys/ds/p_jpg->subsample_y[1]);
707 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[2])
708 * (p_jpg->y_phys/ds/p_jpg->subsample_y[2]);
710 #endif
711 return size;
714 /* how far can we zoom in without running out of memory */
715 int min_downscale(struct jpeg *p_jpg, int bufsize)
717 int downscale = 8;
719 if (jpegmem(p_jpg, 8) > bufsize)
720 return 0; /* error, too large, even 1:8 doesn't fit */
722 while (downscale > 1 && jpegmem(p_jpg, downscale/2) <= bufsize)
723 downscale /= 2;
725 return downscale;
729 /* how far can we zoom out, to fit image into the LCD */
730 int max_downscale(struct jpeg *p_jpg)
732 int downscale = 1;
734 while (downscale < 8 && (p_jpg->x_size > LCD_WIDTH*downscale
735 || p_jpg->y_size > LCD_HEIGHT*downscale))
737 downscale *= 2;
740 return downscale;
744 /* return decoded or cached image */
745 struct t_disp* get_image(struct jpeg* p_jpg, int ds)
747 int w, h; /* used to center output */
748 int size; /* decompressed image size */
749 long time; /* measured ticks */
750 int status;
752 struct t_disp* p_disp = &disp[ds]; /* short cut */
754 if (p_disp->bitmap[0] != NULL)
756 return p_disp; /* we still have it */
759 /* assign image buffer */
761 /* physical size needed for decoding */
762 size = jpegmem(p_jpg, ds);
763 if (buf_size <= size)
764 { /* have to discard the current */
765 int i;
766 for (i=1; i<=8; i++)
767 disp[i].bitmap[0] = NULL; /* invalidate all bitmaps */
768 buf = buf_root; /* start again from the beginning of the buffer */
769 buf_size = root_size;
772 #ifdef HAVE_LCD_COLOR
773 if (p_jpg->blocks > 1) /* colour jpeg */
775 int i;
777 for (i = 1; i < 3; i++)
779 size = (p_jpg->x_phys / ds / p_jpg->subsample_x[i])
780 * (p_jpg->y_phys / ds / p_jpg->subsample_y[i]);
781 p_disp->bitmap[i] = buf;
782 buf += size;
783 buf_size -= size;
785 p_disp->csub_x = p_jpg->subsample_x[1];
786 p_disp->csub_y = p_jpg->subsample_y[1];
788 else
790 p_disp->csub_x = p_disp->csub_y = 0;
791 p_disp->bitmap[1] = p_disp->bitmap[2] = buf;
793 #endif
794 /* size may be less when decoded (if height is not block aligned) */
795 size = (p_jpg->x_phys/ds) * (p_jpg->y_size / ds);
796 p_disp->bitmap[0] = buf;
797 buf += size;
798 buf_size -= size;
800 if(!running_slideshow)
802 rb->snprintf(print, sizeof(print), "decoding %d*%d",
803 p_jpg->x_size/ds, p_jpg->y_size/ds);
804 rb->lcd_puts(0, 3, print);
805 rb->lcd_update();
808 /* update image properties */
809 p_disp->width = p_jpg->x_size / ds;
810 p_disp->stride = p_jpg->x_phys / ds; /* use physical size for stride */
811 p_disp->height = p_jpg->y_size / ds;
813 /* the actual decoding */
814 time = *rb->current_tick;
815 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
816 rb->cpu_boost(true);
817 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess);
818 rb->cpu_boost(false);
819 #else
820 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess);
821 #endif
822 if (status)
824 rb->splashf(HZ, "decode error %d", status);
825 file_pt[curfile] = NULL;
826 return NULL;
828 time = *rb->current_tick - time;
830 if(!running_slideshow)
832 rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ);
833 rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */
834 rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print);
835 rb->lcd_update();
838 return p_disp;
842 /* set the view to the given center point, limit if necessary */
843 void set_view (struct t_disp* p_disp, int cx, int cy)
845 int x, y;
847 /* plain center to available width/height */
848 x = cx - MIN(LCD_WIDTH, p_disp->width) / 2;
849 y = cy - MIN(LCD_HEIGHT, p_disp->height) / 2;
851 /* limit against upper image size */
852 x = MIN(p_disp->width - LCD_WIDTH, x);
853 y = MIN(p_disp->height - LCD_HEIGHT, y);
855 /* limit against negative side */
856 x = MAX(0, x);
857 y = MAX(0, y);
859 p_disp->x = x; /* set the values */
860 p_disp->y = y;
864 /* calculate the view center based on the bitmap position */
865 void get_view(struct t_disp* p_disp, int* p_cx, int* p_cy)
867 *p_cx = p_disp->x + MIN(LCD_WIDTH, p_disp->width) / 2;
868 *p_cy = p_disp->y + MIN(LCD_HEIGHT, p_disp->height) / 2;
872 /* load, decode, display the image */
873 int load_and_show(char* filename)
875 int fd;
876 int filesize;
877 unsigned char* buf_jpeg; /* compressed JPEG image */
878 int status;
879 struct t_disp* p_disp; /* currenly displayed image */
880 int cx, cy; /* view center */
882 fd = rb->open(filename, O_RDONLY);
883 if (fd < 0)
885 rb->snprintf(print,sizeof(print),"err opening %s:%d",filename,fd);
886 rb->splash(HZ, print);
887 return PLUGIN_ERROR;
889 filesize = rb->filesize(fd);
890 rb->memset(&disp, 0, sizeof(disp));
892 buf = buf_images + filesize;
893 buf_size = buf_images_size - filesize;
894 /* allocate JPEG buffer */
895 buf_jpeg = buf_images;
897 buf_root = buf; /* we can start the decompressed images behind it */
898 root_size = buf_size;
900 if (buf_size <= 0)
902 rb->close(fd);
903 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
904 if(plug_buf)
906 rb->lcd_setfont(FONT_SYSFIXED);
907 rb->lcd_clear_display();
908 rb->snprintf(print,sizeof(print),"%s:",rb->strrchr(filename,'/')+1);
909 rb->lcd_puts(0,0,print);
910 rb->lcd_puts(0,1,"Not enough plugin memory!");
911 rb->lcd_puts(0,2,"Zoom In: Stop playback.");
912 if(entries>1)
913 rb->lcd_puts(0,3,"Left/Right: Skip File.");
914 rb->lcd_puts(0,4,"Off: Quit.");
915 rb->lcd_update();
916 rb->lcd_setfont(FONT_UI);
918 rb->button_clear_queue();
920 while (1)
922 int button = rb->button_get(true);
923 switch(button)
925 case JPEG_ZOOM_IN:
926 plug_buf = false;
927 buf_images = rb->plugin_get_audio_buffer(
928 (size_t *)&buf_images_size);
929 /*try again this file, now using the audio buffer */
930 return PLUGIN_OTHER;
931 #ifdef JPEG_RC_MENU
932 case JPEG_RC_MENU:
933 #endif
934 case JPEG_MENU:
935 return PLUGIN_OK;
937 case JPEG_LEFT:
938 if(entries>1)
940 rb->lcd_clear_display();
941 return change_filename(DIR_PREV);
943 break;
945 case JPEG_RIGHT:
946 if(entries>1)
948 rb->lcd_clear_display();
949 return change_filename(DIR_NEXT);
951 break;
952 default:
953 if(rb->default_event_handler_ex(button, cleanup, NULL)
954 == SYS_USB_CONNECTED)
955 return PLUGIN_USB_CONNECTED;
960 else
961 #endif
963 rb->splash(HZ, "Out of Memory");
964 return PLUGIN_ERROR;
968 if(!running_slideshow)
970 #if LCD_DEPTH > 1
971 rb->lcd_set_foreground(LCD_WHITE);
972 rb->lcd_set_background(LCD_BLACK);
973 rb->lcd_set_backdrop(NULL);
974 #endif
976 rb->lcd_clear_display();
977 rb->snprintf(print, sizeof(print), "%s:", rb->strrchr(filename,'/')+1);
978 rb->lcd_puts(0, 0, print);
979 rb->lcd_update();
981 rb->snprintf(print, sizeof(print), "loading %d bytes", filesize);
982 rb->lcd_puts(0, 1, print);
983 rb->lcd_update();
986 rb->read(fd, buf_jpeg, filesize);
987 rb->close(fd);
989 if(!running_slideshow)
991 rb->snprintf(print, sizeof(print), "decoding markers");
992 rb->lcd_puts(0, 2, print);
993 rb->lcd_update();
995 #ifndef SIMULATOR
996 else if(immediate_ata_off)
998 /* running slideshow and time is long enough: power down disk */
999 rb->storage_sleep();
1001 #endif
1003 rb->memset(&jpg, 0, sizeof(jpg)); /* clear info struct */
1004 /* process markers, unstuffing */
1005 status = process_markers(buf_jpeg, filesize, &jpg);
1007 if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0))
1008 { /* bad format or minimum components not contained */
1009 rb->splashf(HZ, "unsupported %d", status);
1010 file_pt[curfile] = NULL;
1011 return change_filename(direction);
1014 if (!(status & DHT)) /* if no Huffman table present: */
1015 default_huff_tbl(&jpg); /* use default */
1016 build_lut(&jpg); /* derive Huffman and other lookup-tables */
1018 if(!running_slideshow)
1020 rb->snprintf(print, sizeof(print), "image %dx%d", jpg.x_size, jpg.y_size);
1021 rb->lcd_puts(0, 2, print);
1022 rb->lcd_update();
1024 ds_max = max_downscale(&jpg); /* check display constraint */
1025 ds_min = min_downscale(&jpg, buf_size); /* check memory constraint */
1026 if (ds_min == 0)
1028 rb->splash(HZ, "too large");
1029 file_pt[curfile] = NULL;
1030 return change_filename(direction);
1033 ds = ds_max; /* initialize setting */
1034 cx = jpg.x_size/ds/2; /* center the view */
1035 cy = jpg.y_size/ds/2;
1037 do /* loop the image prepare and decoding when zoomed */
1039 p_disp = get_image(&jpg, ds); /* decode or fetch from cache */
1040 if (p_disp == NULL)
1041 return change_filename(direction);
1043 set_view(p_disp, cx, cy);
1045 if(!running_slideshow)
1047 rb->snprintf(print, sizeof(print), "showing %dx%d",
1048 p_disp->width, p_disp->height);
1049 rb->lcd_puts(0, 3, print);
1050 rb->lcd_update();
1053 MYLCD(clear_display)();
1054 draw_image_rect(p_disp, 0, 0,
1055 p_disp->width-p_disp->x, p_disp->height-p_disp->y);
1056 MYLCD_UPDATE();
1058 #ifdef USEGSLIB
1059 grey_show(true); /* switch on greyscale overlay */
1060 #endif
1062 /* drawing is now finished, play around with scrolling
1063 * until you press OFF or connect USB
1065 while (1)
1067 status = scroll_bmp(p_disp);
1068 if (status == ZOOM_IN)
1070 if (ds > ds_min)
1072 ds /= 2; /* reduce downscaling to zoom in */
1073 get_view(p_disp, &cx, &cy);
1074 cx *= 2; /* prepare the position in the new image */
1075 cy *= 2;
1077 else
1078 continue;
1081 if (status == ZOOM_OUT)
1083 if (ds < ds_max)
1085 ds *= 2; /* increase downscaling to zoom out */
1086 get_view(p_disp, &cx, &cy);
1087 cx /= 2; /* prepare the position in the new image */
1088 cy /= 2;
1090 else
1091 continue;
1093 break;
1096 #ifdef USEGSLIB
1097 grey_show(false); /* switch off overlay */
1098 #endif
1099 rb->lcd_clear_display();
1101 while (status != PLUGIN_OK && status != PLUGIN_USB_CONNECTED
1102 && status != PLUGIN_OTHER);
1103 #ifdef USEGSLIB
1104 rb->lcd_update();
1105 #endif
1106 return status;
1109 /******************** Plugin entry point *********************/
1111 enum plugin_status plugin_start(const void* parameter)
1113 int condition;
1114 #ifdef USEGSLIB
1115 long greysize; /* helper */
1116 #endif
1117 #if LCD_DEPTH > 1
1118 old_backdrop = rb->lcd_get_backdrop();
1119 #endif
1121 if(!parameter) return PLUGIN_ERROR;
1123 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
1124 buf = rb->plugin_get_buffer((size_t *)&buf_size);
1125 #else
1126 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
1127 #endif
1129 rb->strcpy(np_file, parameter);
1130 get_pic_list();
1132 if(!entries) return PLUGIN_ERROR;
1134 #if (PLUGIN_BUFFER_SIZE >= MIN_MEM) && !defined(SIMULATOR)
1135 if(rb->audio_status())
1136 plug_buf = true;
1137 else
1138 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
1139 #endif
1141 #ifdef USEGSLIB
1142 if (!grey_init(buf, buf_size, GREY_ON_COP,
1143 LCD_WIDTH, LCD_HEIGHT, &greysize))
1145 rb->splash(HZ, "grey buf error");
1146 return PLUGIN_ERROR;
1148 buf += greysize;
1149 buf_size -= greysize;
1150 #endif
1152 /* should be ok to just load settings since the plugin itself has
1153 just been loaded from disk and the drive should be spinning */
1154 configfile_load(JPEG_CONFIGFILE, jpeg_config,
1155 ARRAYLEN(jpeg_config), JPEG_SETTINGS_MINVERSION);
1156 old_settings = jpeg_settings;
1158 buf_images = buf; buf_images_size = buf_size;
1160 /* Turn off backlight timeout */
1161 backlight_force_on(); /* backlight control in lib/helper.c */
1165 condition = load_and_show(np_file);
1166 }while (condition != PLUGIN_OK && condition != PLUGIN_USB_CONNECTED
1167 && condition != PLUGIN_ERROR);
1169 if (rb->memcmp(&jpeg_settings, &old_settings, sizeof (jpeg_settings)))
1171 /* Just in case drive has to spin, keep it from looking locked */
1172 rb->splash(0, "Saving Settings");
1173 configfile_save(JPEG_CONFIGFILE, jpeg_config,
1174 ARRAYLEN(jpeg_config), JPEG_SETTINGS_VERSION);
1177 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
1178 /* set back ata spindown time in case we changed it */
1179 rb->storage_spindown(rb->global_settings->disk_spindown);
1180 #endif
1182 /* Turn on backlight timeout (revert to settings) */
1183 backlight_use_settings(); /* backlight control in lib/helper.c */
1185 #ifdef USEGSLIB
1186 grey_release(); /* deinitialize */
1187 #endif
1189 return condition;