Introduce __attribute__((unused)) (#defined to UNUSED_ATTR) to mark possibly unused...
[kugel-rb.git] / apps / plugins / jpeg / jpeg.c
blob75abdc3d96bb85b91f9b91b295eefa0e1969f0dc
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 static void cleanup(UNUSED_ATTR void *parameter)
291 #ifdef USEGSLIB
292 grey_show(false);
293 #endif
296 #define VSCROLL (LCD_HEIGHT/8)
297 #define HSCROLL (LCD_WIDTH/10)
299 #define ZOOM_IN 100 /* return codes for below function */
300 #define ZOOM_OUT 101
302 #ifdef HAVE_LCD_COLOR
303 bool set_option_grayscale(void)
305 bool gray = jpeg_settings.colour_mode == COLOURMODE_GRAY;
306 rb->set_bool("Grayscale", &gray);
307 jpeg_settings.colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR;
308 return false;
311 bool set_option_dithering(void)
313 static const struct opt_items dithering[DITHER_NUM_MODES] = {
314 [DITHER_NONE] = { "Off", -1 },
315 [DITHER_ORDERED] = { "Ordered", -1 },
316 [DITHER_DIFFUSION] = { "Diffusion", -1 },
319 rb->set_option("Dithering", &jpeg_settings.dither_mode, INT,
320 dithering, DITHER_NUM_MODES, NULL);
321 return false;
324 MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale",
325 set_option_grayscale, NULL, NULL, Icon_NOICON);
326 MENUITEM_FUNCTION(dithering_item, 0, "Dithering",
327 set_option_dithering, NULL, NULL, Icon_NOICON);
328 MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON,
329 &grayscale_item, &dithering_item);
331 static void display_options(void)
333 rb->do_menu(&display_menu, NULL, NULL, false);
335 #endif /* HAVE_LCD_COLOR */
337 int show_menu(void) /* return 1 to quit */
339 #if LCD_DEPTH > 1
340 rb->lcd_set_backdrop(old_backdrop);
341 #ifdef HAVE_LCD_COLOR
342 rb->lcd_set_foreground(rb->global_settings->fg_color);
343 rb->lcd_set_background(rb->global_settings->bg_color);
344 #else
345 rb->lcd_set_foreground(LCD_BLACK);
346 rb->lcd_set_background(LCD_WHITE);
347 #endif
348 #endif
349 int result;
351 enum menu_id
353 MIID_RETURN = 0,
354 MIID_TOGGLE_SS_MODE,
355 MIID_CHANGE_SS_MODE,
356 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
357 MIID_SHOW_PLAYBACK_MENU,
358 #endif
359 #ifdef HAVE_LCD_COLOR
360 MIID_DISPLAY_OPTIONS,
361 #endif
362 MIID_QUIT,
365 MENUITEM_STRINGLIST(menu, "Jpeg Menu", NULL,
366 "Return", "Toggle Slideshow Mode",
367 "Change Slideshow Time",
368 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
369 "Show Playback Menu",
370 #endif
371 #ifdef HAVE_LCD_COLOR
372 "Display Options",
373 #endif
374 "Quit");
376 static const struct opt_items slideshow[2] = {
377 { "Disable", -1 },
378 { "Enable", -1 },
381 result=rb->do_menu(&menu, NULL, NULL, false);
383 switch (result)
385 case MIID_RETURN:
386 break;
387 case MIID_TOGGLE_SS_MODE:
388 rb->set_option("Toggle Slideshow", &slideshow_enabled, INT,
389 slideshow , 2, NULL);
390 break;
391 case MIID_CHANGE_SS_MODE:
392 rb->set_int("Slideshow Time", "s", UNIT_SEC,
393 &jpeg_settings.ss_timeout, NULL, 1,
394 SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL);
395 break;
397 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
398 case MIID_SHOW_PLAYBACK_MENU:
399 if (plug_buf)
401 playback_control(NULL);
403 else
405 rb->splash(HZ, "Cannot restart playback");
407 break;
408 #endif
409 #ifdef HAVE_LCD_COLOR
410 case MIID_DISPLAY_OPTIONS:
411 display_options();
412 break;
413 #endif
414 case MIID_QUIT:
415 return 1;
416 break;
419 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
420 /* change ata spindown time based on slideshow time setting */
421 immediate_ata_off = false;
422 rb->storage_spindown(rb->global_settings->disk_spindown);
424 if (slideshow_enabled)
426 if(jpeg_settings.ss_timeout < 10)
428 /* slideshow times < 10s keep disk spinning */
429 rb->storage_spindown(0);
431 else if (!rb->mp3_is_playing())
433 /* slideshow times > 10s and not playing: ata_off after load */
434 immediate_ata_off = true;
437 #endif
438 #if LCD_DEPTH > 1
439 rb->lcd_set_backdrop(NULL);
440 rb->lcd_set_foreground(LCD_WHITE);
441 rb->lcd_set_background(LCD_BLACK);
442 #endif
443 rb->lcd_clear_display();
444 return 0;
447 /* Pan the viewing window right - move image to the left and fill in
448 the right-hand side */
449 static void pan_view_right(struct t_disp* pdisp)
451 int move;
453 move = MIN(HSCROLL, pdisp->width - pdisp->x - LCD_WIDTH);
454 if (move > 0)
456 MYXLCD(scroll_left)(move); /* scroll left */
457 pdisp->x += move;
458 #ifdef HAVE_LCD_COLOR
459 yuv_bitmap_part(
460 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
461 pdisp->x + LCD_WIDTH - move, pdisp->y, pdisp->stride,
462 LCD_WIDTH - move, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
463 move, MIN(LCD_HEIGHT, pdisp->height), /* w, h */
464 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
465 #else
466 MYXLCD(gray_bitmap_part)(
467 pdisp->bitmap[0], pdisp->x + LCD_WIDTH - move,
468 pdisp->y, pdisp->stride,
469 LCD_WIDTH - move, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
470 move, MIN(LCD_HEIGHT, pdisp->height)); /* w, h */
471 #endif
472 MYLCD_UPDATE();
476 /* Pan the viewing window left - move image to the right and fill in
477 the left-hand side */
478 static void pan_view_left(struct t_disp* pdisp)
480 int move;
482 move = MIN(HSCROLL, pdisp->x);
483 if (move > 0)
485 MYXLCD(scroll_right)(move); /* scroll right */
486 pdisp->x -= move;
487 #ifdef HAVE_LCD_COLOR
488 yuv_bitmap_part(
489 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
490 pdisp->x, pdisp->y, pdisp->stride,
491 0, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
492 move, MIN(LCD_HEIGHT, pdisp->height), /* w, h */
493 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
494 #else
495 MYXLCD(gray_bitmap_part)(
496 pdisp->bitmap[0], pdisp->x, pdisp->y, pdisp->stride,
497 0, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */
498 move, MIN(LCD_HEIGHT, pdisp->height)); /* w, h */
499 #endif
500 MYLCD_UPDATE();
505 /* Pan the viewing window up - move image down and fill in
506 the top */
507 static void pan_view_up(struct t_disp* pdisp)
509 int move;
511 move = MIN(VSCROLL, pdisp->y);
512 if (move > 0)
514 MYXLCD(scroll_down)(move); /* scroll down */
515 pdisp->y -= move;
516 #ifdef HAVE_LCD_COLOR
517 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
519 /* Draw over the band at the top of the last update
520 caused by lack of error history on line zero. */
521 move = MIN(move + 1, pdisp->y + pdisp->height);
524 yuv_bitmap_part(
525 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
526 pdisp->x, pdisp->y, pdisp->stride,
527 MAX(0, (LCD_WIDTH-pdisp->width)/2), 0, /* x, y */
528 MIN(LCD_WIDTH, pdisp->width), move, /* w, h */
529 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
530 #else
531 MYXLCD(gray_bitmap_part)(
532 pdisp->bitmap[0], pdisp->x, pdisp->y, pdisp->stride,
533 MAX(0, (LCD_WIDTH-pdisp->width)/2), 0, /* x, y */
534 MIN(LCD_WIDTH, pdisp->width), move); /* w, h */
535 #endif
536 MYLCD_UPDATE();
540 /* Pan the viewing window down - move image up and fill in
541 the bottom */
542 static void pan_view_down(struct t_disp* pdisp)
544 int move;
546 move = MIN(VSCROLL, pdisp->height - pdisp->y - LCD_HEIGHT);
547 if (move > 0)
549 MYXLCD(scroll_up)(move); /* scroll up */
550 pdisp->y += move;
551 #ifdef HAVE_LCD_COLOR
552 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
554 /* Save the line that was on the last line of the display
555 and draw one extra line above then recover the line with
556 image data that had an error history when it was drawn.
558 move++, pdisp->y--;
559 rb->memcpy(rgb_linebuf,
560 rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
561 LCD_WIDTH*sizeof (fb_data));
564 yuv_bitmap_part(
565 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x,
566 pdisp->y + LCD_HEIGHT - move, pdisp->stride,
567 MAX(0, (LCD_WIDTH-pdisp->width)/2), LCD_HEIGHT - move, /* x, y */
568 MIN(LCD_WIDTH, pdisp->width), move, /* w, h */
569 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
571 if (jpeg_settings.dither_mode == DITHER_DIFFUSION)
573 /* Cover the first row drawn with previous image data. */
574 rb->memcpy(rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH,
575 rgb_linebuf,
576 LCD_WIDTH*sizeof (fb_data));
577 pdisp->y++;
579 #else
580 MYXLCD(gray_bitmap_part)(
581 pdisp->bitmap[0], pdisp->x,
582 pdisp->y + LCD_HEIGHT - move, pdisp->stride,
583 MAX(0, (LCD_WIDTH-pdisp->width)/2), LCD_HEIGHT - move, /* x, y */
584 MIN(LCD_WIDTH, pdisp->width), move); /* w, h */
585 #endif
586 MYLCD_UPDATE();
590 /* interactively scroll around the image */
591 int scroll_bmp(struct t_disp* pdisp)
593 int button;
594 int lastbutton = 0;
596 while (true)
598 if (slideshow_enabled)
599 button = rb->button_get_w_tmo(jpeg_settings.ss_timeout * HZ);
600 else button = rb->button_get(true);
602 running_slideshow = false;
604 switch(button)
606 case JPEG_LEFT:
607 if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE)
608 return change_filename(DIR_PREV);
609 case JPEG_LEFT | BUTTON_REPEAT:
610 pan_view_left(pdisp);
611 break;
613 case JPEG_RIGHT:
614 if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE)
615 return change_filename(DIR_NEXT);
616 case JPEG_RIGHT | BUTTON_REPEAT:
617 pan_view_right(pdisp);
618 break;
620 case JPEG_UP:
621 case JPEG_UP | BUTTON_REPEAT:
622 pan_view_up(pdisp);
623 break;
625 case JPEG_DOWN:
626 case JPEG_DOWN | BUTTON_REPEAT:
627 pan_view_down(pdisp);
628 break;
630 case BUTTON_NONE:
631 if (!slideshow_enabled)
632 break;
633 running_slideshow = true;
634 if (entries > 0)
635 return change_filename(DIR_NEXT);
636 break;
638 #ifdef JPEG_SLIDE_SHOW
639 case JPEG_SLIDE_SHOW:
640 slideshow_enabled = !slideshow_enabled;
641 running_slideshow = slideshow_enabled;
642 break;
643 #endif
645 #ifdef JPEG_NEXT_REPEAT
646 case JPEG_NEXT_REPEAT:
647 #endif
648 case JPEG_NEXT:
649 if (entries > 0)
650 return change_filename(DIR_NEXT);
651 break;
653 #ifdef JPEG_PREVIOUS_REPEAT
654 case JPEG_PREVIOUS_REPEAT:
655 #endif
656 case JPEG_PREVIOUS:
657 if (entries > 0)
658 return change_filename(DIR_PREV);
659 break;
661 case JPEG_ZOOM_IN:
662 #ifdef JPEG_ZOOM_PRE
663 if (lastbutton != JPEG_ZOOM_PRE)
664 break;
665 #endif
666 return ZOOM_IN;
667 break;
669 case JPEG_ZOOM_OUT:
670 #ifdef JPEG_ZOOM_PRE
671 if (lastbutton != JPEG_ZOOM_PRE)
672 break;
673 #endif
674 return ZOOM_OUT;
675 break;
676 #ifdef JPEG_RC_MENU
677 case JPEG_RC_MENU:
678 #endif
679 case JPEG_MENU:
680 #ifdef USEGSLIB
681 grey_show(false); /* switch off greyscale overlay */
682 #endif
683 if (show_menu() == 1)
684 return PLUGIN_OK;
686 #ifdef USEGSLIB
687 grey_show(true); /* switch on greyscale overlay */
688 #else
689 yuv_bitmap_part(
690 pdisp->bitmap, pdisp->csub_x, pdisp->csub_y,
691 pdisp->x, pdisp->y, pdisp->stride,
692 MAX(0, (LCD_WIDTH - pdisp->width) / 2),
693 MAX(0, (LCD_HEIGHT - pdisp->height) / 2),
694 MIN(LCD_WIDTH, pdisp->width),
695 MIN(LCD_HEIGHT, pdisp->height),
696 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
697 MYLCD_UPDATE();
698 #endif
699 break;
700 default:
701 if (rb->default_event_handler_ex(button, cleanup, NULL)
702 == SYS_USB_CONNECTED)
703 return PLUGIN_USB_CONNECTED;
704 break;
706 } /* switch */
708 if (button != BUTTON_NONE)
709 lastbutton = button;
710 } /* while (true) */
713 /********************* main function *************************/
715 /* callback updating a progress meter while JPEG decoding */
716 void cb_progess(int current, int total)
718 rb->yield(); /* be nice to the other threads */
719 if(!running_slideshow)
721 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-8, LCD_WIDTH, 8, total, 0,
722 current, HORIZONTAL);
723 rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8);
725 #ifndef USEGSLIB
726 else
728 /* in slideshow mode, keep gui interference to a minimum */
729 rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-4, LCD_WIDTH, 4, total, 0,
730 current, HORIZONTAL);
731 rb->lcd_update_rect(0, LCD_HEIGHT-4, LCD_WIDTH, 4);
733 #endif
736 int jpegmem(struct jpeg *p_jpg, int ds)
738 int size;
740 size = (p_jpg->x_phys/ds/p_jpg->subsample_x[0])
741 * (p_jpg->y_phys/ds/p_jpg->subsample_y[0]);
742 #ifdef HAVE_LCD_COLOR
743 if (p_jpg->blocks > 1) /* colour, add requirements for chroma */
745 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[1])
746 * (p_jpg->y_phys/ds/p_jpg->subsample_y[1]);
747 size += (p_jpg->x_phys/ds/p_jpg->subsample_x[2])
748 * (p_jpg->y_phys/ds/p_jpg->subsample_y[2]);
750 #endif
751 return size;
754 /* how far can we zoom in without running out of memory */
755 int min_downscale(struct jpeg *p_jpg, int bufsize)
757 int downscale = 8;
759 if (jpegmem(p_jpg, 8) > bufsize)
760 return 0; /* error, too large, even 1:8 doesn't fit */
762 while (downscale > 1 && jpegmem(p_jpg, downscale/2) <= bufsize)
763 downscale /= 2;
765 return downscale;
769 /* how far can we zoom out, to fit image into the LCD */
770 int max_downscale(struct jpeg *p_jpg)
772 int downscale = 1;
774 while (downscale < 8 && (p_jpg->x_size > LCD_WIDTH*downscale
775 || p_jpg->y_size > LCD_HEIGHT*downscale))
777 downscale *= 2;
780 return downscale;
784 /* return decoded or cached image */
785 struct t_disp* get_image(struct jpeg* p_jpg, int ds)
787 int w, h; /* used to center output */
788 int size; /* decompressed image size */
789 long time; /* measured ticks */
790 int status;
792 struct t_disp* p_disp = &disp[ds]; /* short cut */
794 if (p_disp->bitmap[0] != NULL)
796 return p_disp; /* we still have it */
799 /* assign image buffer */
801 /* physical size needed for decoding */
802 size = jpegmem(p_jpg, ds);
803 if (buf_size <= size)
804 { /* have to discard the current */
805 int i;
806 for (i=1; i<=8; i++)
807 disp[i].bitmap[0] = NULL; /* invalidate all bitmaps */
808 buf = buf_root; /* start again from the beginning of the buffer */
809 buf_size = root_size;
812 #ifdef HAVE_LCD_COLOR
813 if (p_jpg->blocks > 1) /* colour jpeg */
815 int i;
817 for (i = 1; i < 3; i++)
819 size = (p_jpg->x_phys / ds / p_jpg->subsample_x[i])
820 * (p_jpg->y_phys / ds / p_jpg->subsample_y[i]);
821 p_disp->bitmap[i] = buf;
822 buf += size;
823 buf_size -= size;
825 p_disp->csub_x = p_jpg->subsample_x[1];
826 p_disp->csub_y = p_jpg->subsample_y[1];
828 else
830 p_disp->csub_x = p_disp->csub_y = 0;
831 p_disp->bitmap[1] = p_disp->bitmap[2] = buf;
833 #endif
834 /* size may be less when decoded (if height is not block aligned) */
835 size = (p_jpg->x_phys/ds) * (p_jpg->y_size / ds);
836 p_disp->bitmap[0] = buf;
837 buf += size;
838 buf_size -= size;
840 if(!running_slideshow)
842 rb->snprintf(print, sizeof(print), "decoding %d*%d",
843 p_jpg->x_size/ds, p_jpg->y_size/ds);
844 rb->lcd_puts(0, 3, print);
845 rb->lcd_update();
848 /* update image properties */
849 p_disp->width = p_jpg->x_size / ds;
850 p_disp->stride = p_jpg->x_phys / ds; /* use physical size for stride */
851 p_disp->height = p_jpg->y_size / ds;
853 /* the actual decoding */
854 time = *rb->current_tick;
855 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
856 rb->cpu_boost(true);
857 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess);
858 rb->cpu_boost(false);
859 #else
860 status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess);
861 #endif
862 if (status)
864 rb->splashf(HZ, "decode error %d", status);
865 file_pt[curfile] = '\0';
866 return NULL;
868 time = *rb->current_tick - time;
870 if(!running_slideshow)
872 rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ);
873 rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */
874 rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print);
875 rb->lcd_update();
878 return p_disp;
882 /* set the view to the given center point, limit if necessary */
883 void set_view (struct t_disp* p_disp, int cx, int cy)
885 int x, y;
887 /* plain center to available width/height */
888 x = cx - MIN(LCD_WIDTH, p_disp->width) / 2;
889 y = cy - MIN(LCD_HEIGHT, p_disp->height) / 2;
891 /* limit against upper image size */
892 x = MIN(p_disp->width - LCD_WIDTH, x);
893 y = MIN(p_disp->height - LCD_HEIGHT, y);
895 /* limit against negative side */
896 x = MAX(0, x);
897 y = MAX(0, y);
899 p_disp->x = x; /* set the values */
900 p_disp->y = y;
904 /* calculate the view center based on the bitmap position */
905 void get_view(struct t_disp* p_disp, int* p_cx, int* p_cy)
907 *p_cx = p_disp->x + MIN(LCD_WIDTH, p_disp->width) / 2;
908 *p_cy = p_disp->y + MIN(LCD_HEIGHT, p_disp->height) / 2;
912 /* load, decode, display the image */
913 int load_and_show(char* filename)
915 int fd;
916 int filesize;
917 unsigned char* buf_jpeg; /* compressed JPEG image */
918 int status;
919 struct t_disp* p_disp; /* currenly displayed image */
920 int cx, cy; /* view center */
922 fd = rb->open(filename, O_RDONLY);
923 if (fd < 0)
925 rb->snprintf(print,sizeof(print),"err opening %s:%d",filename,fd);
926 rb->splash(HZ, print);
927 return PLUGIN_ERROR;
929 filesize = rb->filesize(fd);
930 rb->memset(&disp, 0, sizeof(disp));
932 buf = buf_images + filesize;
933 buf_size = buf_images_size - filesize;
934 /* allocate JPEG buffer */
935 buf_jpeg = buf_images;
937 buf_root = buf; /* we can start the decompressed images behind it */
938 root_size = buf_size;
940 if (buf_size <= 0)
942 #if PLUGIN_BUFFER_SIZE >= MIN_MEM
943 if(plug_buf)
945 rb->close(fd);
946 rb->lcd_setfont(FONT_SYSFIXED);
947 rb->lcd_clear_display();
948 rb->snprintf(print,sizeof(print),"%s:",rb->strrchr(filename,'/')+1);
949 rb->lcd_puts(0,0,print);
950 rb->lcd_puts(0,1,"Not enough plugin memory!");
951 rb->lcd_puts(0,2,"Zoom In: Stop playback.");
952 if(entries>1)
953 rb->lcd_puts(0,3,"Left/Right: Skip File.");
954 rb->lcd_puts(0,4,"Off: Quit.");
955 rb->lcd_update();
956 rb->lcd_setfont(FONT_UI);
958 rb->button_clear_queue();
960 while (1)
962 int button = rb->button_get(true);
963 switch(button)
965 case JPEG_ZOOM_IN:
966 plug_buf = false;
967 buf_images = rb->plugin_get_audio_buffer(
968 (size_t *)&buf_images_size);
969 /*try again this file, now using the audio buffer */
970 return PLUGIN_OTHER;
971 #ifdef JPEG_RC_MENU
972 case JPEG_RC_MENU:
973 #endif
974 case JPEG_MENU:
975 return PLUGIN_OK;
977 case JPEG_LEFT:
978 if(entries>1)
980 rb->lcd_clear_display();
981 return change_filename(DIR_PREV);
983 break;
985 case JPEG_RIGHT:
986 if(entries>1)
988 rb->lcd_clear_display();
989 return change_filename(DIR_NEXT);
991 break;
992 default:
993 if(rb->default_event_handler_ex(button, cleanup, NULL)
994 == SYS_USB_CONNECTED)
995 return PLUGIN_USB_CONNECTED;
1000 else
1001 #endif
1003 rb->splash(HZ, "Out of Memory");
1004 rb->close(fd);
1005 return PLUGIN_ERROR;
1009 if(!running_slideshow)
1011 #if LCD_DEPTH > 1
1012 rb->lcd_set_foreground(LCD_WHITE);
1013 rb->lcd_set_background(LCD_BLACK);
1014 rb->lcd_set_backdrop(NULL);
1015 #endif
1017 rb->lcd_clear_display();
1018 rb->snprintf(print, sizeof(print), "%s:", rb->strrchr(filename,'/')+1);
1019 rb->lcd_puts(0, 0, print);
1020 rb->lcd_update();
1022 rb->snprintf(print, sizeof(print), "loading %d bytes", filesize);
1023 rb->lcd_puts(0, 1, print);
1024 rb->lcd_update();
1027 rb->read(fd, buf_jpeg, filesize);
1028 rb->close(fd);
1030 if(!running_slideshow)
1032 rb->snprintf(print, sizeof(print), "decoding markers");
1033 rb->lcd_puts(0, 2, print);
1034 rb->lcd_update();
1036 #ifndef SIMULATOR
1037 else if(immediate_ata_off)
1039 /* running slideshow and time is long enough: power down disk */
1040 rb->storage_sleep();
1042 #endif
1044 rb->memset(&jpg, 0, sizeof(jpg)); /* clear info struct */
1045 /* process markers, unstuffing */
1046 status = process_markers(buf_jpeg, filesize, &jpg);
1048 if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0))
1049 { /* bad format or minimum components not contained */
1050 rb->splashf(HZ, "unsupported %d", status);
1051 file_pt[curfile] = '\0';
1052 return change_filename(direction);
1055 if (!(status & DHT)) /* if no Huffman table present: */
1056 default_huff_tbl(&jpg); /* use default */
1057 build_lut(&jpg); /* derive Huffman and other lookup-tables */
1059 if(!running_slideshow)
1061 rb->snprintf(print, sizeof(print), "image %dx%d", jpg.x_size, jpg.y_size);
1062 rb->lcd_puts(0, 2, print);
1063 rb->lcd_update();
1065 ds_max = max_downscale(&jpg); /* check display constraint */
1066 ds_min = min_downscale(&jpg, buf_size); /* check memory constraint */
1067 if (ds_min == 0)
1069 rb->splash(HZ, "too large");
1070 file_pt[curfile] = '\0';
1071 return change_filename(direction);
1074 ds = ds_max; /* initials setting */
1075 cx = jpg.x_size/ds/2; /* center the view */
1076 cy = jpg.y_size/ds/2;
1078 do /* loop the image prepare and decoding when zoomed */
1080 p_disp = get_image(&jpg, ds); /* decode or fetch from cache */
1081 if (p_disp == NULL)
1082 return change_filename(direction);
1084 set_view(p_disp, cx, cy);
1086 if(!running_slideshow)
1088 rb->snprintf(print, sizeof(print), "showing %dx%d",
1089 p_disp->width, p_disp->height);
1090 rb->lcd_puts(0, 3, print);
1091 rb->lcd_update();
1093 MYLCD(clear_display)();
1094 #ifdef HAVE_LCD_COLOR
1095 yuv_bitmap_part(
1096 p_disp->bitmap, p_disp->csub_x, p_disp->csub_y,
1097 p_disp->x, p_disp->y, p_disp->stride,
1098 MAX(0, (LCD_WIDTH - p_disp->width) / 2),
1099 MAX(0, (LCD_HEIGHT - p_disp->height) / 2),
1100 MIN(LCD_WIDTH, p_disp->width),
1101 MIN(LCD_HEIGHT, p_disp->height),
1102 jpeg_settings.colour_mode, jpeg_settings.dither_mode);
1103 #else
1104 MYXLCD(gray_bitmap_part)(
1105 p_disp->bitmap[0], p_disp->x, p_disp->y, p_disp->stride,
1106 MAX(0, (LCD_WIDTH - p_disp->width) / 2),
1107 MAX(0, (LCD_HEIGHT - p_disp->height) / 2),
1108 MIN(LCD_WIDTH, p_disp->width),
1109 MIN(LCD_HEIGHT, p_disp->height));
1110 #endif
1111 MYLCD_UPDATE();
1113 #ifdef USEGSLIB
1114 grey_show(true); /* switch on greyscale overlay */
1115 #endif
1117 /* drawing is now finished, play around with scrolling
1118 * until you press OFF or connect USB
1120 while (1)
1122 status = scroll_bmp(p_disp);
1123 if (status == ZOOM_IN)
1125 if (ds > ds_min)
1127 ds /= 2; /* reduce downscaling to zoom in */
1128 get_view(p_disp, &cx, &cy);
1129 cx *= 2; /* prepare the position in the new image */
1130 cy *= 2;
1132 else
1133 continue;
1136 if (status == ZOOM_OUT)
1138 if (ds < ds_max)
1140 ds *= 2; /* increase downscaling to zoom out */
1141 get_view(p_disp, &cx, &cy);
1142 cx /= 2; /* prepare the position in the new image */
1143 cy /= 2;
1145 else
1146 continue;
1148 break;
1151 #ifdef USEGSLIB
1152 grey_show(false); /* switch off overlay */
1153 #endif
1154 rb->lcd_clear_display();
1156 while (status != PLUGIN_OK && status != PLUGIN_USB_CONNECTED
1157 && status != PLUGIN_OTHER);
1158 #ifdef USEGSLIB
1159 rb->lcd_update();
1160 #endif
1161 return status;
1164 /******************** Plugin entry point *********************/
1166 enum plugin_status plugin_start(UNUSED_ATTR const void* parameter)
1168 int condition;
1169 #ifdef USEGSLIB
1170 long greysize; /* helper */
1171 #endif
1172 #if LCD_DEPTH > 1
1173 old_backdrop = rb->lcd_get_backdrop();
1174 #endif
1176 if(!parameter) return PLUGIN_ERROR;
1178 rb->strcpy(np_file, parameter);
1179 get_pic_list();
1181 if(!entries) return PLUGIN_ERROR;
1183 #if (PLUGIN_BUFFER_SIZE >= MIN_MEM) && !defined(SIMULATOR)
1184 if(rb->audio_status())
1186 buf = rb->plugin_get_buffer((size_t *)&buf_size) +
1187 (entries * sizeof(char**));
1188 buf_size -= (entries * sizeof(char**));
1189 plug_buf = true;
1191 else
1192 buf = rb->plugin_get_audio_buffer((size_t *)&buf_size);
1193 #else
1194 buf = rb->plugin_get_audio_buffer(&buf_size) +
1195 (entries * sizeof(char**));
1196 buf_size -= (entries * sizeof(char**));
1197 #endif
1199 #ifdef USEGSLIB
1200 if (!grey_init(buf, buf_size, GREY_ON_COP,
1201 LCD_WIDTH, LCD_HEIGHT, &greysize))
1203 rb->splash(HZ, "grey buf error");
1204 return PLUGIN_ERROR;
1206 buf += greysize;
1207 buf_size -= greysize;
1208 #endif
1210 /* should be ok to just load settings since the plugin itself has
1211 just been loaded from disk and the drive should be spinning */
1212 configfile_load(JPEG_CONFIGFILE, jpeg_config,
1213 ARRAYLEN(jpeg_config), JPEG_SETTINGS_MINVERSION);
1214 old_settings = jpeg_settings;
1216 buf_images = buf; buf_images_size = buf_size;
1218 /* Turn off backlight timeout */
1219 backlight_force_on(); /* backlight control in lib/helper.c */
1223 condition = load_and_show(np_file);
1224 }while (condition != PLUGIN_OK && condition != PLUGIN_USB_CONNECTED
1225 && condition != PLUGIN_ERROR);
1227 if (rb->memcmp(&jpeg_settings, &old_settings, sizeof (jpeg_settings)))
1229 /* Just in case drive has to spin, keep it from looking locked */
1230 rb->splash(0, "Saving Settings");
1231 configfile_save(JPEG_CONFIGFILE, jpeg_config,
1232 ARRAYLEN(jpeg_config), JPEG_SETTINGS_VERSION);
1235 #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
1236 /* set back ata spindown time in case we changed it */
1237 rb->storage_spindown(rb->global_settings->disk_spindown);
1238 #endif
1240 /* Turn on backlight timeout (revert to settings) */
1241 backlight_use_settings(); /* backlight control in lib/helper.c */
1243 #ifdef USEGSLIB
1244 grey_release(); /* deinitialize */
1245 #endif
1247 return condition;