jpeg/png: Remove erased file name from list of file when change file.
[kugel-rb.git] / apps / plugins / jpeg / jpeg.c
blob1049e4827e7e2fd9926c4769cf086bb3ef67610e
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 /* Min memory allowing us to use the plugin buffer
59 * and thus not stopping the music
60 * *Very* rough estimation:
61 * Max 10 000 dir entries * 4bytes/entry (char **) = 40000 bytes
62 * + 20k code size = 60 000
63 * + 50k min for jpeg = 120 000
65 #define MIN_MEM 120000
67 /* Headings */
68 #define DIR_PREV 1
69 #define DIR_NEXT -1
70 #define DIR_NONE 0
72 #define PLUGIN_OTHER 10 /* State code for output with return. */
74 /******************************* Globals ***********************************/
76 static int slideshow_enabled = false; /* run slideshow */
77 static int running_slideshow = false; /* loading image because of slideshw */
78 #ifndef SIMULATOR
79 static int immediate_ata_off = false; /* power down disk after loading */
80 #endif
82 #ifdef HAVE_LCD_COLOR
83 fb_data rgb_linebuf[LCD_WIDTH]; /* Line buffer for scrolling when
84 DITHER_DIFFUSION is set */
85 #endif
88 /* Persistent configuration */
89 #define JPEG_CONFIGFILE "jpeg.cfg"
90 #define JPEG_SETTINGS_MINVERSION 1
91 #define JPEG_SETTINGS_VERSION 2
93 /* Slideshow times */
94 #define SS_MIN_TIMEOUT 1
95 #define SS_MAX_TIMEOUT 20
96 #define SS_DEFAULT_TIMEOUT 5
98 struct jpeg_settings
100 #ifdef HAVE_LCD_COLOR
101 int colour_mode;
102 int dither_mode;
103 #endif
104 int ss_timeout;
107 static struct jpeg_settings jpeg_settings =
109 #ifdef HAVE_LCD_COLOR
110 COLOURMODE_COLOUR,
111 DITHER_NONE,
112 #endif
113 SS_DEFAULT_TIMEOUT
115 static struct jpeg_settings old_settings;
117 static struct configdata jpeg_config[] =
119 #ifdef HAVE_LCD_COLOR
120 { TYPE_ENUM, 0, COLOUR_NUM_MODES, { .int_p = &jpeg_settings.colour_mode },
121 "Colour Mode", (char *[]){ "Colour", "Grayscale" } },
122 { TYPE_ENUM, 0, DITHER_NUM_MODES, { .int_p = &jpeg_settings.dither_mode },
123 "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" } },
124 #endif
125 { TYPE_INT, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT,
126 { .int_p = &jpeg_settings.ss_timeout }, "Slideshow Time", NULL },
129 #if LCD_DEPTH > 1
130 static fb_data* old_backdrop;
131 #endif
133 /**************** begin Application ********************/
136 /************************* Types ***************************/
138 struct t_disp
140 #ifdef HAVE_LCD_COLOR
141 unsigned char* bitmap[3]; /* Y, Cr, Cb */
142 int csub_x, csub_y;
143 #else
144 unsigned char* bitmap[1]; /* Y only */
145 #endif
146 int width;
147 int height;
148 int stride;
149 int x, y;
152 /************************* Globals ***************************/
154 /* decompressed image in the possible sizes (1,2,4,8), wasting the other */
155 static struct t_disp disp[9];
157 /* my memory pool (from the mp3 buffer) */
158 static char print[32]; /* use a common snprintf() buffer */
159 /* the remaining free part of the buffer for compressed+uncompressed images */
160 static unsigned char* buf;
161 static ssize_t buf_size;
163 /* the root of the images, hereafter are decompresed ones */
164 static unsigned char* buf_root;
165 static int root_size;
167 /* up to here currently used by image(s) */
168 static unsigned char* buf_images;
169 static ssize_t buf_images_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 bool file_erased = (file_pt[curfile] == NULL);
235 direction = direct;
237 curfile += (direct == DIR_PREV? entries - 1: 1);
238 if (curfile >= entries)
239 curfile -= entries;
241 if (file_erased)
243 /* remove 'erased' file names from list. */
244 int count, i;
245 for (count = i = 0; i < entries; i++)
247 if (curfile == i)
248 curfile = count;
249 if (file_pt[i] != NULL)
250 file_pt[count++] = file_pt[i];
252 entries = count;
255 if (entries == 0)
257 rb->splash(HZ, "No supported files");
258 return PLUGIN_ERROR;
261 if (rb->strlen(tree->currdir) > 1)
263 rb->strcpy(np_file, tree->currdir);
264 rb->strcat(np_file, "/");
266 else
267 rb->strcpy(np_file, tree->currdir);
269 rb->strcat(np_file, file_pt[curfile]);
271 return PLUGIN_OTHER;
274 /* switch off overlay, for handling SYS_ events */
275 void cleanup(void *parameter)
277 (void)parameter;
278 #ifdef USEGSLIB
279 grey_show(false);
280 #endif
283 #define VSCROLL (LCD_HEIGHT/8)
284 #define HSCROLL (LCD_WIDTH/10)
286 #define ZOOM_IN 100 /* return codes for below function */
287 #define ZOOM_OUT 101
289 #ifdef HAVE_LCD_COLOR
290 bool set_option_grayscale(void)
292 bool gray = jpeg_settings.colour_mode == COLOURMODE_GRAY;
293 rb->set_bool("Grayscale", &gray);
294 jpeg_settings.colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR;
295 return false;
298 bool set_option_dithering(void)
300 static const struct opt_items dithering[DITHER_NUM_MODES] = {
301 [DITHER_NONE] = { "Off", -1 },
302 [DITHER_ORDERED] = { "Ordered", -1 },
303 [DITHER_DIFFUSION] = { "Diffusion", -1 },
306 rb->set_option("Dithering", &jpeg_settings.dither_mode, INT,
307 dithering, DITHER_NUM_MODES, NULL);
308 return false;
311 MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale",
312 set_option_grayscale, NULL, NULL, Icon_NOICON);
313 MENUITEM_FUNCTION(dithering_item, 0, "Dithering",
314 set_option_dithering, NULL, NULL, Icon_NOICON);
315 MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON,
316 &grayscale_item, &dithering_item);
318 static void display_options(void)
320 rb->do_menu(&display_menu, NULL, NULL, false);
322 #endif /* HAVE_LCD_COLOR */
324 int show_menu(void) /* return 1 to quit */
326 #if LCD_DEPTH > 1
327 rb->lcd_set_backdrop(old_backdrop);
328 #ifdef HAVE_LCD_COLOR
329 rb->lcd_set_foreground(rb->global_settings->fg_color);
330 rb->lcd_set_background(rb->global_settings->bg_color);
331 #else
332 rb->lcd_set_foreground(LCD_BLACK);
333 rb->lcd_set_background(LCD_WHITE);
334 #endif
335 #endif
336 int result;
338 enum menu_id
340 MIID_RETURN = 0,
341 MIID_TOGGLE_SS_MODE,
342 MIID_CHANGE_SS_MODE,
343 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
344 MIID_SHOW_PLAYBACK_MENU,
345 #endif
346 #ifdef HAVE_LCD_COLOR
347 MIID_DISPLAY_OPTIONS,
348 #endif
349 MIID_QUIT,
352 MENUITEM_STRINGLIST(menu, "Jpeg Menu", NULL,
353 "Return", "Toggle Slideshow Mode",
354 "Change Slideshow Time",
355 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
356 "Show Playback Menu",
357 #endif
358 #ifdef HAVE_LCD_COLOR
359 "Display Options",
360 #endif
361 "Quit");
363 static const struct opt_items slideshow[2] = {
364 { "Disable", -1 },
365 { "Enable", -1 },
368 result=rb->do_menu(&menu, NULL, NULL, false);
370 switch (result)
372 case MIID_RETURN:
373 break;
374 case MIID_TOGGLE_SS_MODE:
375 rb->set_option("Toggle Slideshow", &slideshow_enabled, INT,
376 slideshow , 2, NULL);
377 break;
378 case MIID_CHANGE_SS_MODE:
379 rb->set_int("Slideshow Time", "s", UNIT_SEC,
380 &jpeg_settings.ss_timeout, NULL, 1,
381 SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL);
382 break;
384 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
385 case MIID_SHOW_PLAYBACK_MENU:
386 if (plug_buf)
388 playback_control(NULL);
390 else
392 rb->splash(HZ, "Cannot restart playback");
394 break;
395 #endif
396 #ifdef HAVE_LCD_COLOR
397 case MIID_DISPLAY_OPTIONS:
398 display_options();
399 break;
400 #endif
401 case MIID_QUIT:
402 return 1;
403 break;
406 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
407 /* change ata spindown time based on slideshow time setting */
408 immediate_ata_off = false;
409 rb->storage_spindown(rb->global_settings->disk_spindown);
411 if (slideshow_enabled)
413 if(jpeg_settings.ss_timeout < 10)
415 /* slideshow times < 10s keep disk spinning */
416 rb->storage_spindown(0);
418 else if (!rb->mp3_is_playing())
420 /* slideshow times > 10s and not playing: ata_off after load */
421 immediate_ata_off = true;
424 #endif
425 #if LCD_DEPTH > 1
426 rb->lcd_set_backdrop(NULL);
427 rb->lcd_set_foreground(LCD_WHITE);
428 rb->lcd_set_background(LCD_BLACK);
429 #endif
430 rb->lcd_clear_display();
431 return 0;
434 void draw_image_rect(struct t_disp* pdisp, int x, int y, int width, int height)
436 #ifdef HAVE_LCD_COLOR
437 yuv_bitmap_part(
438 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
439 pdisp->x + x, pdisp->y + y, pdisp->stride,
440 x + MAX(0, (LCD_WIDTH - pdisp->width) / 2),
441 y + MAX(0, (LCD_HEIGHT - pdisp->height) / 2),
442 width, height,
443 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
444 #else
445 MYXLCD(gray_bitmap_part)(
446 pdisp->bitmap[0], pdisp->x + x, pdisp->y + y, pdisp->stride,
447 x + MAX(0, (LCD_WIDTH-pdisp->width)/2),
448 y + MAX(0, (LCD_HEIGHT-pdisp->height)/2),
449 width, height);
450 #endif
453 /* Pan the viewing window right - move image to the left and fill in
454 the right-hand side */
455 static void pan_view_right(struct t_disp* pdisp)
457 int move;
459 move = MIN(HSCROLL, pdisp->width - pdisp->x - LCD_WIDTH);
460 if (move > 0)
462 MYXLCD(scroll_left)(move); /* scroll left */
463 pdisp->x += move;
464 draw_image_rect(pdisp, LCD_WIDTH - move, 0, move, pdisp->height-pdisp->y);
465 MYLCD_UPDATE();
469 /* Pan the viewing window left - move image to the right and fill in
470 the left-hand side */
471 static void pan_view_left(struct t_disp* pdisp)
473 int move;
475 move = MIN(HSCROLL, pdisp->x);
476 if (move > 0)
478 MYXLCD(scroll_right)(move); /* scroll right */
479 pdisp->x -= move;
480 draw_image_rect(pdisp, 0, 0, move, pdisp->height-pdisp->y);
481 MYLCD_UPDATE();
485 /* Pan the viewing window up - move image down and fill in
486 the top */
487 static void pan_view_up(struct t_disp* pdisp)
489 int move;
491 move = MIN(VSCROLL, pdisp->y);
492 if (move > 0)
494 MYXLCD(scroll_down)(move); /* scroll down */
495 pdisp->y -= move;
496 #ifdef HAVE_LCD_COLOR
497 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
499 /* Draw over the band at the top of the last update
500 caused by lack of error history on line zero. */
501 move = MIN(move + 1, pdisp->y + pdisp->height);
503 #endif
504 draw_image_rect(pdisp, 0, 0, pdisp->width-pdisp->x, move);
505 MYLCD_UPDATE();
509 /* Pan the viewing window down - move image up and fill in
510 the bottom */
511 static void pan_view_down(struct t_disp* pdisp)
513 int move;
515 move = MIN(VSCROLL, pdisp->height - pdisp->y - LCD_HEIGHT);
516 if (move > 0)
518 MYXLCD(scroll_up)(move); /* scroll up */
519 pdisp->y += move;
520 #ifdef HAVE_LCD_COLOR
521 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
523 /* Save the line that was on the last line of the display
524 and draw one extra line above then recover the line with
525 image data that had an error history when it was drawn.
527 move++, pdisp->y--;
528 rb->memcpy(rgb_linebuf,
529 rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
530 LCD_WIDTH*sizeof (fb_data));
532 #endif
534 draw_image_rect(pdisp, 0, LCD_HEIGHT - move, pdisp->width-pdisp->x, move);
536 #ifdef HAVE_LCD_COLOR
537 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
539 /* Cover the first row drawn with previous image data. */
540 rb->memcpy(rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
541 rgb_linebuf,
542 LCD_WIDTH*sizeof (fb_data));
543 pdisp->y++;
545 #endif
546 MYLCD_UPDATE();
550 /* interactively scroll around the image */
551 int scroll_bmp(struct t_disp* pdisp)
553 int button;
554 int lastbutton = 0;
556 while (true)
558 if (slideshow_enabled)
559 button = rb->button_get_w_tmo(jpeg_settings.ss_timeout * HZ);
560 else button = rb->button_get(true);
562 running_slideshow = false;
564 switch(button)
566 case JPEG_LEFT:
567 if (entries > 1 && pdisp->width <= LCD_WIDTH
568 && pdisp->height <= LCD_HEIGHT)
569 return change_filename(DIR_PREV);
570 case JPEG_LEFT | BUTTON_REPEAT:
571 pan_view_left(pdisp);
572 break;
574 case JPEG_RIGHT:
575 if (entries > 1 && pdisp->width <= LCD_WIDTH
576 && pdisp->height <= LCD_HEIGHT)
577 return change_filename(DIR_NEXT);
578 case JPEG_RIGHT | BUTTON_REPEAT:
579 pan_view_right(pdisp);
580 break;
582 case JPEG_UP:
583 case JPEG_UP | BUTTON_REPEAT:
584 pan_view_up(pdisp);
585 break;
587 case JPEG_DOWN:
588 case JPEG_DOWN | BUTTON_REPEAT:
589 pan_view_down(pdisp);
590 break;
592 case BUTTON_NONE:
593 if (!slideshow_enabled)
594 break;
595 running_slideshow = true;
596 if (entries > 1)
597 return change_filename(DIR_NEXT);
598 break;
600 #ifdef JPEG_SLIDE_SHOW
601 case JPEG_SLIDE_SHOW:
602 slideshow_enabled = !slideshow_enabled;
603 running_slideshow = slideshow_enabled;
604 break;
605 #endif
607 #ifdef JPEG_NEXT_REPEAT
608 case JPEG_NEXT_REPEAT:
609 #endif
610 case JPEG_NEXT:
611 if (entries > 1)
612 return change_filename(DIR_NEXT);
613 break;
615 #ifdef JPEG_PREVIOUS_REPEAT
616 case JPEG_PREVIOUS_REPEAT:
617 #endif
618 case JPEG_PREVIOUS:
619 if (entries > 1)
620 return change_filename(DIR_PREV);
621 break;
623 case JPEG_ZOOM_IN:
624 #ifdef JPEG_ZOOM_PRE
625 if (lastbutton != JPEG_ZOOM_PRE)
626 break;
627 #endif
628 return ZOOM_IN;
629 break;
631 case JPEG_ZOOM_OUT:
632 #ifdef JPEG_ZOOM_PRE
633 if (lastbutton != JPEG_ZOOM_PRE)
634 break;
635 #endif
636 return ZOOM_OUT;
637 break;
638 #ifdef JPEG_RC_MENU
639 case JPEG_RC_MENU:
640 #endif
641 case JPEG_MENU:
642 #ifdef USEGSLIB
643 grey_show(false); /* switch off greyscale overlay */
644 #endif
645 if (show_menu() == 1)
646 return PLUGIN_OK;
648 #ifdef USEGSLIB
649 grey_show(true); /* switch on greyscale overlay */
650 #else
651 draw_image_rect(pdisp, 0, 0,
652 pdisp->width-pdisp->x, pdisp->height-pdisp->y);
653 MYLCD_UPDATE();
654 #endif
655 break;
656 default:
657 if (rb->default_event_handler_ex(button, cleanup, NULL)
658 == SYS_USB_CONNECTED)
659 return PLUGIN_USB_CONNECTED;
660 break;
662 } /* switch */
664 if (button != BUTTON_NONE)
665 lastbutton = button;
666 } /* while (true) */
669 /********************* main function *************************/
671 /* callback updating a progress meter while JPEG decoding */
672 void cb_progress(int current, int total)
674 rb->yield(); /* be nice to the other threads */
675 if(!running_slideshow)
677 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],
678 0, LCD_HEIGHT-8, LCD_WIDTH, 8,
679 total, 0, current, HORIZONTAL);
680 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
682 #ifndef USEGSLIB
683 else
685 /* in slideshow mode, keep gui interference to a minimum */
686 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],
687 0, LCD_HEIGHT-4, LCD_WIDTH, 4,
688 total, 0, current, HORIZONTAL);
689 rb->lcd_update_rect(0, LCD_HEIGHT-4, LCD_WIDTH, 4);
691 #endif
694 int jpegmem(struct jpeg *p_jpg, int ds)
696 int size;
698 size = (p_jpg->x_phys/ds/p_jpg->subsample_x[0])
699 * (p_jpg->y_phys/ds/p_jpg->subsample_y[0]);
700 #ifdef HAVE_LCD_COLOR
701 if (p_jpg->blocks > 1) /* colour, add requirements for chroma */
703 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[1])
704 * (p_jpg->y_phys/ds/p_jpg->subsample_y[1]);
705 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[2])
706 * (p_jpg->y_phys/ds/p_jpg->subsample_y[2]);
708 #endif
709 return size;
712 /* how far can we zoom in without running out of memory */
713 int min_downscale(struct jpeg *p_jpg, int bufsize)
715 int downscale = 8;
717 if (jpegmem(p_jpg, 8) > bufsize)
718 return 0; /* error, too large, even 1:8 doesn't fit */
720 while (downscale > 1 && jpegmem(p_jpg, downscale/2) <= bufsize)
721 downscale /= 2;
723 return downscale;
726 /* how far can we zoom out, to fit image into the LCD */
727 int max_downscale(struct jpeg *p_jpg)
729 int downscale = 1;
731 while (downscale < 8 && (p_jpg->x_size/downscale > LCD_WIDTH
732 || p_jpg->y_size/downscale > LCD_HEIGHT))
734 downscale *= 2;
737 return downscale;
741 /* return decoded or cached image */
742 struct t_disp* get_image(struct jpeg* p_jpg, int ds)
744 int w, h; /* used to center output */
745 int size; /* decompressed image size */
746 long time; /* measured ticks */
747 int status;
749 struct t_disp* p_disp = &disp[ds]; /* short cut */
751 if (p_disp->bitmap[0] != NULL)
753 return p_disp; /* we still have it */
756 /* assign image buffer */
758 /* physical size needed for decoding */
759 size = jpegmem(p_jpg, ds);
760 if (buf_images_size <= size)
761 { /* have to discard the current */
762 int i;
763 for (i=1; i<=8; i++)
764 disp[i].bitmap[0] = NULL; /* invalidate all bitmaps */
765 buf_images = buf_root; /* start again from the beginning of the buffer */
766 buf_images_size = root_size;
769 #ifdef HAVE_LCD_COLOR
770 if (p_jpg->blocks > 1) /* colour jpeg */
772 int i;
774 for (i = 1; i < 3; i++)
776 size = (p_jpg->x_phys / ds / p_jpg->subsample_x[i])
777 * (p_jpg->y_phys / ds / p_jpg->subsample_y[i]);
778 p_disp->bitmap[i] = buf_images;
779 buf_images += size;
780 buf_images_size -= size;
782 p_disp->csub_x = p_jpg->subsample_x[1];
783 p_disp->csub_y = p_jpg->subsample_y[1];
785 else
787 p_disp->csub_x = p_disp->csub_y = 0;
788 p_disp->bitmap[1] = p_disp->bitmap[2] = buf_images;
790 #endif
791 /* size may be less when decoded (if height is not block aligned) */
792 size = (p_jpg->x_phys/ds) * (p_jpg->y_size / ds);
793 p_disp->bitmap[0] = buf_images;
794 buf_images += size;
795 buf_images_size -= size;
797 if(!running_slideshow)
799 rb->snprintf(print, sizeof(print), "decoding %d*%d",
800 p_jpg->x_size/ds, p_jpg->y_size/ds);
801 rb->lcd_puts(0, 3, print);
802 rb->lcd_update();
805 /* update image properties */
806 p_disp->width = p_jpg->x_size / ds;
807 p_disp->stride = p_jpg->x_phys / ds; /* use physical size for stride */
808 p_disp->height = p_jpg->y_size / ds;
810 /* the actual decoding */
811 time = *rb->current_tick;
812 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
813 rb->cpu_boost(true);
814 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progress);
815 rb->cpu_boost(false);
816 #else
817 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progress);
818 #endif
819 if (status)
821 rb->splashf(HZ, "decode error %d", status);
822 return NULL;
824 time = *rb->current_tick - time;
826 if(!running_slideshow)
828 rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ);
829 rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */
830 rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print);
831 rb->lcd_update();
834 return p_disp;
838 /* set the view to the given center point, limit if necessary */
839 void set_view (struct t_disp* p_disp, int cx, int cy)
841 int x, y;
843 /* plain center to available width/height */
844 x = cx - MIN(LCD_WIDTH, p_disp->width) / 2;
845 y = cy - MIN(LCD_HEIGHT, p_disp->height) / 2;
847 /* limit against upper image size */
848 x = MIN(p_disp->width - LCD_WIDTH, x);
849 y = MIN(p_disp->height - LCD_HEIGHT, y);
851 /* limit against negative side */
852 x = MAX(0, x);
853 y = MAX(0, y);
855 p_disp->x = x; /* set the values */
856 p_disp->y = y;
860 /* calculate the view center based on the bitmap position */
861 void get_view(struct t_disp* p_disp, int* p_cx, int* p_cy)
863 *p_cx = p_disp->x + MIN(LCD_WIDTH, p_disp->width) / 2;
864 *p_cy = p_disp->y + MIN(LCD_HEIGHT, p_disp->height) / 2;
868 /* load, decode, display the image */
869 int load_and_show(char* filename)
871 int fd;
872 int filesize;
873 unsigned char* buf_jpeg; /* compressed JPEG image */
874 int status;
875 struct t_disp* p_disp; /* currenly displayed image */
876 int cx, cy; /* view center */
878 fd = rb->open(filename, O_RDONLY);
879 if (fd < 0)
881 rb->snprintf(print,sizeof(print),"err opening %s:%d",filename,fd);
882 rb->splash(HZ, print);
883 return PLUGIN_ERROR;
885 filesize = rb->filesize(fd);
886 rb->memset(&disp, 0, sizeof(disp));
888 /* allocate JPEG buffer */
889 buf_jpeg = buf;
891 /* we can start the decompressed images behind it */
892 buf_images = buf_root = buf + filesize;
893 buf_images_size = root_size = buf_size - filesize;
895 if (buf_images_size <= 0)
897 rb->close(fd);
898 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
899 if(plug_buf)
901 rb->lcd_setfont(FONT_SYSFIXED);
902 rb->lcd_clear_display();
903 rb->snprintf(print,sizeof(print),"%s:",rb->strrchr(filename,'/')+1);
904 rb->lcd_puts(0,0,print);
905 rb->lcd_puts(0,1,"Not enough plugin memory!");
906 rb->lcd_puts(0,2,"Zoom In: Stop playback.");
907 if(entries>1)
908 rb->lcd_puts(0,3,"Left/Right: Skip File.");
909 rb->lcd_puts(0,4,"Off: Quit.");
910 rb->lcd_update();
911 rb->lcd_setfont(FONT_UI);
913 rb->button_clear_queue();
915 while (1)
917 int button = rb->button_get(true);
918 switch(button)
920 case JPEG_ZOOM_IN:
921 plug_buf = false;
922 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
923 /*try again this file, now using the audio buffer */
924 return PLUGIN_OTHER;
925 #ifdef JPEG_RC_MENU
926 case JPEG_RC_MENU:
927 #endif
928 case JPEG_MENU:
929 return PLUGIN_OK;
931 case JPEG_LEFT:
932 if(entries>1)
934 rb->lcd_clear_display();
935 return change_filename(DIR_PREV);
937 break;
939 case JPEG_RIGHT:
940 if(entries>1)
942 rb->lcd_clear_display();
943 return change_filename(DIR_NEXT);
945 break;
946 default:
947 if(rb->default_event_handler_ex(button, cleanup, NULL)
948 == SYS_USB_CONNECTED)
949 return PLUGIN_USB_CONNECTED;
954 else
955 #endif
957 rb->splash(HZ, "Out of Memory");
958 return PLUGIN_ERROR;
962 if(!running_slideshow)
964 #if LCD_DEPTH > 1
965 rb->lcd_set_foreground(LCD_WHITE);
966 rb->lcd_set_background(LCD_BLACK);
967 rb->lcd_set_backdrop(NULL);
968 #endif
970 rb->lcd_clear_display();
971 rb->snprintf(print, sizeof(print), "%s:", rb->strrchr(filename,'/')+1);
972 rb->lcd_puts(0, 0, print);
973 rb->lcd_update();
975 rb->snprintf(print, sizeof(print), "loading %d bytes", filesize);
976 rb->lcd_puts(0, 1, print);
977 rb->lcd_update();
980 rb->read(fd, buf_jpeg, filesize);
981 rb->close(fd);
983 if(!running_slideshow)
985 rb->snprintf(print, sizeof(print), "decoding markers");
986 rb->lcd_puts(0, 2, print);
987 rb->lcd_update();
989 #ifndef SIMULATOR
990 else if(immediate_ata_off)
992 /* running slideshow and time is long enough: power down disk */
993 rb->storage_sleep();
995 #endif
997 rb->memset(&jpg, 0, sizeof(jpg)); /* clear info struct */
998 /* process markers, unstuffing */
999 status = process_markers(buf_jpeg, filesize, &jpg);
1001 if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0))
1002 { /* bad format or minimum components not contained */
1003 rb->splashf(HZ, "unsupported %d", status);
1004 file_pt[curfile] = NULL;
1005 return change_filename(direction);
1008 if (!(status & DHT)) /* if no Huffman table present: */
1009 default_huff_tbl(&jpg); /* use default */
1010 build_lut(&jpg); /* derive Huffman and other lookup-tables */
1012 if(!running_slideshow)
1014 rb->snprintf(print, sizeof(print), "image %dx%d", jpg.x_size, jpg.y_size);
1015 rb->lcd_puts(0, 2, print);
1016 rb->lcd_update();
1018 ds_max = max_downscale(&jpg); /* check display constraint */
1019 ds_min = min_downscale(&jpg, buf_images_size); /* check memory constraint */
1020 if (ds_min == 0)
1022 rb->splash(HZ, "too large");
1023 file_pt[curfile] = NULL;
1024 return change_filename(direction);
1027 ds = ds_max; /* initialize setting */
1028 cx = jpg.x_size/ds/2; /* center the view */
1029 cy = jpg.y_size/ds/2;
1031 do /* loop the image prepare and decoding when zoomed */
1033 p_disp = get_image(&jpg, ds); /* decode or fetch from cache */
1034 if (p_disp == NULL)
1036 file_pt[curfile] = NULL;
1037 return change_filename(direction);
1040 set_view(p_disp, cx, cy);
1042 if(!running_slideshow)
1044 rb->snprintf(print, sizeof(print), "showing %dx%d",
1045 p_disp->width, p_disp->height);
1046 rb->lcd_puts(0, 3, print);
1047 rb->lcd_update();
1050 MYLCD(clear_display)();
1051 draw_image_rect(p_disp, 0, 0,
1052 p_disp->width-p_disp->x, p_disp->height-p_disp->y);
1053 MYLCD_UPDATE();
1055 #ifdef USEGSLIB
1056 grey_show(true); /* switch on greyscale overlay */
1057 #endif
1059 /* drawing is now finished, play around with scrolling
1060 * until you press OFF or connect USB
1062 while (1)
1064 status = scroll_bmp(p_disp);
1065 if (status == ZOOM_IN)
1067 if (ds > ds_min)
1069 ds /= 2; /* reduce downscaling to zoom in */
1070 get_view(p_disp, &cx, &cy);
1071 cx *= 2; /* prepare the position in the new image */
1072 cy *= 2;
1074 else
1075 continue;
1078 if (status == ZOOM_OUT)
1080 if (ds < ds_max)
1082 ds *= 2; /* increase downscaling to zoom out */
1083 get_view(p_disp, &cx, &cy);
1084 cx /= 2; /* prepare the position in the new image */
1085 cy /= 2;
1087 else
1088 continue;
1090 break;
1093 #ifdef USEGSLIB
1094 grey_show(false); /* switch off overlay */
1095 #endif
1096 rb->lcd_clear_display();
1098 while (status != PLUGIN_OK && status != PLUGIN_USB_CONNECTED
1099 && status != PLUGIN_OTHER);
1100 #ifdef USEGSLIB
1101 rb->lcd_update();
1102 #endif
1103 return status;
1106 /******************** Plugin entry point *********************/
1108 enum plugin_status plugin_start(const void* parameter)
1110 int condition;
1111 #ifdef USEGSLIB
1112 long greysize; /* helper */
1113 #endif
1114 #if LCD_DEPTH > 1
1115 old_backdrop = rb->lcd_get_backdrop();
1116 #endif
1118 if(!parameter) return PLUGIN_ERROR;
1120 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
1121 buf = rb->plugin_get_buffer((size_t *)&buf_size);
1122 #else
1123 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
1124 #endif
1126 rb->strcpy(np_file, parameter);
1127 get_pic_list();
1129 if(!entries) return PLUGIN_ERROR;
1131 #if (PLUGIN_BUFFER_SIZE >= MIN_MEM) && !defined(SIMULATOR)
1132 if(rb->audio_status())
1133 plug_buf = true;
1134 else
1135 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
1136 #endif
1138 #ifdef USEGSLIB
1139 if (!grey_init(buf, buf_size, GREY_ON_COP,
1140 LCD_WIDTH, LCD_HEIGHT, &greysize))
1142 rb->splash(HZ, "grey buf error");
1143 return PLUGIN_ERROR;
1145 buf += greysize;
1146 buf_size -= greysize;
1147 #endif
1149 /* should be ok to just load settings since the plugin itself has
1150 just been loaded from disk and the drive should be spinning */
1151 configfile_load(JPEG_CONFIGFILE, jpeg_config,
1152 ARRAYLEN(jpeg_config), JPEG_SETTINGS_MINVERSION);
1153 old_settings = jpeg_settings;
1155 /* Turn off backlight timeout */
1156 backlight_force_on(); /* backlight control in lib/helper.c */
1160 condition = load_and_show(np_file);
1161 }while (condition != PLUGIN_OK && condition != PLUGIN_USB_CONNECTED
1162 && condition != PLUGIN_ERROR);
1164 if (rb->memcmp(&jpeg_settings, &old_settings, sizeof (jpeg_settings)))
1166 /* Just in case drive has to spin, keep it from looking locked */
1167 rb->splash(0, "Saving Settings");
1168 configfile_save(JPEG_CONFIGFILE, jpeg_config,
1169 ARRAYLEN(jpeg_config), JPEG_SETTINGS_VERSION);
1172 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
1173 /* set back ata spindown time in case we changed it */
1174 rb->storage_spindown(rb->global_settings->disk_spindown);
1175 #endif
1177 /* Turn on backlight timeout (revert to settings) */
1178 backlight_use_settings(); /* backlight control in lib/helper.c */
1180 #ifdef USEGSLIB
1181 grey_release(); /* deinitialize */
1182 #endif
1184 return condition;