summaryrefslogtreecommitdiff
path: root/apps/plugins/jpeg/jpeg.c
diff options
context:
space:
mode:
authorDave Chapman <dave@dchapman.com>2008-10-21 16:05:46 +0000
committerDave Chapman <dave@dchapman.com>2008-10-21 16:05:46 +0000
commit76596deaf1c90f107d43542bf60c9bad1a7ffd68 (patch)
treeba3a919baa3a3ddd3064f86d98bc220b4daee654 /apps/plugins/jpeg/jpeg.c
parentb0b3f0339ab928530ceac34a0d2714b266f8d831 (diff)
downloadrockbox-76596deaf1c90f107d43542bf60c9bad1a7ffd68.zip
rockbox-76596deaf1c90f107d43542bf60c9bad1a7ffd68.tar.gz
rockbox-76596deaf1c90f107d43542bf60c9bad1a7ffd68.tar.bz2
rockbox-76596deaf1c90f107d43542bf60c9bad1a7ffd68.tar.xz
Move the monolithic jpeg viewer into its own subdirectory and split it into three (for now - maybe it should be split further) files - jpeg.c (the main plugin/viewer parts), jpeg_decoder.c (the actual decoder) and. for colour targets only, yuv2rgb.c. The intention of this commit is as a first step towards abstracting this viewer into a reusable jpeg decoder and a multi-format image viewer.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18853 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/jpeg/jpeg.c')
-rw-r--r--apps/plugins/jpeg/jpeg.c1235
1 files changed, 1235 insertions, 0 deletions
diff --git a/apps/plugins/jpeg/jpeg.c b/apps/plugins/jpeg/jpeg.c
new file mode 100644
index 0000000..d70fca8
--- /dev/null
+++ b/apps/plugins/jpeg/jpeg.c
@@ -0,0 +1,1235 @@
+/***************************************************************************
+* __________ __ ___.
+* 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 <lib/playback_control.h>
+#include <lib/oldmenuapi.h>
+#include <lib/helper.h>
+#include <lib/configfile.h>
+
+#include <lib/grey.h>
+#include <lib/xlcd.h>
+
+#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 ***********************************/
+
+const struct plugin_api* rb;
+MEM_FUNCTION_WRAPPERS(rb);
+
+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, &jpeg_settings.colour_mode,
+ "Colour Mode", (char *[]){ "Colour", "Grayscale" }, NULL },
+ { TYPE_ENUM, 0, DITHER_NUM_MODES, &jpeg_settings.dither_mode,
+ "Dither Mode", (char *[]){ "None", "Ordered", "Diffusion" }, NULL },
+#endif
+ { TYPE_INT, SS_MIN_TIMEOUT, SS_MAX_TIMEOUT, &jpeg_settings.ss_timeout,
+ "Slideshow Time", NULL, NULL},
+};
+
+#if LCD_DEPTH > 1
+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 */
+struct t_disp disp[9];
+
+/* my memory pool (from the mp3 buffer) */
+char print[32]; /* use a common snprintf() buffer */
+unsigned char* buf; /* up to here currently used by image(s) */
+
+/* the remaining free part of the buffer for compressed+uncompressed images */
+unsigned char* buf_images;
+
+ssize_t buf_size, buf_images_size;
+/* the root of the images, hereafter are decompresed ones */
+unsigned char* buf_root;
+int root_size;
+
+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];
+int curfile = 0, direction = DIR_NONE, entries = 0;
+
+/* list of the jpeg files */
+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;
+}
+
+static void display_options(void)
+{
+ static const struct menu_item items[] = {
+ { "Grayscale", set_option_grayscale },
+ { "Dithering", set_option_dithering },
+ };
+
+ int m = menu_init(rb, items, ARRAYLEN(items),
+ NULL, NULL, NULL, NULL);
+ menu_run(m);
+ menu_exit(m);
+}
+#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 m;
+ int result;
+
+ enum menu_id
+ {
+ MIID_QUIT = 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_RETURN,
+ };
+
+ static const struct menu_item items[] = {
+ [MIID_QUIT] =
+ { "Quit", NULL },
+ [MIID_TOGGLE_SS_MODE] =
+ { "Toggle Slideshow Mode", NULL },
+ [MIID_CHANGE_SS_MODE] =
+ { "Change Slideshow Time", NULL },
+#if PLUGIN_BUFFER_SIZE >= MIN_MEM
+ [MIID_SHOW_PLAYBACK_MENU] =
+ { "Show Playback Menu", NULL },
+#endif
+#ifdef HAVE_LCD_COLOR
+ [MIID_DISPLAY_OPTIONS] =
+ { "Display Options", NULL },
+#endif
+ [MIID_RETURN] =
+ { "Return", NULL },
+ };
+
+ static const struct opt_items slideshow[2] = {
+ { "Disable", -1 },
+ { "Enable", -1 },
+ };
+
+ m = menu_init(rb, items, sizeof(items) / sizeof(*items),
+ NULL, NULL, NULL, NULL);
+ result=menu_show(m);
+
+ switch (result)
+ {
+ case MIID_QUIT:
+ menu_exit(m);
+ return 1;
+ 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(rb, NULL);
+ }
+ else
+ {
+ rb->splash(HZ, "Cannot restart playback");
+ }
+ break;
+#endif
+#ifdef HAVE_LCD_COLOR
+ case MIID_DISPLAY_OPTIONS:
+ display_options();
+ break;
+#endif
+ case MIID_RETURN:
+ break;
+ }
+
+#if !defined(SIMULATOR) && defined(HAVE_DISK_STORAGE)
+ /* change ata spindown time based on slideshow time setting */
+ immediate_ata_off = false;
+ rb->ata_spindown(rb->global_settings->disk_spindown);
+
+ if (slideshow_enabled)
+ {
+ if(jpeg_settings.ss_timeout < 10)
+ {
+ /* slideshow times < 10s keep disk spinning */
+ rb->ata_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();
+ menu_exit(m);
+ return 0;
+}
+/* interactively scroll around the image */
+int scroll_bmp(struct t_disp* pdisp)
+{
+ int lastbutton = 0;
+
+ while (true)
+ {
+ int button;
+ int move;
+
+ 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:
+ 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();
+ }
+ 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:
+ 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();
+ }
+ break;
+
+ case JPEG_UP:
+ case JPEG_UP | BUTTON_REPEAT:
+ 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();
+ }
+ break;
+
+ case JPEG_DOWN:
+ case JPEG_DOWN | BUTTON_REPEAT:
+ 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();
+ }
+ 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->ata_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 struct plugin_api* api, const void* parameter)
+{
+ rb = api;
+
+ 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(rb, 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;
+#else
+ xlcd_init(rb);
+#endif
+
+ /* should be ok to just load settings since a parameter is present
+ here and the drive should be spinning */
+ configfile_init(rb);
+ 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(rb); /* 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->ata_spindown(rb->global_settings->disk_spindown);
+#endif
+
+ /* Turn on backlight timeout (revert to settings) */
+ backlight_use_settings(rb); /* backlight control in lib/helper.c */
+
+#ifdef USEGSLIB
+ grey_release(); /* deinitialize */
+#endif
+
+ return condition;
+}