/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * JPEG image viewer * (This is a real mess if it has to be coded in one single C file) * * File scrolling addition (C) 2005 Alexander Spyridakis * Copyright (C) 2004 Jörg Hohensohn aka [IDC]Dragon * Heavily borrowed from the IJG implementation (C) Thomas G. Lane * Small & fast downscaling IDCT (C) 2002 by Guido Vollbeding JPEGclub.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" #include #include #include #include #include #include "jpeg.h" #include "jpeg_decoder.h" PLUGIN_HEADER #ifdef HAVE_LCD_COLOR #include "yuv2rgb.h" #endif /* different graphics libraries */ #if LCD_DEPTH < 8 #define USEGSLIB GREY_INFO_STRUCT #define MYLCD(fn) grey_ub_ ## fn #define MYLCD_UPDATE() #define MYXLCD(fn) grey_ub_ ## fn #else #define MYLCD(fn) rb->lcd_ ## fn #define MYLCD_UPDATE() rb->lcd_update(); #define MYXLCD(fn) xlcd_ ## fn #endif #define MAX_X_SIZE LCD_WIDTH*8 /* Min memory allowing us to use the plugin buffer * and thus not stopping the music * *Very* rough estimation: * Max 10 000 dir entries * 4bytes/entry (char **) = 40000 bytes * + 20k code size = 60 000 * + 50k min for jpeg = 120 000 */ #define MIN_MEM 120000 /* Headings */ #define DIR_PREV 1 #define DIR_NEXT -1 #define DIR_NONE 0 #define PLUGIN_OTHER 10 /* State code for output with return. */ /******************************* Globals ***********************************/ static int slideshow_enabled = false; /* run slideshow */ static int running_slideshow = false; /* loading image because of slideshw */ #ifndef SIMULATOR static int immediate_ata_off = false; /* power down disk after loading */ #endif #ifdef HAVE_LCD_COLOR fb_data rgb_linebuf[LCD_WIDTH]; /* Line buffer for scrolling when DITHER_DIFFUSION is set */ #endif /* Persistent configuration */ #define JPEG_CONFIGFILE "jpeg.cfg" #define JPEG_SETTINGS_MINVERSION 1 #define JPEG_SETTINGS_VERSION 2 /* Slideshow times */ #define SS_MIN_TIMEOUT 1 #define SS_MAX_TIMEOUT 20 #define SS_DEFAULT_TIMEOUT 5 struct jpeg_settings { #ifdef HAVE_LCD_COLOR int colour_mode; int dither_mode; #endif int ss_timeout; }; static struct jpeg_settings jpeg_settings = { #ifdef HAVE_LCD_COLOR COLOURMODE_COLOUR, DITHER_NONE, #endif SS_DEFAULT_TIMEOUT }; static struct jpeg_settings old_settings; static struct configdata jpeg_config[] = { #ifdef HAVE_LCD_COLOR { TYPE_ENUM, 0, COLOUR_NUM_MODES, { .int_p = &jpeg_settings.colour_mode }, "Colour Mode", (char *[]){ "Colour", "Grayscale" } }, { TYPE_ENUM, 0, DITHER_NUM_MODES, { .int_p = &jpeg_settings.dither_mode }, "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" } }, #endif { TYPE_INT, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, { .int_p = &jpeg_settings.ss_timeout }, "Slideshow Time", NULL }, }; #if LCD_DEPTH > 1 static fb_data* old_backdrop; #endif /**************** begin Application ********************/ /************************* Types ***************************/ struct t_disp { #ifdef HAVE_LCD_COLOR unsigned char* bitmap[3]; /* Y, Cr, Cb */ int csub_x, csub_y; #else unsigned char* bitmap[1]; /* Y only */ #endif int width; int height; int stride; int x, y; }; /************************* Globals ***************************/ /* decompressed image in the possible sizes (1,2,4,8), wasting the other */ static struct t_disp disp[9]; /* my memory pool (from the mp3 buffer) */ static char print[32]; /* use a common snprintf() buffer */ static unsigned char* buf; /* up to here currently used by image(s) */ /* the remaining free part of the buffer for compressed+uncompressed images */ static unsigned char* buf_images; static ssize_t buf_size, buf_images_size; /* the root of the images, hereafter are decompresed ones */ static unsigned char* buf_root; static int root_size; static int ds, ds_min, ds_max; /* downscaling and limits */ static struct jpeg jpg; /* too large for stack */ static struct tree_context *tree; /* the current full file name */ static char np_file[MAX_PATH]; static int curfile = 0, direction = DIR_NONE, entries = 0; /* list of the jpeg files */ static char **file_pt; /* are we using the plugin buffer or the audio buffer? */ bool plug_buf = false; /************************* Implementation ***************************/ /* support function for qsort() */ static int compare(const void* p1, const void* p2) { return rb->strcasecmp(*((char **)p1), *((char **)p2)); } bool jpg_ext(const char ext[]) { if(!ext) return false; if(!rb->strcasecmp(ext,".jpg") || !rb->strcasecmp(ext,".jpe") || !rb->strcasecmp(ext,".jpeg")) return true; else return false; } /*Read directory contents for scrolling. */ void get_pic_list(void) { int i; long int str_len = 0; char *pname; tree = rb->tree_get_context(); #if PLUGIN_BUFFER_SIZE >= MIN_MEM file_pt = rb->plugin_get_buffer((size_t *)&buf_size); #else file_pt = rb->plugin_get_audio_buffer((size_t *)&buf_size); #endif for(i = 0; i < tree->filesindir; i++) { if(jpg_ext(rb->strrchr(&tree->name_buffer[str_len],'.'))) file_pt[entries++] = &tree->name_buffer[str_len]; str_len += rb->strlen(&tree->name_buffer[str_len]) + 1; } rb->qsort(file_pt, entries, sizeof(char**), compare); /* Remove path and leave only the name.*/ pname = rb->strrchr(np_file,'/'); pname++; /* Find Selected File. */ for(i = 0; i < entries; i++) if(!rb->strcmp(file_pt[i], pname)) curfile = i; } int change_filename(int direct) { int count = 0; direction = direct; if(direct == DIR_PREV) { do { count++; if(curfile == 0) curfile = entries - 1; else curfile--; }while(file_pt[curfile] == '\0' && count < entries); /* we "erase" the file name if we encounter * a non-supported file, so skip it now */ } else /* DIR_NEXT/DIR_NONE */ { do { count++; if(curfile == entries - 1) curfile = 0; else curfile++; }while(file_pt[curfile] == '\0' && count < entries); } if(count == entries && file_pt[curfile] == '\0') { rb->splash(HZ, "No supported files"); return PLUGIN_ERROR; } if(rb->strlen(tree->currdir) > 1) { rb->strcpy(np_file, tree->currdir); rb->strcat(np_file, "/"); } else rb->strcpy(np_file, tree->currdir); rb->strcat(np_file, file_pt[curfile]); return PLUGIN_OTHER; } /* switch off overlay, for handling SYS_ events */ void cleanup(void *parameter) { (void)parameter; #ifdef USEGSLIB grey_show(false); #endif } #define VSCROLL (LCD_HEIGHT/8) #define HSCROLL (LCD_WIDTH/10) #define ZOOM_IN 100 /* return codes for below function */ #define ZOOM_OUT 101 #ifdef HAVE_LCD_COLOR bool set_option_grayscale(void) { bool gray = jpeg_settings.colour_mode == COLOURMODE_GRAY; rb->set_bool("Grayscale", &gray); jpeg_settings.colour_mode = gray ? COLOURMODE_GRAY : COLOURMODE_COLOUR; return false; } bool set_option_dithering(void) { static const struct opt_items dithering[DITHER_NUM_MODES] = { [DITHER_NONE] = { "Off", -1 }, [DITHER_ORDERED] = { "Ordered", -1 }, [DITHER_DIFFUSION] = { "Diffusion", -1 }, }; rb->set_option("Dithering", &jpeg_settings.dither_mode, INT, dithering, DITHER_NUM_MODES, NULL); return false; } MENUITEM_FUNCTION(grayscale_item, 0, "Greyscale", set_option_grayscale, NULL, NULL, Icon_NOICON); MENUITEM_FUNCTION(dithering_item, 0, "Dithering", set_option_dithering, NULL, NULL, Icon_NOICON); MAKE_MENU(display_menu, "Display Options", NULL, Icon_NOICON, &grayscale_item, &dithering_item); static void display_options(void) { rb->do_menu(&display_menu, NULL, NULL, false); } #endif /* HAVE_LCD_COLOR */ int show_menu(void) /* return 1 to quit */ { #if LCD_DEPTH > 1 rb->lcd_set_backdrop(old_backdrop); #ifdef HAVE_LCD_COLOR rb->lcd_set_foreground(rb->global_settings->fg_color); rb->lcd_set_background(rb->global_settings->bg_color); #else rb->lcd_set_foreground(LCD_BLACK); rb->lcd_set_background(LCD_WHITE); #endif #endif int result; enum menu_id { MIID_RETURN = 0, MIID_TOGGLE_SS_MODE, MIID_CHANGE_SS_MODE, #if PLUGIN_BUFFER_SIZE >= MIN_MEM MIID_SHOW_PLAYBACK_MENU, #endif #ifdef HAVE_LCD_COLOR MIID_DISPLAY_OPTIONS, #endif MIID_QUIT, }; MENUITEM_STRINGLIST(menu, "Jpeg Menu", NULL, "Return", "Toggle Slideshow Mode", "Change Slideshow Time", #if PLUGIN_BUFFER_SIZE >= MIN_MEM "Show Playback Menu", #endif #ifdef HAVE_LCD_COLOR "Display Options", #endif "Quit"); static const struct opt_items slideshow[2] = { { "Disable", -1 }, { "Enable", -1 }, }; result=rb->do_menu(&menu, NULL, NULL, false); switch (result) { case MIID_RETURN: break; case MIID_TOGGLE_SS_MODE: rb->set_option("Toggle Slideshow", &slideshow_enabled, INT, slideshow , 2, NULL); break; case MIID_CHANGE_SS_MODE: rb->set_int("Slideshow Time", "s", UNIT_SEC, &jpeg_settings.ss_timeout, NULL, 1, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, NULL); break; #if PLUGIN_BUFFER_SIZE >= MIN_MEM case MIID_SHOW_PLAYBACK_MENU: if (plug_buf) { playback_control(NULL); } else { rb->splash(HZ, "Cannot restart playback"); } break; #endif #ifdef HAVE_LCD_COLOR case MIID_DISPLAY_OPTIONS: display_options(); break; #endif case MIID_QUIT: return 1; break; } #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE) /* change ata spindown time based on slideshow time setting */ immediate_ata_off = false; rb->storage_spindown(rb->global_settings->disk_spindown); if (slideshow_enabled) { if(jpeg_settings.ss_timeout < 10) { /* slideshow times < 10s keep disk spinning */ rb->storage_spindown(0); } else if (!rb->mp3_is_playing()) { /* slideshow times > 10s and not playing: ata_off after load */ immediate_ata_off = true; } } #endif #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); #endif rb->lcd_clear_display(); return 0; } /* Pan the viewing window right - move image to the left and fill in the right-hand side */ static void pan_view_right(struct t_disp* pdisp) { int move; move = MIN(HSCROLL, pdisp->width - pdisp->x - LCD_WIDTH); if (move > 0) { MYXLCD(scroll_left)(move); /* scroll left */ pdisp->x += move; #ifdef HAVE_LCD_COLOR yuv_bitmap_part( pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x + LCD_WIDTH - move, pdisp->y, pdisp->stride, LCD_WIDTH - move, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */ move, MIN(LCD_HEIGHT, pdisp->height), /* w, h */ jpeg_settings.colour_mode, jpeg_settings.dither_mode); #else MYXLCD(gray_bitmap_part)( pdisp->bitmap[0], pdisp->x + LCD_WIDTH - move, pdisp->y, pdisp->stride, LCD_WIDTH - move, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */ move, MIN(LCD_HEIGHT, pdisp->height)); /* w, h */ #endif MYLCD_UPDATE(); } } /* Pan the viewing window left - move image to the right and fill in the left-hand side */ static void pan_view_left(struct t_disp* pdisp) { int move; move = MIN(HSCROLL, pdisp->x); if (move > 0) { MYXLCD(scroll_right)(move); /* scroll right */ pdisp->x -= move; #ifdef HAVE_LCD_COLOR yuv_bitmap_part( pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x, pdisp->y, pdisp->stride, 0, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */ move, MIN(LCD_HEIGHT, pdisp->height), /* w, h */ jpeg_settings.colour_mode, jpeg_settings.dither_mode); #else MYXLCD(gray_bitmap_part)( pdisp->bitmap[0], pdisp->x, pdisp->y, pdisp->stride, 0, MAX(0, (LCD_HEIGHT-pdisp->height)/2), /* x, y */ move, MIN(LCD_HEIGHT, pdisp->height)); /* w, h */ #endif MYLCD_UPDATE(); } } /* Pan the viewing window up - move image down and fill in the top */ static void pan_view_up(struct t_disp* pdisp) { int move; move = MIN(VSCROLL, pdisp->y); if (move > 0) { MYXLCD(scroll_down)(move); /* scroll down */ pdisp->y -= move; #ifdef HAVE_LCD_COLOR if (jpeg_settings.dither_mode == DITHER_DIFFUSION) { /* Draw over the band at the top of the last update caused by lack of error history on line zero. */ move = MIN(move + 1, pdisp->y + pdisp->height); } yuv_bitmap_part( pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x, pdisp->y, pdisp->stride, MAX(0, (LCD_WIDTH-pdisp->width)/2), 0, /* x, y */ MIN(LCD_WIDTH, pdisp->width), move, /* w, h */ jpeg_settings.colour_mode, jpeg_settings.dither_mode); #else MYXLCD(gray_bitmap_part)( pdisp->bitmap[0], pdisp->x, pdisp->y, pdisp->stride, MAX(0, (LCD_WIDTH-pdisp->width)/2), 0, /* x, y */ MIN(LCD_WIDTH, pdisp->width), move); /* w, h */ #endif MYLCD_UPDATE(); } } /* Pan the viewing window down - move image up and fill in the bottom */ static void pan_view_down(struct t_disp* pdisp) { int move; move = MIN(VSCROLL, pdisp->height - pdisp->y - LCD_HEIGHT); if (move > 0) { MYXLCD(scroll_up)(move); /* scroll up */ pdisp->y += move; #ifdef HAVE_LCD_COLOR if (jpeg_settings.dither_mode == DITHER_DIFFUSION) { /* Save the line that was on the last line of the display and draw one extra line above then recover the line with image data that had an error history when it was drawn. */ move++, pdisp->y--; rb->memcpy(rgb_linebuf, rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH, LCD_WIDTH*sizeof (fb_data)); } yuv_bitmap_part( pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x, pdisp->y + LCD_HEIGHT - move, pdisp->stride, MAX(0, (LCD_WIDTH-pdisp->width)/2), LCD_HEIGHT - move, /* x, y */ MIN(LCD_WIDTH, pdisp->width), move, /* w, h */ jpeg_settings.colour_mode, jpeg_settings.dither_mode); if (jpeg_settings.dither_mode == DITHER_DIFFUSION) { /* Cover the first row drawn with previous image data. */ rb->memcpy(rb->lcd_framebuffer + (LCD_HEIGHT - move)*LCD_WIDTH, rgb_linebuf, LCD_WIDTH*sizeof (fb_data)); pdisp->y++; } #else MYXLCD(gray_bitmap_part)( pdisp->bitmap[0], pdisp->x, pdisp->y + LCD_HEIGHT - move, pdisp->stride, MAX(0, (LCD_WIDTH-pdisp->width)/2), LCD_HEIGHT - move, /* x, y */ MIN(LCD_WIDTH, pdisp->width), move); /* w, h */ #endif MYLCD_UPDATE(); } } /* interactively scroll around the image */ int scroll_bmp(struct t_disp* pdisp) { int button; int lastbutton = 0; while (true) { if (slideshow_enabled) button = rb->button_get_w_tmo(jpeg_settings.ss_timeout * HZ); else button = rb->button_get(true); running_slideshow = false; switch(button) { case JPEG_LEFT: if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE) return change_filename(DIR_PREV); case JPEG_LEFT | BUTTON_REPEAT: pan_view_left(pdisp); break; case JPEG_RIGHT: if (!(ds < ds_max) && entries > 0 && jpg.x_size <= MAX_X_SIZE) return change_filename(DIR_NEXT); case JPEG_RIGHT | BUTTON_REPEAT: pan_view_right(pdisp); break; case JPEG_UP: case JPEG_UP | BUTTON_REPEAT: pan_view_up(pdisp); break; case JPEG_DOWN: case JPEG_DOWN | BUTTON_REPEAT: pan_view_down(pdisp); break; case BUTTON_NONE: if (!slideshow_enabled) break; running_slideshow = true; if (entries > 0) return change_filename(DIR_NEXT); break; #ifdef JPEG_SLIDE_SHOW case JPEG_SLIDE_SHOW: slideshow_enabled = !slideshow_enabled; running_slideshow = slideshow_enabled; break; #endif #ifdef JPEG_NEXT_REPEAT case JPEG_NEXT_REPEAT: #endif case JPEG_NEXT: if (entries > 0) return change_filename(DIR_NEXT); break; #ifdef JPEG_PREVIOUS_REPEAT case JPEG_PREVIOUS_REPEAT: #endif case JPEG_PREVIOUS: if (entries > 0) return change_filename(DIR_PREV); break; case JPEG_ZOOM_IN: #ifdef JPEG_ZOOM_PRE if (lastbutton != JPEG_ZOOM_PRE) break; #endif return ZOOM_IN; break; case JPEG_ZOOM_OUT: #ifdef JPEG_ZOOM_PRE if (lastbutton != JPEG_ZOOM_PRE) break; #endif return ZOOM_OUT; break; #ifdef JPEG_RC_MENU case JPEG_RC_MENU: #endif case JPEG_MENU: #ifdef USEGSLIB grey_show(false); /* switch off greyscale overlay */ #endif if (show_menu() == 1) return PLUGIN_OK; #ifdef USEGSLIB grey_show(true); /* switch on greyscale overlay */ #else yuv_bitmap_part( pdisp->bitmap, pdisp->csub_x, pdisp->csub_y, pdisp->x, pdisp->y, pdisp->stride, MAX(0, (LCD_WIDTH - pdisp->width) / 2), MAX(0, (LCD_HEIGHT - pdisp->height) / 2), MIN(LCD_WIDTH, pdisp->width), MIN(LCD_HEIGHT, pdisp->height), jpeg_settings.colour_mode, jpeg_settings.dither_mode); MYLCD_UPDATE(); #endif break; default: if (rb->default_event_handler_ex(button, cleanup, NULL) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; break; } /* switch */ if (button != BUTTON_NONE) lastbutton = button; } /* while (true) */ } /********************* main function *************************/ /* callback updating a progress meter while JPEG decoding */ void cb_progess(int current, int total) { rb->yield(); /* be nice to the other threads */ if(!running_slideshow) { rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-8, LCD_WIDTH, 8, total, 0, current, HORIZONTAL); rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); } #ifndef USEGSLIB else { /* in slideshow mode, keep gui interference to a minimum */ rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, LCD_HEIGHT-4, LCD_WIDTH, 4, total, 0, current, HORIZONTAL); rb->lcd_update_rect(0, LCD_HEIGHT-4, LCD_WIDTH, 4); } #endif } int jpegmem(struct jpeg *p_jpg, int ds) { int size; size = (p_jpg->x_phys/ds/p_jpg->subsample_x[0]) * (p_jpg->y_phys/ds/p_jpg->subsample_y[0]); #ifdef HAVE_LCD_COLOR if (p_jpg->blocks > 1) /* colour, add requirements for chroma */ { size += (p_jpg->x_phys/ds/p_jpg->subsample_x[1]) * (p_jpg->y_phys/ds/p_jpg->subsample_y[1]); size += (p_jpg->x_phys/ds/p_jpg->subsample_x[2]) * (p_jpg->y_phys/ds/p_jpg->subsample_y[2]); } #endif return size; } /* how far can we zoom in without running out of memory */ int min_downscale(struct jpeg *p_jpg, int bufsize) { int downscale = 8; if (jpegmem(p_jpg, 8) > bufsize) return 0; /* error, too large, even 1:8 doesn't fit */ while (downscale > 1 && jpegmem(p_jpg, downscale/2) <= bufsize) downscale /= 2; return downscale; } /* how far can we zoom out, to fit image into the LCD */ int max_downscale(struct jpeg *p_jpg) { int downscale = 1; while (downscale < 8 && (p_jpg->x_size > LCD_WIDTH*downscale || p_jpg->y_size > LCD_HEIGHT*downscale)) { downscale *= 2; } return downscale; } /* return decoded or cached image */ struct t_disp* get_image(struct jpeg* p_jpg, int ds) { int w, h; /* used to center output */ int size; /* decompressed image size */ long time; /* measured ticks */ int status; struct t_disp* p_disp = &disp[ds]; /* short cut */ if (p_disp->bitmap[0] != NULL) { return p_disp; /* we still have it */ } /* assign image buffer */ /* physical size needed for decoding */ size = jpegmem(p_jpg, ds); if (buf_size <= size) { /* have to discard the current */ int i; for (i=1; i<=8; i++) disp[i].bitmap[0] = NULL; /* invalidate all bitmaps */ buf = buf_root; /* start again from the beginning of the buffer */ buf_size = root_size; } #ifdef HAVE_LCD_COLOR if (p_jpg->blocks > 1) /* colour jpeg */ { int i; for (i = 1; i < 3; i++) { size = (p_jpg->x_phys / ds / p_jpg->subsample_x[i]) * (p_jpg->y_phys / ds / p_jpg->subsample_y[i]); p_disp->bitmap[i] = buf; buf += size; buf_size -= size; } p_disp->csub_x = p_jpg->subsample_x[1]; p_disp->csub_y = p_jpg->subsample_y[1]; } else { p_disp->csub_x = p_disp->csub_y = 0; p_disp->bitmap[1] = p_disp->bitmap[2] = buf; } #endif /* size may be less when decoded (if height is not block aligned) */ size = (p_jpg->x_phys/ds) * (p_jpg->y_size / ds); p_disp->bitmap[0] = buf; buf += size; buf_size -= size; if(!running_slideshow) { rb->snprintf(print, sizeof(print), "decoding %d*%d", p_jpg->x_size/ds, p_jpg->y_size/ds); rb->lcd_puts(0, 3, print); rb->lcd_update(); } /* update image properties */ p_disp->width = p_jpg->x_size / ds; p_disp->stride = p_jpg->x_phys / ds; /* use physical size for stride */ p_disp->height = p_jpg->y_size / ds; /* the actual decoding */ time = *rb->current_tick; #ifdef HAVE_ADJUSTABLE_CPU_FREQ rb->cpu_boost(true); status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess); rb->cpu_boost(false); #else status = jpeg_decode(p_jpg, p_disp->bitmap, ds, cb_progess); #endif if (status) { rb->splashf(HZ, "decode error %d", status); file_pt[curfile] = '\0'; return NULL; } time = *rb->current_tick - time; if(!running_slideshow) { rb->snprintf(print, sizeof(print), " %ld.%02ld sec ", time/HZ, time%HZ); rb->lcd_getstringsize(print, &w, &h); /* centered in progress bar */ rb->lcd_putsxy((LCD_WIDTH - w)/2, LCD_HEIGHT - h, print); rb->lcd_update(); } return p_disp; } /* set the view to the given center point, limit if necessary */ void set_view (struct t_disp* p_disp, int cx, int cy) { int x, y; /* plain center to available width/height */ x = cx - MIN(LCD_WIDTH, p_disp->width) / 2; y = cy - MIN(LCD_HEIGHT, p_disp->height) / 2; /* limit against upper image size */ x = MIN(p_disp->width - LCD_WIDTH, x); y = MIN(p_disp->height - LCD_HEIGHT, y); /* limit against negative side */ x = MAX(0, x); y = MAX(0, y); p_disp->x = x; /* set the values */ p_disp->y = y; } /* calculate the view center based on the bitmap position */ void get_view(struct t_disp* p_disp, int* p_cx, int* p_cy) { *p_cx = p_disp->x + MIN(LCD_WIDTH, p_disp->width) / 2; *p_cy = p_disp->y + MIN(LCD_HEIGHT, p_disp->height) / 2; } /* load, decode, display the image */ int load_and_show(char* filename) { int fd; int filesize; unsigned char* buf_jpeg; /* compressed JPEG image */ int status; struct t_disp* p_disp; /* currenly displayed image */ int cx, cy; /* view center */ fd = rb->open(filename, O_RDONLY); if (fd < 0) { rb->snprintf(print,sizeof(print),"err opening %s:%d",filename,fd); rb->splash(HZ, print); return PLUGIN_ERROR; } filesize = rb->filesize(fd); rb->memset(&disp, 0, sizeof(disp)); buf = buf_images + filesize; buf_size = buf_images_size - filesize; /* allocate JPEG buffer */ buf_jpeg = buf_images; buf_root = buf; /* we can start the decompressed images behind it */ root_size = buf_size; if (buf_size <= 0) { #if PLUGIN_BUFFER_SIZE >= MIN_MEM if(plug_buf) { rb->close(fd); rb->lcd_setfont(FONT_SYSFIXED); rb->lcd_clear_display(); rb->snprintf(print,sizeof(print),"%s:",rb->strrchr(filename,'/')+1); rb->lcd_puts(0,0,print); rb->lcd_puts(0,1,"Not enough plugin memory!"); rb->lcd_puts(0,2,"Zoom In: Stop playback."); if(entries>1) rb->lcd_puts(0,3,"Left/Right: Skip File."); rb->lcd_puts(0,4,"Off: Quit."); rb->lcd_update(); rb->lcd_setfont(FONT_UI); rb->button_clear_queue(); while (1) { int button = rb->button_get(true); switch(button) { case JPEG_ZOOM_IN: plug_buf = false; buf_images = rb->plugin_get_audio_buffer( (size_t *)&buf_images_size); /*try again this file, now using the audio buffer */ return PLUGIN_OTHER; #ifdef JPEG_RC_MENU case JPEG_RC_MENU: #endif case JPEG_MENU: return PLUGIN_OK; case JPEG_LEFT: if(entries>1) { rb->lcd_clear_display(); return change_filename(DIR_PREV); } break; case JPEG_RIGHT: if(entries>1) { rb->lcd_clear_display(); return change_filename(DIR_NEXT); } break; default: if(rb->default_event_handler_ex(button, cleanup, NULL) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; } } } else #endif { rb->splash(HZ, "Out of Memory"); rb->close(fd); return PLUGIN_ERROR; } } if(!running_slideshow) { #if LCD_DEPTH > 1 rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); rb->lcd_set_backdrop(NULL); #endif rb->lcd_clear_display(); rb->snprintf(print, sizeof(print), "%s:", rb->strrchr(filename,'/')+1); rb->lcd_puts(0, 0, print); rb->lcd_update(); rb->snprintf(print, sizeof(print), "loading %d bytes", filesize); rb->lcd_puts(0, 1, print); rb->lcd_update(); } rb->read(fd, buf_jpeg, filesize); rb->close(fd); if(!running_slideshow) { rb->snprintf(print, sizeof(print), "decoding markers"); rb->lcd_puts(0, 2, print); rb->lcd_update(); } #ifndef SIMULATOR else if(immediate_ata_off) { /* running slideshow and time is long enough: power down disk */ rb->storage_sleep(); } #endif rb->memset(&jpg, 0, sizeof(jpg)); /* clear info struct */ /* process markers, unstuffing */ status = process_markers(buf_jpeg, filesize, &jpg); if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0)) { /* bad format or minimum components not contained */ rb->splashf(HZ, "unsupported %d", status); file_pt[curfile] = '\0'; return change_filename(direction); } if (!(status & DHT)) /* if no Huffman table present: */ default_huff_tbl(&jpg); /* use default */ build_lut(&jpg); /* derive Huffman and other lookup-tables */ if(!running_slideshow) { rb->snprintf(print, sizeof(print), "image %dx%d", jpg.x_size, jpg.y_size); rb->lcd_puts(0, 2, print); rb->lcd_update(); } ds_max = max_downscale(&jpg); /* check display constraint */ ds_min = min_downscale(&jpg, buf_size); /* check memory constraint */ if (ds_min == 0) { rb->splash(HZ, "too large"); file_pt[curfile] = '\0'; return change_filename(direction); } ds = ds_max; /* initials setting */ cx = jpg.x_size/ds/2; /* center the view */ cy = jpg.y_size/ds/2; do /* loop the image prepare and decoding when zoomed */ { p_disp = get_image(&jpg, ds); /* decode or fetch from cache */ if (p_disp == NULL) return change_filename(direction); set_view(p_disp, cx, cy); if(!running_slideshow) { rb->snprintf(print, sizeof(print), "showing %dx%d", p_disp->width, p_disp->height); rb->lcd_puts(0, 3, print); rb->lcd_update(); } MYLCD(clear_display)(); #ifdef HAVE_LCD_COLOR yuv_bitmap_part( p_disp->bitmap, p_disp->csub_x, p_disp->csub_y, p_disp->x, p_disp->y, p_disp->stride, MAX(0, (LCD_WIDTH - p_disp->width) / 2), MAX(0, (LCD_HEIGHT - p_disp->height) / 2), MIN(LCD_WIDTH, p_disp->width), MIN(LCD_HEIGHT, p_disp->height), jpeg_settings.colour_mode, jpeg_settings.dither_mode); #else MYXLCD(gray_bitmap_part)( p_disp->bitmap[0], p_disp->x, p_disp->y, p_disp->stride, MAX(0, (LCD_WIDTH - p_disp->width) / 2), MAX(0, (LCD_HEIGHT - p_disp->height) / 2), MIN(LCD_WIDTH, p_disp->width), MIN(LCD_HEIGHT, p_disp->height)); #endif MYLCD_UPDATE(); #ifdef USEGSLIB grey_show(true); /* switch on greyscale overlay */ #endif /* drawing is now finished, play around with scrolling * until you press OFF or connect USB */ while (1) { status = scroll_bmp(p_disp); if (status == ZOOM_IN) { if (ds > ds_min) { ds /= 2; /* reduce downscaling to zoom in */ get_view(p_disp, &cx, &cy); cx *= 2; /* prepare the position in the new image */ cy *= 2; } else continue; } if (status == ZOOM_OUT) { if (ds < ds_max) { ds *= 2; /* increase downscaling to zoom out */ get_view(p_disp, &cx, &cy); cx /= 2; /* prepare the position in the new image */ cy /= 2; } else continue; } break; } #ifdef USEGSLIB grey_show(false); /* switch off overlay */ #endif rb->lcd_clear_display(); } while (status != PLUGIN_OK && status != PLUGIN_USB_CONNECTED && status != PLUGIN_OTHER); #ifdef USEGSLIB rb->lcd_update(); #endif return status; } /******************** Plugin entry point *********************/ enum plugin_status plugin_start(const void* parameter) { int condition; #ifdef USEGSLIB long greysize; /* helper */ #endif #if LCD_DEPTH > 1 old_backdrop = rb->lcd_get_backdrop(); #endif if(!parameter) return PLUGIN_ERROR; rb->strcpy(np_file, parameter); get_pic_list(); if(!entries) return PLUGIN_ERROR; #if (PLUGIN_BUFFER_SIZE >= MIN_MEM) && !defined(SIMULATOR) if(rb->audio_status()) { buf = rb->plugin_get_buffer((size_t *)&buf_size) + (entries * sizeof(char**)); buf_size -= (entries * sizeof(char**)); plug_buf = true; } else buf = rb->plugin_get_audio_buffer((size_t *)&buf_size); #else buf = rb->plugin_get_audio_buffer(&buf_size) + (entries * sizeof(char**)); buf_size -= (entries * sizeof(char**)); #endif #ifdef USEGSLIB if (!grey_init(buf, buf_size, GREY_ON_COP, LCD_WIDTH, LCD_HEIGHT, &greysize)) { rb->splash(HZ, "grey buf error"); return PLUGIN_ERROR; } buf += greysize; buf_size -= greysize; #endif /* should be ok to just load settings since the plugin itself has just been loaded from disk and the drive should be spinning */ configfile_load(JPEG_CONFIGFILE, jpeg_config, ARRAYLEN(jpeg_config), JPEG_SETTINGS_MINVERSION); old_settings = jpeg_settings; buf_images = buf; buf_images_size = buf_size; /* Turn off backlight timeout */ backlight_force_on(); /* backlight control in lib/helper.c */ do { condition = load_and_show(np_file); }while (condition != PLUGIN_OK && condition != PLUGIN_USB_CONNECTED && condition != PLUGIN_ERROR); if (rb->memcmp(&jpeg_settings, &old_settings, sizeof (jpeg_settings))) { /* Just in case drive has to spin, keep it from looking locked */ rb->splash(0, "Saving Settings"); configfile_save(JPEG_CONFIGFILE, jpeg_config, ARRAYLEN(jpeg_config), JPEG_SETTINGS_VERSION); } #if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE) /* set back ata spindown time in case we changed it */ rb->storage_spindown(rb->global_settings->disk_spindown); #endif /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); /* backlight control in lib/helper.c */ #ifdef USEGSLIB grey_release(); /* deinitialize */ #endif return condition; }