af9f405651
Replace the old menu API with the "new" one (a very long time overdue so huge thanks for the work.) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21306 a1c6a512-1295-4272-9138-f99709370657
1249 lines
35 KiB
C
1249 lines
35 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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/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 ***********************************/
|
|
|
|
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;
|
|
}
|